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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -44
  3. package/bin/chamba.js +212 -0
  4. package/dist/commands/advanced.js +278 -0
  5. package/dist/commands/dev.js +619 -0
  6. package/dist/commands/doctor.js +29 -0
  7. package/dist/commands/menu.js +80 -0
  8. package/dist/commands/onboard.js +229 -0
  9. package/dist/commands/settings.js +349 -0
  10. package/dist/lib/agent-context.js +177 -0
  11. package/dist/lib/browser.js +40 -0
  12. package/dist/lib/chamba-yaml.js +191 -0
  13. package/dist/lib/constants.js +135 -0
  14. package/dist/lib/dockerfile-builder.js +267 -0
  15. package/dist/lib/env.js +78 -0
  16. package/dist/lib/global-config.js +66 -0
  17. package/dist/lib/pnpm-store.js +19 -0
  18. package/dist/lib/ports.js +210 -0
  19. package/dist/lib/safe-rm.js +26 -0
  20. package/dist/lib/sessions.js +34 -0
  21. package/dist/lib/shadows.js +174 -0
  22. package/dist/lib/webterm.js +490 -0
  23. package/dist/lib/workspace-identity.js +260 -0
  24. package/package.json +61 -24
  25. package/schema/chamba.schema.json +65 -0
  26. package/templates/.dockerignore +3 -0
  27. package/templates/Dockerfile +173 -0
  28. package/templates/claude-statusline.sh +120 -0
  29. package/templates/context/baseline.md +13 -0
  30. package/templates/context/context-usage.md +1 -0
  31. package/templates/context/git-mode-local.md +1 -0
  32. package/templates/context/git-mode-strict.md +1 -0
  33. package/templates/context/git-mode-unrestricted.md +1 -0
  34. package/templates/context/git-unavailable.md +1 -0
  35. package/templates/context/shadow-paths.md +3 -0
  36. package/templates/context-usage.sh +249 -0
  37. package/templates/git-readonly-wrapper.mjs +309 -0
  38. package/templates/npmrc +2 -0
  39. package/templates/pnpm-config.yaml +9 -0
  40. package/templates/runtime-constants.mjs +18 -0
  41. package/templates/skills/chamba-statusline/SKILL.md +79 -0
  42. package/templates/skills/context-usage/SKILL.md +53 -0
  43. package/templates/skills/web-pane/SKILL.md +62 -0
  44. package/templates/startup-git-mode.mjs +145 -0
  45. package/templates/startup.mjs +333 -0
  46. package/templates/webpane.sh +126 -0
  47. package/templates/webterm/README.md +157 -0
  48. package/templates/webterm/artifacts.js +583 -0
  49. package/templates/webterm/config.js +269 -0
  50. package/templates/webterm/context/claude.md +14 -0
  51. package/templates/webterm/conversation.js +248 -0
  52. package/templates/webterm/package-lock.json +884 -0
  53. package/templates/webterm/package.json +17 -0
  54. package/templates/webterm/pane.js +156 -0
  55. package/templates/webterm/proc.js +89 -0
  56. package/templates/webterm/public/app/alerts.js +472 -0
  57. package/templates/webterm/public/app/cards.js +123 -0
  58. package/templates/webterm/public/app/clipboard.js +229 -0
  59. package/templates/webterm/public/app/composer.js +226 -0
  60. package/templates/webterm/public/app/connection.js +342 -0
  61. package/templates/webterm/public/app/dictation.js +98 -0
  62. package/templates/webterm/public/app/dom.js +37 -0
  63. package/templates/webterm/public/app/drafts.js +244 -0
  64. package/templates/webterm/public/app/frames.js +166 -0
  65. package/templates/webterm/public/app/main.js +82 -0
  66. package/templates/webterm/public/app/new-session.js +188 -0
  67. package/templates/webterm/public/app/note.js +24 -0
  68. package/templates/webterm/public/app/pane-frame.js +166 -0
  69. package/templates/webterm/public/app/pane.js +353 -0
  70. package/templates/webterm/public/app/state.js +51 -0
  71. package/templates/webterm/public/app/status-strip.js +170 -0
  72. package/templates/webterm/public/app/tabs.js +475 -0
  73. package/templates/webterm/public/app/terminal.js +102 -0
  74. package/templates/webterm/public/app/theme.js +46 -0
  75. package/templates/webterm/public/favicon.svg +21 -0
  76. package/templates/webterm/public/index.html +105 -0
  77. package/templates/webterm/public/styles.css +1193 -0
  78. package/templates/webterm/server.js +1142 -0
  79. package/templates/webterm/sessions.js +515 -0
  80. package/templates/webterm/snapshot.js +135 -0
  81. package/templates/webterm.sh +167 -0
  82. package/dist/cli.js +0 -1691
  83. package/dist/server.js +0 -1919
  84. package/inject/annotate.js +0 -18
  85. package/skill/README.md +0 -12
  86. package/skill/SKILL.md +0 -93
  87. package/web/assets/highlighted-body-OFNGDK62-Bn4Eu7CG.js +0 -1
  88. package/web/assets/index-B9DI4F1Z.js +0 -202
  89. package/web/assets/index-DK_n6CTo.css +0 -2
  90. package/web/assets/mermaid-GHXKKRXX-CEMduc-U.js +0 -1
  91. 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