1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
schemaVersion: "2"
kind: mixin
name: ori
displayName: Ori — ACP web client for Claude Code
description: >
Starts the ori web server inside the sandbox. Ori is a browser client for
ACP (Agent Client Protocol) code agents: a Go backend serves a React SPA
(Zed-style agent panel: streamed answers, thoughts, tool calls, plans,
permission prompts) plus a workspace panel (file tree with Material icons,
Monaco preview/editor with rendered Markdown and AsciiDoc, interactive
terminal). This kit only launches and describes the server; the binaries
ship in the k33g/ori template image (template/Dockerfile in the ori
repository), so use it together with `--template k33g/ori:0.0.1`.
licenses:
- MIT
# This mixin only makes sense composed with the claude agent kit: the server
# it starts drives Claude Code through the claude-code-acp adapter, and the
# Anthropic credentials/egress come from that kit.
requires:
agent: claude
args:
port:
default: "8888"
description: TCP port the ori server listens on inside the sandbox
pattern: "^[0-9]{2,5}$"
# No extra network permissions: the Anthropic endpoints come from the claude
# agent kit this mixin composes with, and the ACP adapter is baked into the
# template image, so nothing is downloaded at runtime.
# Auto-publishes an ephemeral localhost port at create (the create output
# prints it: "Published web: localhost:<port> -> <port>/tcp"). Pin a fixed
# host port instead with `-p 8888:8888` at create or `sbx ports … --publish`.
# The unquoted arg reference decodes as the integer the schema expects.
ports:
- container: ${{ kit.args.port }}
protocol: tcp
name: web
setup:
startup:
# Startup commands run on every container start through the detached
# dispatcher, so the pgrep guard makes this idempotent. The server binds
# 0.0.0.0 (empty host in --addr) so published ports reach it from the
# host's browser. `user` is omitted: startup commands default to uid 1000,
# the agent user.
#
# The PATH export is load-bearing: the dispatcher runs this via
# `su -s /bin/sh -c … agent`, and su resets PATH to the login.defs
# default — which does not contain the npm prefix where claude-code-acp
# lives, nor ~/.local/bin where claude does.
- command:
- "sh"
- "-c"
- |
export PATH="/usr/local/share/npm-global/bin:/home/agent/.local/bin:$PATH"
pgrep -x ori >/dev/null 2>&1 && exit 0
nohup /usr/local/bin/ori \
--addr ":${{ kit.args.port }}" \
--agent-cmd claude-code-acp \
--cwd "${WORKSPACE_DIR:-/home/agent/workspace}" \
>> /home/agent/.ori.log 2>&1 &
description: Start the ori web server (idempotent)
agentInstructions:
content: |
## Ori web server (kit `ori`)
This sandbox runs **ori**, a web client for Claude Code over ACP. It was
started automatically and serves on **port ${{ kit.args.port }}** (all
interfaces).
- Binaries: `/usr/local/bin/ori` (server) and `/usr/local/bin/ori-mock-agent`
(a deterministic demo ACP agent that needs no credentials).
- Log file: `/home/agent/.ori.log`.
- The server drives its own Claude Code session through the
`claude-code-acp` adapter (preinstalled); credentials are injected by the
sandbox proxy, so no login is needed inside the container.
- Restart it with:
`pkill -x ori; nohup ori --addr ":${{ kit.args.port }}" --agent-cmd claude-code-acp --cwd "$PWD" >> /home/agent/.ori.log 2>&1 &`
- To demo without consuming Claude usage, restart it with
`--agent-cmd ori-mock-agent` instead.
A localhost port for the UI was published on the host at create time (see
the create output); the human can pin a different one with
`sbx ports <sandbox-name> --publish <host-port>:${{ kit.args.port }}/tcp`.
|