Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
- open-code-review
- skillspector
- herdr
- herdr-outside-container
- officecli
- strix
- colibri
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ This repository contains a _collection_ of Features.
| open-code-review | https://github.com/alibaba/open-code-review | CLI-oriented code review tool for diffs with deterministic checks plus optional LLM review. |
| SkillSpector | https://github.com/NVIDIA/SkillSpector | Standalone security scanner for local AI-agent skill/config files; useful only if you want an AI-dev-security feature. |
| herdr | https://github.com/herdrdev/herdr | Fast, cross-platform CLI tool distributed as a static binary via GitHub releases. |
| herdr-outside-container | https://github.com/herdrdev/herdr | Client-only herdr CLI that controls a Herdr server on the host via a bind-mounted Unix socket, so several dev containers can share one server. |
| officecli | https://github.com/iOfficeAI/OfficeCLI | AI-friendly CLI to read, edit, and automate Word, Excel, and PowerPoint files — single static binary, no Office install required. |
| strix | https://github.com/usestrix/strix | Open-source AI penetration testing tool to find and fix your app's vulnerabilities. |
| colibri | https://github.com/JustVugg/colibri | Run GLM-5.2 (744B MoE) on a consumer machine — pure C, zero deps, experts streamed from disk. |
Expand Down Expand Up @@ -775,6 +776,31 @@ Running `herdr --version` inside the built container will print the version of h
herdr --version
```

### `herdr-outside-container`

Installs `herdr` as a client-only wrapper that talks to a Herdr server running on the host through a bind-mounted socket directory. It never starts a local server or TUI. The mount is required; see [src/herdr-outside-container/README.md](src/herdr-outside-container/README.md) for setup, permissions, and security notes. Do not combine with the `herdr` feature (both install `/usr/local/bin/herdr`).

```jsonc
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"remoteUser": "vscode",
"updateRemoteUserUID": true,
"features": {
"ghcr.io/jsburckhardt/devcontainer-features/herdr-outside-container:1": {
"version": "v0.9.4"
}
},
"mounts": [
"source=${localEnv:HOME}/.local/share/herdr-control,target=/run/herdr-host,type=bind,readonly"
]
}
```

```bash
herdr --version
herdr api snapshot
```

### `colibri`

Running `colibri` inside the built container will print the usage banner of colibri. The installed version is recorded in `/usr/local/share/colibri/VERSION`.
Expand Down
105 changes: 105 additions & 0 deletions src/herdr-outside-container/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
## Share one host Herdr server (Linux)

This Feature installs `herdr` as a client-only wrapper around the release
binary. API commands (`agent`, `pane`, `workspace`, `api`, ...) talk to
`HERDR_SOCKET_PATH`, or `containerSocketPath` when unset. Launching the TUI or a
server, and `--session`/`--remote`/`--machine` routing, are rejected. Use the
`herdr` Feature instead if you want a local Herdr inside the container.

Pin the same Herdr version on host and container. On the Linux Docker host,
start **one** server:

```bash
install -d -m 700 "$HOME/.local/share/herdr-control"
HERDR_SOCKET_PATH="$HOME/.local/share/herdr-control/herdr.sock" herdr
```

In each repository's `.devcontainer/devcontainer.json`:

```json
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"remoteUser": "vscode",
"updateRemoteUserUID": true,
"features": {
"ghcr.io/jsburckhardt/devcontainer-features/herdr-outside-container:1": {
"version": "v0.9.4"
}
},
"mounts": [
"source=${localEnv:HOME}/.local/share/herdr-control,target=/run/herdr-host,type=bind,readonly"
]
}
```

**The mount is required.** The Feature never mounts host sockets itself, because
the host path differs per machine. Mount the socket **directory**, not the
socket file: Herdr recreates the socket on restart and a file mount would keep a
stale inode. Create the host directory before building the container. If you
change the mount `target`, set `containerSocketPath` to match. `readonly` only
prevents filesystem changes, not API writes.

Do not combine this Feature with the `herdr` Feature in the same container: both
install `/usr/local/bin/herdr`.

### Permissions and troubleshooting

Herdr sockets are mode 0600, so the container user needs the **same numeric UID**
as the host socket owner (`updateRemoteUserUID` usually handles this on Linux;
rootless Docker may remap it). Never `chmod 666` the socket.

* **No Unix socket:** start the host server, check `mounts`, rebuild the container.
* **Permission denied:** compare `id -u` with `ls -ln` on the socket.
* **Connection refused:** the socket is stale; restart the host server.
* **Unknown command:** align host and container Herdr versions.

`herdr api snapshot` is a quick connectivity probe.

### Agents across containers

The socket controls terminals owned by the host server; it does not register
container processes. Run each container's agent from its own host Herdr pane
and forward the pane identity:

```bash
devcontainer exec --workspace-folder "$HOME/repos/repo-a" \
env HERDR_ENV=1 HERDR_PANE_ID="$HERDR_PANE_ID" \
HERDR_SOCKET_PATH=/run/herdr-host/herdr.sock copilot
```

Report lifecycle state and name the agent from inside the container, then
other containers can find it and exchange terminal input/output:

```bash
herdr pane report-agent "$HERDR_PANE_ID" --source devcontainer-copilot --agent copilot --state idle --seq 1
herdr agent rename "$HERDR_PANE_ID" backend

