chamba 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -44
- package/bin/chamba.js +212 -0
- package/dist/commands/advanced.js +278 -0
- package/dist/commands/dev.js +619 -0
- package/dist/commands/doctor.js +29 -0
- package/dist/commands/menu.js +80 -0
- package/dist/commands/onboard.js +229 -0
- package/dist/commands/settings.js +349 -0
- package/dist/lib/agent-context.js +177 -0
- package/dist/lib/browser.js +40 -0
- package/dist/lib/chamba-yaml.js +191 -0
- package/dist/lib/constants.js +135 -0
- package/dist/lib/dockerfile-builder.js +267 -0
- package/dist/lib/env.js +78 -0
- package/dist/lib/global-config.js +66 -0
- package/dist/lib/pnpm-store.js +19 -0
- package/dist/lib/ports.js +210 -0
- package/dist/lib/safe-rm.js +26 -0
- package/dist/lib/sessions.js +34 -0
- package/dist/lib/shadows.js +174 -0
- package/dist/lib/webterm.js +490 -0
- package/dist/lib/workspace-identity.js +260 -0
- package/package.json +61 -24
- package/schema/chamba.schema.json +65 -0
- package/templates/.dockerignore +3 -0
- package/templates/Dockerfile +173 -0
- package/templates/claude-statusline.sh +120 -0
- package/templates/context/baseline.md +13 -0
- package/templates/context/context-usage.md +1 -0
- package/templates/context/git-mode-local.md +1 -0
- package/templates/context/git-mode-strict.md +1 -0
- package/templates/context/git-mode-unrestricted.md +1 -0
- package/templates/context/git-unavailable.md +1 -0
- package/templates/context/shadow-paths.md +3 -0
- package/templates/context-usage.sh +249 -0
- package/templates/git-readonly-wrapper.mjs +309 -0
- package/templates/npmrc +2 -0
- package/templates/pnpm-config.yaml +9 -0
- package/templates/runtime-constants.mjs +18 -0
- package/templates/skills/chamba-statusline/SKILL.md +79 -0
- package/templates/skills/context-usage/SKILL.md +53 -0
- package/templates/skills/web-pane/SKILL.md +62 -0
- package/templates/startup-git-mode.mjs +145 -0
- package/templates/startup.mjs +333 -0
- package/templates/webpane.sh +126 -0
- package/templates/webterm/README.md +157 -0
- package/templates/webterm/artifacts.js +583 -0
- package/templates/webterm/config.js +269 -0
- package/templates/webterm/context/claude.md +14 -0
- package/templates/webterm/conversation.js +248 -0
- package/templates/webterm/package-lock.json +884 -0
- package/templates/webterm/package.json +17 -0
- package/templates/webterm/pane.js +156 -0
- package/templates/webterm/proc.js +89 -0
- package/templates/webterm/public/app/alerts.js +472 -0
- package/templates/webterm/public/app/cards.js +123 -0
- package/templates/webterm/public/app/clipboard.js +229 -0
- package/templates/webterm/public/app/composer.js +226 -0
- package/templates/webterm/public/app/connection.js +342 -0
- package/templates/webterm/public/app/dictation.js +98 -0
- package/templates/webterm/public/app/dom.js +37 -0
- package/templates/webterm/public/app/drafts.js +244 -0
- package/templates/webterm/public/app/frames.js +166 -0
- package/templates/webterm/public/app/main.js +82 -0
- package/templates/webterm/public/app/new-session.js +188 -0
- package/templates/webterm/public/app/note.js +24 -0
- package/templates/webterm/public/app/pane-frame.js +166 -0
- package/templates/webterm/public/app/pane.js +353 -0
- package/templates/webterm/public/app/state.js +51 -0
- package/templates/webterm/public/app/status-strip.js +170 -0
- package/templates/webterm/public/app/tabs.js +475 -0
- package/templates/webterm/public/app/terminal.js +102 -0
- package/templates/webterm/public/app/theme.js +46 -0
- package/templates/webterm/public/favicon.svg +21 -0
- package/templates/webterm/public/index.html +105 -0
- package/templates/webterm/public/styles.css +1193 -0
- package/templates/webterm/server.js +1142 -0
- package/templates/webterm/sessions.js +515 -0
- package/templates/webterm/snapshot.js +135 -0
- package/templates/webterm.sh +167 -0
- package/dist/cli.js +0 -1691
- package/dist/server.js +0 -1919
- package/inject/annotate.js +0 -18
- package/skill/README.md +0 -12
- package/skill/SKILL.md +0 -93
- package/web/assets/highlighted-body-OFNGDK62-Bn4Eu7CG.js +0 -1
- package/web/assets/index-B9DI4F1Z.js +0 -202
- package/web/assets/index-DK_n6CTo.css +0 -2
- package/web/assets/mermaid-GHXKKRXX-CEMduc-U.js +0 -1
- package/web/index.html +0 -28
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# webterm - run an AI agent in the browser (the web agent interface) from inside this container.
|
|
3
|
+
# Baked into the image at /usr/local/share/chamba/webterm.sh, on PATH as `webterm`.
|
|
4
|
+
# The agent is always named explicitly: webterm claude | webterm opencode | webterm codex.
|
|
5
|
+
# The server runs all of them, one per session; the agent named here is the one new sessions start with.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
CONTAINER_PORT=3899
|
|
9
|
+
AGENTS=(claude opencode codex)
|
|
10
|
+
# The state file sits in a directory the host bind-mounts, not in /tmp: the agent named here is the one the
|
|
11
|
+
# host starts the interface with at the next container start, so the choice has to outlive this container.
|
|
12
|
+
STATE_FILE=/home/devuser/.chamba-webterm/agent
|
|
13
|
+
KEY_FILE=/tmp/webterm.key
|
|
14
|
+
SERVER_LOG=/tmp/webterm.log
|
|
15
|
+
|
|
16
|
+
# CHAMBA_WEB_URL is injected by the host at container create whenever a host port could be published for the
|
|
17
|
+
# interface, so its absence means this container has no web address to serve on.
|
|
18
|
+
if [ -z "${CHAMBA_WEB_URL:-}" ]; then
|
|
19
|
+
echo "No host port is published for the web interface in this container."
|
|
20
|
+
echo "Chamba says why when it opens the session; widen the range under Settings > Ports, then reopen it."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Is something already serving on the fixed container port? The probe runs in a subshell, so its file
|
|
25
|
+
# descriptor closes with it.
|
|
26
|
+
server_live() {
|
|
27
|
+
(exec 3<>"/dev/tcp/127.0.0.1/${CONTAINER_PORT}") 2>/dev/null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# The URL to open, key and all. The server mints a new key every time it starts and publishes it to the key
|
|
31
|
+
# file the moment it binds the port, so a live port means the file next to it is the current key. Only ever
|
|
32
|
+
# called with a server up: a key left behind by a dead server would make a URL that is refused.
|
|
33
|
+
web_url() {
|
|
34
|
+
if [ -r "$KEY_FILE" ]; then
|
|
35
|
+
echo "${CHAMBA_WEB_URL}/?k=$(head -n 1 "$KEY_FILE")"
|
|
36
|
+
else
|
|
37
|
+
echo "${CHAMBA_WEB_URL}"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
usage() {
|
|
42
|
+
local list="${AGENTS[*]}" # Space separated; render it as "a | b | c".
|
|
43
|
+
echo "Usage: webterm <agent> - run an agent in the web interface"
|
|
44
|
+
echo ""
|
|
45
|
+
echo "Agents: ${list// / | }"
|
|
46
|
+
echo ""
|
|
47
|
+
echo "Example: webterm ${AGENTS[0]}"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "The interface runs all of them, one per session - the agent named here is the one"
|
|
50
|
+
echo "its \"+ New session\" starts, and the browser can pick another per session."
|
|
51
|
+
echo ""
|
|
52
|
+
# With a server up this is the URL to open. With none there is no URL yet - the key comes with the
|
|
53
|
+
# server - so say where it appears rather than handing out a link that would be refused.
|
|
54
|
+
if server_live; then
|
|
55
|
+
echo "The interface is at $(web_url)"
|
|
56
|
+
else
|
|
57
|
+
echo "The interface is then at ${CHAMBA_WEB_URL}, plus the key the server prints when it starts."
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Bare `webterm` explains how to invoke instead of guessing an agent. The usage text carries the
|
|
62
|
+
# URL too, so it doubles as "what is my web address" for an interface that is already running.
|
|
63
|
+
if [ $# -eq 0 ]; then
|
|
64
|
+
usage
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
AGENT="$1"
|
|
69
|
+
AGENT_KNOWN=""
|
|
70
|
+
for known in "${AGENTS[@]}"; do
|
|
71
|
+
if [ "$AGENT" = "$known" ]; then
|
|
72
|
+
AGENT_KNOWN=1
|
|
73
|
+
break
|
|
74
|
+
fi
|
|
75
|
+
done
|
|
76
|
+
if [ -z "$AGENT_KNOWN" ]; then
|
|
77
|
+
echo "Unknown agent: ${AGENT}"
|
|
78
|
+
echo ""
|
|
79
|
+
usage
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Idempotent: the host launches this on every container start, and the user may run it by hand.
|
|
84
|
+
# If something already listens on the fixed container port, there is nothing to start - the server runs every
|
|
85
|
+
# agent, one per session. Naming another agent moves what "+ New session" starts, over the same key-gated
|
|
86
|
+
# route the browser uses; live sessions keep the agent they were started with, because the agent is the
|
|
87
|
+
# process. A bound port only proves something is listening, so the current default comes from the state file.
|
|
88
|
+
if server_live; then
|
|
89
|
+
RUNNING_AGENT=""
|
|
90
|
+
if [ -r "$STATE_FILE" ]; then
|
|
91
|
+
RUNNING_AGENT="$(head -n 1 "$STATE_FILE" 2>/dev/null || true)"
|
|
92
|
+
fi
|
|
93
|
+
if [ -n "$RUNNING_AGENT" ] && [ "$RUNNING_AGENT" != "$AGENT" ]; then
|
|
94
|
+
if [ -r "$KEY_FILE" ] && curl -fsS -X POST -H "Content-Type: application/json" \
|
|
95
|
+
-d "{\"agent\":\"${AGENT}\"}" \
|
|
96
|
+
"http://127.0.0.1:${CONTAINER_PORT}/agent?k=$(head -n 1 "$KEY_FILE")" >/dev/null 2>&1; then
|
|
97
|
+
echo "webterm is already running: $(web_url)"
|
|
98
|
+
echo ""
|
|
99
|
+
echo "New sessions there now start ${AGENT}. Sessions already open keep running ${RUNNING_AGENT}."
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
echo "webterm is already running, starting new sessions with ${RUNNING_AGENT}: $(web_url)"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "Could not switch it to ${AGENT} - see ${SERVER_LOG}. Pick the agent per session in the browser instead."
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
echo "webterm is already running${RUNNING_AGENT:+ (new sessions start ${RUNNING_AGENT})}: $(web_url)"
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
export WEBTERM_PORT="${CONTAINER_PORT}"
|
|
112
|
+
export WEBTERM_AGENT="$AGENT"
|
|
113
|
+
export WEBTERM_STATE_FILE="$STATE_FILE"
|
|
114
|
+
# Where the server publishes the key it mints for this run; every URL printed above is read back from it.
|
|
115
|
+
export WEBTERM_KEY_FILE="$KEY_FILE"
|
|
116
|
+
# Guarded like the sibling baked scripts: a detached `docker exec` need not carry HOME, and under
|
|
117
|
+
# `set -u` a bare $HOME would abort the launcher before the server ever starts.
|
|
118
|
+
export WEBTERM_RESUME_MARKER="${HOME:-/home/devuser}/.chamba-resume-pending"
|
|
119
|
+
# The workspace name (host-injected as CHAMBA_WORKSPACE) labels the session bar and the browser tab.
|
|
120
|
+
export WEBTERM_WORKSPACE="${CHAMBA_WORKSPACE:-}"
|
|
121
|
+
# Where new sessions start. The host passes it (the directory `npx chamba` ran in), so a browser session
|
|
122
|
+
# opens where a terminal session would; a hand-run `webterm <agent>` falls back to this shell's own
|
|
123
|
+
# directory, which is the same promise. The server refuses anything outside the workspace.
|
|
124
|
+
export WEBTERM_CWD="${WEBTERM_CWD:-$PWD}"
|
|
125
|
+
|
|
126
|
+
# Detached launches (docker exec -d) have no terminal; keep this script's own output reachable too.
|
|
127
|
+
if [ ! -t 1 ]; then
|
|
128
|
+
exec >>"$SERVER_LOG" 2>&1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Whatever key is on disk right now is a dead server's: the port was free a moment ago. Remember it rather
|
|
132
|
+
# than deleting it, and wait below for the file to hold something else. Deleting would be the obvious move
|
|
133
|
+
# and is the wrong one - two launchers can both find the port free, and the one that loses the bind would
|
|
134
|
+
# delete the key the winner just published, leaving a live interface nobody can read the key of.
|
|
135
|
+
PREV_KEY=""
|
|
136
|
+
if [ -r "$KEY_FILE" ]; then
|
|
137
|
+
PREV_KEY="$(head -n 1 "$KEY_FILE")"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Start the server detached: the interface is a background service, not a foreground command, so a
|
|
141
|
+
# hand-run returns the prompt and the interface survives the shell that started it - the same
|
|
142
|
+
# behavior the host-started launch has. setsid puts it in its own session, so closing this terminal
|
|
143
|
+
# (or Ctrl-C) does not take it down. Its output appends to the log for debugging.
|
|
144
|
+
setsid node /usr/local/share/chamba/webterm/server.js >>"$SERVER_LOG" 2>&1 &
|
|
145
|
+
|
|
146
|
+
# Wait for the port to bind before claiming success, so a server that fails to start says so (with
|
|
147
|
+
# the log to look at) instead of printing a URL that will not answer.
|
|
148
|
+
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
149
|
+
if server_live; then
|
|
150
|
+
# The key is written the moment the port is bound, so it lands a hair after the probe can see the
|
|
151
|
+
# port. Give it that hair rather than printing a URL with the old key on it. Waiting for the value
|
|
152
|
+
# to change (not just for the file to exist) is also what makes losing a launcher race harmless:
|
|
153
|
+
# the winner's key is what ends up in the file, and printing that is right.
|
|
154
|
+
for _ in 1 2 3 4 5; do
|
|
155
|
+
if [ -r "$KEY_FILE" ] && [ "$(head -n 1 "$KEY_FILE")" != "$PREV_KEY" ]; then
|
|
156
|
+
break
|
|
157
|
+
fi
|
|
158
|
+
sleep 0.1
|
|
159
|
+
done
|
|
160
|
+
echo "Web agent interface: $(web_url) (new sessions start ${AGENT})"
|
|
161
|
+
exit 0
|
|
162
|
+
fi
|
|
163
|
+
sleep 0.3
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
echo "webterm did not start within 3s - see ${SERVER_LOG}"
|
|
167
|
+
exit 1
|