senclaw/claude-code
app A coding agent as a Space App: point it at a folder, describe the work, and it plans, reads, edits and runs commands across many turns until the job is done. Powered by Claude. Four trust levels — read-only planning, approve every tool call, auto-accept file edits, or fully unattended. An approval card shows each command before it runs and lets you edit it first
Install
senclaw app install senclaw/claude-code
Verifies sha512 and SenClaw’s signature before writing to disk.
Documentation
Claude Code (Space App)
A coding agent as a Space App. Point it at a folder, describe the work, and it plans, reads, edits and runs commands over many turns — with an approval gate, a recorded timeline, and a git marker taken before it starts.
- Port 4790 · MCP **
claude-code-mcp** at/api/mcp/sse - DB:
~/.senclaw/space-apps/claude-code/coder.db(overrideSENCLAW_DATA_DIR) - Drives the **user's own
claudebinary** — nothing is bundled, no provider key of the app's own. Token spend is reported back withusage.report.
apps/claude-code/
├── src/
│ ├── main.rs serve web/dist + /api, or dispatch a CLI subcommand
│ ├── agent.rs spawn `claude -p`, duplex NDJSON, sessions, SIGTERM, usage
│ ├── api.rs REST + SSE, app state
│ ├── audit.rs timeline from the event stream, git markers, import
│ ├── mcp.rs two MCP servers: per-run approval + the control surface
│ ├── pty.rs integrated terminal: WebSocket ↔ PTY (shell or `claude`)
│ ├── workspace.rs path-safe file read/write/search for the editor
│ ├── cli.rs run / history / timeline / sessions / replay / import / health
│ └── db.rs SQLite (WAL): meta + agent_runs + agent_events
├── web/ React + Vite, plain CSS
├── skills/ personas/
└── scripts/pack.shWhy it does not use the LLM bridge
Every other app talks to the daemon through llm.request: one system prompt plus one user prompt in, one block of text out, 32k output cap, no tools, no streaming. Editing a repository takes many turns of read/edit/bash over minutes. So this app runs claude as a child process and speaks its duplex NDJSON protocol instead. Background and rationale: [docs/claude-code-integration.md](../../docs/claude-code-integration.md).
Trust levels
| Mode | Permission mode | For |
|---|---|---|
Hỏi (ask) | plan | explore and propose; edits never auto-approve |
Duyệt (review) | default | every tool call shows an approval card |
Cho sửa (edit) | acceptEdits | file edits auto-approve, everything else asks |
Tự chạy (auto) | bypassPermissions | the UI default — never asks; hard deny rules still hold |
Khoá (locked) | dontAsk | denies anything not pre-approved; what MCP and the CLI use |
auto and locked are both unattended and they are not the same thing. dontAsk denies rather than prompting, so a run with no allow list can barely do anything — correct for a caller with nobody to ask, wrong for a button labelled "just get on with it". So the UI defaults to auto, while the MCP control surface and claude-code run default to locked: a caller should not be handed bypassPermissions by default.
Approval works through --permission-prompt-tool: Claude Code calls an MCP tool this app serves, the call parks until the UI answers, and the answer comes back as a PermissionResult. You can edit a command before allowing it.
Three things hold regardless of mode:
- Deny rules (
sudo,rm -rf /,curl … | sh,~/.ssh) are evaluated before the permission mode, soeditandautocannot loosen them. - **
--bare** keeps this machine's hooks, plugins, MCP servers andCLAUDE.mdout of a run. It needsANTHROPIC_API_KEY; without one the app falls back and reportsdegradedReasonrather than failing or staying quiet. - Workflows are off (
disableWorkflows). A workflow launched underclaude -pstarts with no prompt at all, and its subagents runacceptEditswhatever the session mode — it walks straight past the approval UI.
Audit trail
The timeline is built from the NDJSON stream, not from Claude Code hooks.
Hooks were the obvious design and they do not work here: Claude Code skips hooks under --bare, including hooks handed to it inline through --settings (verified against 2.1.227 — the session reaches system/init and runs to a result, and the hook never fires). Dropping --bare to regain hooks would pull the user's own hooks and MCP servers into every run. Reading the stream gives the same rows, costs no subprocess per tool call, and behaves identically in every permission mode.
Each run also gets a git marker before the agent touches anything: HEAD plus a git stash create commit capturing the dirty tree. git stash create writes the commit object without modifying the working tree or pushing onto the stash list, so the marker cannot surprise someone who is mid-edit. GET /diff compares against it — which is the honest answer to "what changed", because Claude Code's own file checkpointing misses anything done through Bash.
Transcripts written outside the app can be folded in with import; re-importing the same session replaces it rather than doubling it.
REST
| Endpoint | Does |
|---|---|
GET /status · GET /health | open folder; is claude installed and keyed? |
POST /open | choose the folder runs default to |
POST /run → GET /events?runId= | start a run; SSE of every message, backlog first |
POST /permit | allow / deny / allow-with-changes |
POST /send · POST /stop | another turn into the same process; SIGTERM |
GET /history · GET /timeline · GET /diff | the audit trail |
GET /sessions · GET /transcript · POST /import | Claude Code transcripts |
GET /terminal (WS) | integrated terminal; ?cmd=claude launches the interactive CLI |
GET/POST /settings | auth mode and agent teams |
GET /commands | slash commands, skills and agents this machine's claude offers |
POST /exec | run one shell command and record it in the timeline |
GET /team | teammates and the shared task list for a run |
GET /tree · GET/POST /file · GET /search | workspace files for the editor |
Workspaces
Several folders can be open at once and each runs its own agent independently: a run is keyed by its own cwd, so nothing below the UI had to change to support it. Each workspace's panels stay mounted and only the active one is shown, so a run in one folder keeps streaming while you work in another. Terminals are the exception — they cost a real process, so they wait for first use.
Pick a folder with the + button. The picker marks git repos, because a run outside one has no marker to diff against afterwards.
Authentication
Two modes, and the choice decides isolation as much as billing:
| Mode | What it does | Cost |
|---|---|---|
| API key (default) | --bare + ANTHROPIC_API_KEY | Isolated: none of this machine's hooks, plugins, MCP servers or CLAUDE.md load |
| Claude Pro / Team | uses the login claude already has | Not isolated: --bare must be dropped, so all of the above become active in the run |
Picking API key with no key present falls back to the subscription path rather than failing, and says so in degradedReason. Deny rules and the approval gate are CLI flags, so they hold in both modes.
Slash commands and skills
/name works in the prompt box: Claude Code expands user-invoked skills and custom commands in -p mode before running. ⚙ → Kiểm tra lists what this machine offers, read from the system/init event — which Claude Code emits before the first API call, so the probe costs no tokens.
Agent teams
Off by default. When enabled, the child gets CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 and can spawn teammates: separate Claude Code instances with their own context that coordinate through a shared task list. GET /team reads the team config and task list Claude Code writes under ~/.claude/teams/session-<id8>/ and ~/.claude/tasks/session-<id8>/.
Teammate permission prompts surface in the lead session, so they still reach the approval gate. Teams are experimental in Claude Code itself and cost several times the tokens of one session, which is why they stay opt-in here.
Files
A file tree and a CodeMirror editor over the open workspace, so you can read what the agent is about to change and fix it by hand. Cmd/Ctrl+S saves. Paths are resolved through the same guard the agent's tools use: anything resolving outside the workspace is refused, on read and on write.
Staying alive
A Space App defaults to on-demand: the daemon starts it and its reaper stops it about 60s after the last request through the daemon's proxy. This app's UI is an iframe that talks straight to its own port, so from the daemon's side it looks untouched from the second it boots — it was being stopped mid-session, and the symptom was the terminal going [terminal đã đóng] and REST calls failing.
src/keepalive.rs reports the app's own use: any request that is not the keepalive ping marks activity, and while activity is recent the app pings its own proxy path on the daemon, which is exactly what a real user request would have done. The web UI heartbeats every 30s while its tab is visible, so an open but idle tab counts. Close the tab and the pings stop, and the reaper takes the app down as intended. SENCLAW_KEEPALIVE=0 disables it.
Terminal
A real PTY in the open folder, bridged to xterm.js. Two buttons:
- Shell — the ordinary reasons: run the tests yourself, read
git log, check what the agent just did. - claude (tương tác) — the user's own interactive CLI, the way the other wrappers offer it: their login, their hooks, their MCP servers, their prompts. A plain
claudedoes not read this app's--settings, so the agent-teams toggle is passed to it as the env var it does read.
The second one deliberately runs outside this app. The approval gate, the deny rules and the audit trail govern runs (/api/run), not whatever someone types at a shell. The UI says so on the panel rather than letting the two blur together — a terminal that looked governed but was not would be worse than no terminal at all.
MCP — how another app drives this one
/api/mcp/control exposes coder_run, coder_status, coder_timeline and coder_history. This is the seam for BA Studio: read a user story out of ba-mcp, hand it to coder_run, read back what the agent actually did.
claude --mcp-config '{"mcpServers":{
"ba": {"type":"sse","url":"http://127.0.0.1:4740/api/mcp/sse"},
"coder":{"type":"sse","url":"http://127.0.0.1:4790/api/mcp/sse"}}}'Runs started over MCP are unattended by construction: there is no UI on the other end to answer an approval card, so they deny anything outside allow rather than parking forever. That is also why autoRegister is on despite the tool being able to edit a repo: a run with no allow list in dontAsk mode can barely do anything, and an MCP server that silently fails to appear is a worse failure than one the user can see and disable.
Two transports, on purpose
| Path | Shape | Who uses it |
|---|---|---|
GET/POST /api/mcp/sse + POST /api/mcp/message | legacy MCP HTTP+SSE | the SenClaw daemon, like the other 40 apps in this repo |
POST /api/mcp/control | streamable HTTP | callers configured that way; same handler, alias only |
POST /api/mcp/<runId> | streamable HTTP | Claude Code's --permission-prompt-tool, per run |
The daemon's MCP client opens an SSE stream and reads replies off it. A control server that only answered POST returned 405 on the GET and never connected — verified against a running daemon, which now reports status: "connected" and all four tools. Claude Code is happy with streamable HTTP either way, which is what the per-run permission server uses.
What the daemon picks up
Registering with register-local gives the daemon the manifest, and it copies the skill to ~/.senclaw/managed/skills/<name>/ tagged source: app:claude-code. It does not copy personas — no app's persona appears under managed/, including the 33 others that declare one — so the persona here is declared for correctness and for whenever the daemon starts reading them.
CLI
The same binary, so CI and cron need no iframe and no daemon:
claude-code health # exit 1 when claude is missing or unkeyed
claude-code run "fix the failing test" --mode auto --max-turns 20
claude-code run "audit auth.rs" --json # result object incl. total_cost_usd
claude-code history # recorded runs
claude-code timeline <run-id> # what one run actually did
claude-code import <session-id> # fold a transcript into the timelinerun exits non-zero when the agent failed. --mode review is refused there: it parks every tool call waiting on a UI a shell does not have. CLI runs write to the same audit db as the server, so a CI run shows up in the history too.
Naming
Anthropic's Agent SDK terms list the permitted forms as "Claude Agent", "Claude", and "<YourName> Powered by Claude", and name "Claude Code" among the forms that are not permitted for third-party products. This app is currently named claude-code by explicit choice; if it is ever published to a public hub, that is the thing to revisit first.
Develop
cargo run -p claude-code # API on :4790
cd apps/claude-code/web && npm install && npm run dev
cargo test -p claude-code
apps/claude-code/scripts/pack.sh # -> claude-code-app.zipRegister with a running daemon:
curl -X POST http://127.0.0.1:18788/api/space/apps/register-local \
-H 'Content-Type: application/json' \
-d '{"path":"'"$PWD"'/apps/claude-code"}'Declared permissions
{
"network": [
"127.0.0.1"
],
"exec": [
"./claude-code",
"claude"
]
}Declared by the publisher, not enforced by SenClaw. The CLI compares this against what you already have installed and asks before anything widens.
Published by
The checkmark means SenClaw confirmed who this publisher is. It says nothing about whether their packages are safe.
About
| Owner | @senclaw |
| Latest | 1.0.0 |
| Downloads | 0 |
| Repository | github.com/NortonBen/senclaw-app ↗ |
Versions
| Version | Tags | Artifacts | Published |
|---|---|---|---|
1.0.0 | latest | darwin-arm64, linux-x64, win32-x64 | 2026-08-15 |
Artifacts · 1.0.0
| Platform | Format | Signature | File | Size |
|---|---|---|---|---|
darwin-arm64 | zip | unsigned | claude-code-app.zip | 3947 KB |
linux-x64 | zip | unsigned | claude-code-app.zip | 4395 KB |
win32-x64 | zip | unsigned | claude-code-app.zip | 4396 KB |
Unsigned builds require sign-in to download and are not distributed to the auto-updater, because unsigned executables from a new domain get flagged as unsafe. Sign and notarize (macOS) or Authenticode-sign (Windows) to distribute publicly.
Auto-update endpoints
For a Tauri app — add to tauri.conf.json:
"endpoints": ["https://senclaw.bacnd.com/api/v1/updates/tauri/senclaw/claude-code/{{target}}/{{arch}}/{{current_version}}"]For an electron-updater app — generic provider URL:
https://senclaw.bacnd.com/updates/senclaw/claude-code/stable
Only signed/notarized artifacts are offered as updates — a client fetches the update without credentials, and unsigned binaries are not publicly downloadable.
Something wrong with this package?
Report this package
Report this package
Tell us what is wrong and a moderator will look at it. Be specific — a report that names a version and a behaviour can be checked in minutes; one that says “looks sketchy” cannot be acted on at all.
Not about this package? Contact us instead. What happens to a report is described in reporting and moderation.
API: GET https://senclaw.bacnd.com/api/v1/packages/senclaw/claude-code