# From another container:
herdr agent list
herdr pane run <backend-pane-id> "..."
herdr agent read backend --source recent --lines 100
herdr agent wait backend --until idle --timeout 120000
```

**Limitation:** `herdr agent prompt` and `agent send-keys` return
`agent_not_ready`, because the host pane's foreground process is
`devcontainer exec`, not Copilot. Use `pane run`/`pane send-text` instead; they
skip the readiness checks. Reports must use increasing `--seq` numbers. Release
the agent with `herdr pane release-agent` when it exits. See
[agent integration](https://herdr.dev/docs/add-herdr-support/).

### Platforms

Supported only where the Herdr server and the Docker daemon share a Linux
filesystem: native Linux, or WSL2 with both in the same distribution (not under
`/mnt/c`). Docker Desktop on macOS, native Windows (named pipes), Codespaces and
remote Docker hosts cannot mount a local socket; consider the `herdr` Feature
with Herdr's SSH [machine support](https://herdr.dev/docs/connecting-machines/).

### Security

Socket access is full control of **every** workspace and terminal on that
server: clients can type commands and read other agents' output, including
secrets. There is no per-agent authorization, and the wrapper is not a sandbox.
Share a server only between trusted repositories, use separate servers for
separate trust domains, and avoid approval-skipping modes such as `--yolo`.
130 changes: 130 additions & 0 deletions src/herdr-outside-container/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@

# herdr (outside of container) (herdr-outside-container)

Client-only herdr CLI (herdrdev/herdr) that talks to a Herdr server running on the host through a bind-mounted Unix socket. Never starts a local server or TUI, so several dev containers can share one host Herdr server.

## Example Usage

```json
"features": {
"ghcr.io/jsburckhardt/devcontainer-features/herdr-outside-container:1": {}
}
```

## Options

| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Version of herdr to install (e.g. 'latest' or a specific tag like 'v0.9.4'). Match the host Herdr server version. | string | latest |
| containerSocketPath | Absolute path to the mounted host API socket inside the container. Host source paths are configured in devcontainer.json mounts. | string | /run/herdr-host/herdr.sock |

## Share one host Herdr server (Linux)

This Feature installs `herdr` as a client-only wrapper around the release
binary. API commands (`agent`, `pane`, `workspace`, `api`, ...) talk to
`HERDR_SOCKET_PATH`, or `containerSocketPath` when unset. Launching the TUI or a
server, and `--session`/`--remote`/`--machine` routing, are rejected. Use the
`herdr` Feature instead if you want a local Herdr inside the container.

Pin the same Herdr version on host and container. On the Linux Docker host,
start **one** server:

```bash
install -d -m 700 "$HOME/.local/share/herdr-control"
HERDR_SOCKET_PATH="$HOME/.local/share/herdr-control/herdr.sock" herdr
```

In each repository's `.devcontainer/devcontainer.json`:

```json
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"remoteUser": "vscode",
"updateRemoteUserUID": true,
"features": {
"ghcr.io/jsburckhardt/devcontainer-features/herdr-outside-container:1": {
"version": "v0.9.4"
}
},
"mounts": [
"source=${localEnv:HOME}/.local/share/herdr-control,target=/run/herdr-host,type=bind,readonly"
]
}
```

**The mount is required.** The Feature never mounts host sockets itself, because
the host path differs per machine. Mount the socket **directory**, not the
socket file: Herdr recreates the socket on restart and a file mount would keep a
stale inode. Create the host directory before building the container. If you
change the mount `target`, set `containerSocketPath` to match. `readonly` only
prevents filesystem changes, not API writes.

Do not combine this Feature with the `herdr` Feature in the same container: both
install `/usr/local/bin/herdr`.

### Permissions and troubleshooting

Herdr sockets are mode 0600, so the container user needs the **same numeric UID**
as the host socket owner (`updateRemoteUserUID` usually handles this on Linux;
rootless Docker may remap it). Never `chmod 666` the socket.

* **No Unix socket:** start the host server, check `mounts`, rebuild the container.
* **Permission denied:** compare `id -u` with `ls -ln` on the socket.
* **Connection refused:** the socket is stale; restart the host server.
* **Unknown command:** align host and container Herdr versions.

`herdr api snapshot` is a quick connectivity probe.

### Agents across containers

The socket controls terminals owned by the host server; it does not register
container processes. Run each container's agent from its own host Herdr pane
and forward the pane identity:

```bash
devcontainer exec --workspace-folder "$HOME/repos/repo-a" \
env HERDR_ENV=1 HERDR_PANE_ID="$HERDR_PANE_ID" \
HERDR_SOCKET_PATH=/run/herdr-host/herdr.sock copilot
```

Report lifecycle state and name the agent from inside the container, then
other containers can find it and exchange terminal input/output:

```bash
herdr pane report-agent "$HERDR_PANE_ID" --source devcontainer-copilot --agent copilot --state idle --seq 1
herdr agent rename "$HERDR_PANE_ID" backend

