A Nix-packaged wrapper around Claude Code that injects custom system prompts hierarchically, disables Bash by default, and supports parameterized plugin loading.
Clown is a Nix flake. The bare wrapper (no plugins) can be run directly:
nix run github:amarbel-llc/clownTo build clown with plugins, use mkCircus from your own flake (see
Plugins below).
Clown wraps the claude binary with four additions:
-
Bash disabled by default — passes
--disallowed-tools 'Bash(*)'to every invocation. -
Auto-mode and auto-memory disabled permanently — the
claude-codebundle is patched to read its managed-settings from clown's own store path, which ships withpermissions.disableAutoMode: "disable"andautoMemoryEnabled: false. Managed settings sit at the highest precedence tier, so neither user settings nor CLI flags can re-enable either feature through clown. -
Hierarchical system prompt injection — walks from
$PWDup to$HOME, collecting.circus/directories along the way:- Replace mode: If any directory contains
.circus/system-prompt, the deepest one replaces Claude's system prompt entirely (--system-prompt-file). - Append mode: All
.mdfiles found in.circus/system-prompt.d/directories are concatenated shallowest-first and appended to the system prompt (--append-system-prompt-file). Builtin fragments (insystem-prompt-append.d/) are always prepended before user fragments.
- Replace mode: If any directory contains
-
Fish shell completions — full completions for all Claude Code flags, plus
clown resumeURI completion driven by theclown sessions-completesubcommand.
All other arguments are passed through to claude unchanged.
Clown ships with zero default plugins. Consumers supply plugin flake inputs
via mkCircus, which resolves plugin directories and burns --plugin-dir
flags into the wrapper at build time.
clown.lib.${system}.mkCircus accepts a single attribute set with a
plugins list. Each plugin definition has two fields:
| Field | Type | Description |
|---|---|---|
flake |
flake input | The plugin's flake input value |
dirs |
list of strings | Paths relative to the flake's default package output. Literal paths or glob patterns. |
It returns { packages.default, devShells.default, checks }.
{
inputs = {
clown.url = "github:amarbel-llc/clown";
clown.inputs.nixpkgs.follows = "nixpkgs";
moxy.url = "github:amarbel-llc/moxy";
moxy.inputs.nixpkgs.follows = "nixpkgs";
bob.url = "github:amarbel-llc/bob";
bob.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, clown, moxy, bob, ... }:
let
system = "aarch64-darwin";
circus = clown.lib.${system}.mkCircus {
plugins = [
{ flake = moxy; dirs = [ "share/purse-first/moxy" ]; }
{ flake = bob; dirs = [ "share/purse-first/*" ]; }
];
};
in {
packages.${system}.default = circus.packages.default;
};
}Each path in dirs must point to a directory containing
.claude-plugin/plugin.json with at minimum a name field. Glob patterns
(*, ?, [) are expanded at build time; non-plugin directories are
silently skipped. A glob that matches zero plugins fails the build.
clown version displays all components including dynamically loaded plugins:
COMPONENT VERSION REV
bob/bob - 474d0c4fac8a084c5378dba51337fea45b86ee2d
bob/caldav - 474d0c4fac8a084c5378dba51337fea45b86ee2d
claude-code 1.0.46 b2b9662ffe1e9a5702e7bfbd983595dd56147dbf
clown 0.0.1 edc5db5...
codex 0.0.1-beta e2dde111aea2c0699531dc616112a96cd55ab8b5
moxy/moxy - a90e0dfbc830700efe28d2238bd2acb5bf8095dc
Plugin rows use <flake-name>/<plugin-name> format. Version and rev come
from plugin.json and the flake input respectively.
For the full specification, see RFC 0001.
Plugins can declare HTTP-based MCP servers that clown automatically launches
and manages. This enables MCP features unavailable over stdio, including
notifications/tools/list_changed and server-initiated requests.
A plugin ships a clown.json alongside .claude-plugin/:
{
"version": 1,
"httpServers": {
"my-server": {
"command": "bin/my-server",
"transport": "streamable-http",
"healthcheck": {
"path": "/healthz",
"interval": "1s",
"timeout": "30s"
}
}
}
}Servers must implement the clown plugin protocol: bind to an ephemeral port,
print a handshake line to stdout (1|1|tcp|<addr>|streamable-http), and
respond to health checks. See RFC 0002
for the full specification.
A plugin can also declare background-shell monitors in clown.json. Each entry
mirrors Anthropic's plugin monitor schema and is injected verbatim into the
compiled .claude-plugin/plugin.json so Claude Code (≥ v2.1.105) handles
spawning, ${...} substitution, and lifecycle. Each stdout line from the
monitor command surfaces in the chat as a notification.
{
"version": 1,
"monitors": [
{
"name": "error-log",
"command": "tail -F ${CLAUDE_PLUGIN_ROOT}/logs/error.log",
"description": "Application error log"
},
{
"name": "deploy-status",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/poll-deploy.sh",
"description": "Deployment status changes",
"when": "on-skill-invoke:debug"
}
]
}Clown does not spawn or supervise monitors — Claude Code does. It is an error
for both clown.json and the same plugin's .claude-plugin/plugin.json to
declare a monitors key; pick one source. See clown-json(5) for the full
schema and validation rules.
Place prompt fragments anywhere in your directory hierarchy:
~/.circus/system-prompt.d/
00-global-rules.md # applied everywhere
~/projects/.circus/system-prompt.d/
00-coding-standards.md # applied in ~/projects and below
~/projects/myapp/.circus/system-prompt
# replaces system prompt entirely for myapp
- Append fragments (
.circus/system-prompt.d/*.md) stack from shallowest to deepest — broader rules come first, project-specific ones last. - A replace file (
.circus/system-prompt) short-circuits the default system prompt. Only the deepest one wins. Append fragments still apply on top.
Requires Nix with flakes enabled.
just build # nix build --show-trace
just clean # rm -rf resultMIT — see LICENSE.
🤡 Sasha, Head Clown