# From another container:
herdr agent list
herdr pane run <backend-pane-id> "..."
herdr agent read backend --source recent --lines 100
herdr agent wait backend --until idle --timeout 120000
```

**Limitation:** `herdr agent prompt` and `agent send-keys` return
`agent_not_ready`, because the host pane's foreground process is
`devcontainer exec`, not Copilot. Use `pane run`/`pane send-text` instead; they
skip the readiness checks. Reports must use increasing `--seq` numbers. Release
the agent with `herdr pane release-agent` when it exits. See
[agent integration](https://herdr.dev/docs/add-herdr-support/).

### Platforms

Supported only where the Herdr server and the Docker daemon share a Linux
filesystem: native Linux, or WSL2 with both in the same distribution (not under
`/mnt/c`). Docker Desktop on macOS, native Windows (named pipes), Codespaces and
remote Docker hosts cannot mount a local socket; consider the `herdr` Feature
with Herdr's SSH [machine support](https://herdr.dev/docs/connecting-machines/).

### Security

Socket access is full control of **every** workspace and terminal on that
server: clients can type commands and read other agents' output, including
secrets. There is no per-agent authorization, and the wrapper is not a sandbox.
Share a server only between trusted repositories, use separate servers for
separate trust domains, and avoid approval-skipping modes such as `--yolo`.


---

_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/jsburckhardt/devcontainer-features/blob/main/src/herdr-outside-container/devcontainer-feature.json). Add additional notes to a `NOTES.md`._
18 changes: 18 additions & 0 deletions src/herdr-outside-container/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "herdr (outside of container)",
"id": "herdr-outside-container",
"version": "1.0.0",
"description": "Client-only herdr CLI (herdrdev/herdr) that talks to a Herdr server running on the host through a bind-mounted Unix socket. Never starts a local server or TUI, so several dev containers can share one host Herdr server.",
"options": {
"version": {
"type": "string",
"default": "latest",
"description": "Version of herdr to install (e.g. 'latest' or a specific tag like 'v0.9.4'). Match the host Herdr server version."
},
"containerSocketPath": {
"type": "string",
"default": "/run/herdr-host/herdr.sock",
"description": "Absolute path to the mounted host API socket inside the container. Host source paths are configured in devcontainer.json mounts."
}
}
}
37 changes: 37 additions & 0 deletions src/herdr-outside-container/herdr-client.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash

set -euo pipefail

fail() {
echo "Herdr outside-of-container: $*" >&2
exit 1
}

case "${1:-}" in
--version|-V|--help|-h|--skill|--default-config)
[ "$#" -eq 1 ] || fail "Use a single help/version flag, or a native API subcommand."
exec /usr/local/lib/herdr/herdr "$@"
;;
# Bare herdr/options launch the TUI; these commands start or manage a local server/install.
""|-*|server|session|update|channel|machine)
fail "Use native API commands such as 'herdr agent list'. Local server/TUI launch is disabled; start Herdr on the host."
;;
esac

# Explicit session selection overrides HERDR_SOCKET_PATH in Herdr.
for arg in "$@"; do
[ "$arg" != "--" ] || break
case "$arg" in
--session|--session=*|--remote|--remote=*|--machine|--machine=*|--no-session)
fail "Session/remote routing is disabled. Select the host session by mounting its API socket."
;;
esac
done

IFS= read -r configured_socket < /usr/local/share/herdr/socket-path
export HERDR_SOCKET_PATH="${HERDR_SOCKET_PATH:-$configured_socket}"
[[ "$HERDR_SOCKET_PATH" = /* ]] || fail "HERDR_SOCKET_PATH must be an absolute Unix socket path."
[ -S "$HERDR_SOCKET_PATH" ] || fail "No Unix socket at '$HERDR_SOCKET_PATH'. Start the host server and bind mount its socket directory in devcontainer.json. Docker Desktop/native Windows sockets cannot be mounted this way."
[ -w "$HERDR_SOCKET_PATH" ] || fail "Socket '$HERDR_SOCKET_PATH' is not writable by UID $(id -u). Match the host UID or grant a narrow host ACL; do not chmod 666."

exec /usr/local/lib/herdr/herdr "$@"
Loading
Loading