codebay 0.7.0 → 0.8.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.
@@ -32,18 +32,18 @@
32
32
  "src/components/AppBar.svelte": "/_mochi/css/AppBar-3bxinv4pabv8l.css",
33
33
  "src/components/Avatar.svelte": "/_mochi/css/Avatar-3pi30db6ozvf0.css",
34
34
  "src/components/Button.svelte": "/_mochi/css/Button-2vtbrzb5kgu25.css",
35
- "src/components/ThemePicker.svelte": "/_mochi/css/ThemePicker-1ir8jyocm8mvi.css",
36
35
  "src/components/CoinButton.svelte": "/_mochi/css/CoinButton-1rej3sh3tkjud.css",
37
- "src/components/FolderBrowser.svelte": "/_mochi/css/FolderBrowser-1x428zb3f2kti.css",
36
+ "src/components/ThemePicker.svelte": "/_mochi/css/ThemePicker-1ir8jyocm8mvi.css",
38
37
  "src/components/InstanceCard.svelte": "/_mochi/css/InstanceCard-3vnltxnbxkvay.css",
39
- "src/components/TopBar.svelte": "/_mochi/css/TopBar-2q87li6yt8ids.css",
38
+ "src/components/FolderBrowser.svelte": "/_mochi/css/FolderBrowser-1x428zb3f2kti.css",
40
39
  "node_modules/svelte-french-toast/dist/components/ToastWrapper.svelte": "/_mochi/css/ToastWrapper-1fbnym10rm2nv.css",
41
40
  "node_modules/svelte-french-toast/dist/components/ToastMessage.svelte": "/_mochi/css/ToastMessage-2jfwb2og2lfa8.css",
42
- "src/components/PortsBox.svelte": "/_mochi/css/PortsBox-1t25b5yg7jva3.css",
41
+ "src/components/TopBar.svelte": "/_mochi/css/TopBar-2q87li6yt8ids.css",
43
42
  "src/components/BranchBox.svelte": "/_mochi/css/BranchBox-1yqu1q2mgnjwz.css",
44
- "src/components/Brand.svelte": "/_mochi/css/Brand-1bh2b800g5vrn.css",
45
- "src/components/InstanceFilter.svelte": "/_mochi/css/InstanceFilter-3q6x0jt0hcgpz.css",
43
+ "src/components/PortsBox.svelte": "/_mochi/css/PortsBox-1t25b5yg7jva3.css",
46
44
  "src/components/CredMenu.svelte": "/_mochi/css/CredMenu-9hwgeptp7crb.css",
45
+ "src/components/InstanceFilter.svelte": "/_mochi/css/InstanceFilter-3q6x0jt0hcgpz.css",
46
+ "src/components/Brand.svelte": "/_mochi/css/Brand-1bh2b800g5vrn.css",
47
47
  "src/components/SettingsCog.svelte": "/_mochi/css/SettingsCog-jftyadvo64qo.css"
48
48
  },
49
49
  "clientFiles": {
@@ -70,18 +70,18 @@
70
70
  "/_mochi/css/AppBar-3bxinv4pabv8l.css": "svelte-css/AppBar-3bxinv4pabv8l.css",
71
71
  "/_mochi/css/Avatar-3pi30db6ozvf0.css": "svelte-css/Avatar-3pi30db6ozvf0.css",
72
72
  "/_mochi/css/Button-2vtbrzb5kgu25.css": "svelte-css/Button-2vtbrzb5kgu25.css",
73
- "/_mochi/css/ThemePicker-1ir8jyocm8mvi.css": "svelte-css/ThemePicker-1ir8jyocm8mvi.css",
74
73
  "/_mochi/css/CoinButton-1rej3sh3tkjud.css": "svelte-css/CoinButton-1rej3sh3tkjud.css",
75
- "/_mochi/css/FolderBrowser-1x428zb3f2kti.css": "svelte-css/FolderBrowser-1x428zb3f2kti.css",
74
+ "/_mochi/css/ThemePicker-1ir8jyocm8mvi.css": "svelte-css/ThemePicker-1ir8jyocm8mvi.css",
76
75
  "/_mochi/css/InstanceCard-3vnltxnbxkvay.css": "svelte-css/InstanceCard-3vnltxnbxkvay.css",
77
- "/_mochi/css/TopBar-2q87li6yt8ids.css": "svelte-css/TopBar-2q87li6yt8ids.css",
76
+ "/_mochi/css/FolderBrowser-1x428zb3f2kti.css": "svelte-css/FolderBrowser-1x428zb3f2kti.css",
78
77
  "/_mochi/css/ToastWrapper-1fbnym10rm2nv.css": "svelte-css/ToastWrapper-1fbnym10rm2nv.css",
79
78
  "/_mochi/css/ToastMessage-2jfwb2og2lfa8.css": "svelte-css/ToastMessage-2jfwb2og2lfa8.css",
80
- "/_mochi/css/PortsBox-1t25b5yg7jva3.css": "svelte-css/PortsBox-1t25b5yg7jva3.css",
79
+ "/_mochi/css/TopBar-2q87li6yt8ids.css": "svelte-css/TopBar-2q87li6yt8ids.css",
81
80
  "/_mochi/css/BranchBox-1yqu1q2mgnjwz.css": "svelte-css/BranchBox-1yqu1q2mgnjwz.css",
82
- "/_mochi/css/Brand-1bh2b800g5vrn.css": "svelte-css/Brand-1bh2b800g5vrn.css",
83
- "/_mochi/css/InstanceFilter-3q6x0jt0hcgpz.css": "svelte-css/InstanceFilter-3q6x0jt0hcgpz.css",
81
+ "/_mochi/css/PortsBox-1t25b5yg7jva3.css": "svelte-css/PortsBox-1t25b5yg7jva3.css",
84
82
  "/_mochi/css/CredMenu-9hwgeptp7crb.css": "svelte-css/CredMenu-9hwgeptp7crb.css",
83
+ "/_mochi/css/InstanceFilter-3q6x0jt0hcgpz.css": "svelte-css/InstanceFilter-3q6x0jt0hcgpz.css",
84
+ "/_mochi/css/Brand-1bh2b800g5vrn.css": "svelte-css/Brand-1bh2b800g5vrn.css",
85
85
  "/_mochi/css/SettingsCog-jftyadvo64qo.css": "svelte-css/SettingsCog-jftyadvo64qo.css",
86
86
  "/_mochi/import-css/index-0nwv3654.css": "import-css/index-0nwv3654.css",
87
87
  "/_mochi/import-css/index-xndx4980.css": "import-css/index-xndx4980.css",
package/README.md CHANGED
@@ -18,6 +18,8 @@ The UI opens at `http://localhost:6969`. State (SQLite DB + per-instance workspa
18
18
  - `DATA_DIR` — where state lives (default `~/.codebay`)
19
19
  - `DOCKER_HOST` — Docker socket/URL (defaults to your active Docker context)
20
20
  - `HOST` — bind address (default `127.0.0.1`). Set `0.0.0.0` to reach codebay from other machines; your instances' forwarded app ports are then published on all interfaces too. Each container's code-server port stays loopback-only regardless — it runs without a password of its own and is reached through the Basic-Auth-gated `/p/:id/` proxy instead. Existing instances need a **Restart** to pick up the new binding.
21
+ - `PUBLIC_ORIGIN` — the origin you actually load codebay from (default `http://localhost:<PORT>`). Every form POST — creating an instance, restarting one, saving settings — is checked against it, so reaching the UI at any other address (a LAN IP, a hostname, or a reverse proxy / tunnel that terminates TLS) makes those actions fail with a `403 Cross-site POST form submissions are forbidden` until you set this. Give the exact scheme + host + port your browser shows, with no trailing slash — `PUBLIC_ORIGIN=http://192.168.1.50:6969` or `PUBLIC_ORIGIN=https://codebay.example.com`. Behind a TLS-terminating proxy this is the public `https://` URL, not the loopback address the proxy forwards to.
22
+ - `TRUSTED_ORIGINS` — comma-separated extra origins accepted alongside `PUBLIC_ORIGIN`, for when the UI is legitimately reachable at more than one address (e.g. `http://localhost:6969,http://192.168.1.50:6969`)
21
23
  - `BASIC_AUTH_PASSWORD` — enables HTTP Basic Auth over the whole UI (disabled when unset); required if you bind beyond loopback with `HOST=0.0.0.0`
22
24
  - `CODEBAY_CLAUDE_CODE_TOKEN` — Claude Code token to inject into every container (e.g. from `claude setup-token`) instead of discovering the host's credentials
23
25
  - `CODEBAY_GITHUB_TOKEN` — GitHub token to inject instead of reading `gh auth token` from the host
@@ -25,6 +27,7 @@ The UI opens at `http://localhost:6969`. State (SQLite DB + per-instance workspa
25
27
 
26
28
  ## Troubleshooting
27
29
 
30
+ - `403 Cross-site POST form submissions are forbidden` when clicking anything that saves — codebay is being reached at an origin other than `http://localhost:<PORT>`. Set `PUBLIC_ORIGIN` to the URL in your browser's address bar and restart (see Configuration).
28
31
  - `warn: incorrect peer dependency "svelte@5.56.8"` on startup — harmless. It comes from `svelte-french-toast`, whose published peer range predates Svelte 5; the library works correctly on Svelte 5. Nothing to fix.
29
32
 
30
33
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebay",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Web UI for spinning up isolated devcontainer instances from any local folder, each with a browser-based VS Code (code-server) — built for parallel Claude Code workflows.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,43 @@
1
+ import { execInContainer } from '../lib/exec.server.ts';
2
+ import type { Injection } from '../lib/injections.server.ts';
3
+
4
+ /**
5
+ * The claude-code devcontainer feature installs `npm install -g @anthropic-ai/claude-code`
6
+ * (always latest) at *build* time, so a cached feature layer keeps serving whatever version
7
+ * was current when the layer was first baked. This re-checks at boot and only reinstalls when
8
+ * behind — no-op if Claude Code isn't installed (project-owned images own their tooling) or
9
+ * npm/registry is unreachable, and it echoes `updated <old> -> <new>` so `apply()` can log it.
10
+ */
11
+ export const UPDATE_SCRIPT =
12
+ 'command -v claude >/dev/null 2>&1 || exit 0; ' +
13
+ 'command -v npm >/dev/null 2>&1 || exit 0; ' +
14
+ "installed=$(claude --version 2>/dev/null | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n1); " +
15
+ 'latest=$(npm view @anthropic-ai/claude-code version 2>/dev/null); ' +
16
+ 'if [ -z "$latest" ]; then exit 0; fi; ' +
17
+ 'if [ "$installed" = "$latest" ]; then echo "current $latest"; exit 0; fi; ' +
18
+ 'npm install -g @anthropic-ai/claude-code@latest >/dev/null 2>&1 || exit 1; ' +
19
+ 'echo "updated $installed -> $latest"';
20
+
21
+ /** Best-effort, non-fatal — a failed update leaves the cached version in place. */
22
+ export const claudeCodeUpdate: Injection = {
23
+ id: 'claude-code-update',
24
+ label: 'Claude Code up to date',
25
+
26
+ async apply(target, log) {
27
+ log('Checking Claude Code is up to date…\n');
28
+ // Omitting remoteUser runs as root, which the global npm reinstall needs.
29
+ const res = await execInContainer(
30
+ { containerId: target.containerId },
31
+ { script: UPDATE_SCRIPT, capture: true }
32
+ );
33
+ if (!res.ok) {
34
+ log(`⚠ Claude Code update check failed: ${res.error} — keeping the installed version\n`);
35
+ return;
36
+ }
37
+ const status = res.stdout.split('\n').at(-1)?.trim() ?? '';
38
+ if (status.startsWith('updated ')) log(`✓ Claude Code ${status.slice('updated '.length)}\n`);
39
+ else if (status.startsWith('current '))
40
+ log(`✓ Claude Code already latest (${status.slice('current '.length)})\n`);
41
+ else log('✓ Claude Code version check done\n');
42
+ }
43
+ };
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { basicAuth } from './lib/auth.server.ts';
4
4
  import { themeHandle } from './lib/theme.server.ts';
5
5
  import { PROXY_PREFIX } from './lib/proxy.server.ts';
6
6
  import {
7
+ APP_VERSION,
7
8
  BASIC_AUTH_PASSWORD,
8
9
  HOST,
9
10
  PORT,
@@ -66,7 +67,7 @@ await Mochi.serve({
66
67
  });
67
68
 
68
69
  const url = 'http://localhost:' + PORT;
69
- console.log(`Server running at ${url} (bound to ${HOST})`);
70
+ console.log(`codebay v${APP_VERSION} — server running at ${url} (bound to ${HOST})`);
70
71
 
71
72
  if (process.env.DISABLE_OPEN_BROWSER !== '1') {
72
73
  const openCmd =
@@ -102,9 +102,11 @@ const WAIT_FOR_INJECTIONS =
102
102
  * claude scans `~/.claude/ide/*.lock` once at startup and never retries, so it must not race
103
103
  * the code-server extension host writing that lock — hold it briefly until the bridge appears.
104
104
  * Bounded so an offline/uninstalled instance (lock never written) still yields a usable terminal.
105
- * No single quotes: this is spliced into the single-quoted tmux command string below.
105
+ * No single quotes: this is spliced into the single-quoted tmux command string below. The probe
106
+ * pipes ls through grep instead of globbing — tmux runs this under the user's default shell, and
107
+ * zsh prints "no matches found" to the terminal on every unmatched-glob iteration.
106
108
  */
107
- const WAIT_FOR_IDE_BRIDGE = `i=0; until ls "$HOME/.claude/ide/"*.lock >/dev/null 2>&1 || [ "$i" -ge 30 ]; do sleep 1; i=$((i + 1)); done; `;
109
+ const WAIT_FOR_IDE_BRIDGE = `i=0; until ls "$HOME/.claude/ide/" 2>/dev/null | grep -q "\\.lock$" || [ "$i" -ge 30 ]; do sleep 1; i=$((i + 1)); done; `;
108
110
 
109
111
  /**
110
112
  * Runs under tmux so Claude survives the browser closing — code-server reaps the
@@ -7,6 +7,7 @@ import { gitIdentity } from '../container-injections/git-identity.ts';
7
7
  import { claudeCodeCredentials } from '../container-injections/claude-code-credentials.ts';
8
8
  import { claudeCodeCustom } from '../container-injections/claude-code-custom.ts';
9
9
  import { claudeCodeIdeExtension } from '../container-injections/claude-code-ide-extension.ts';
10
+ import { claudeCodeUpdate } from '../container-injections/claude-code-update.ts';
10
11
  import { claudeCodeModels } from '../container-injections/claude-code-models.ts';
11
12
  import { githubCredentials } from '../container-injections/github-credentials.ts';
12
13
  import { attentionHooks } from '../container-injections/attention-hooks.ts';
@@ -52,6 +53,8 @@ const BASE_INJECTIONS_HEAD: Injection[] = [
52
53
  ];
53
54
 
54
55
  const BASE_INJECTIONS_TAIL: Injection[] = [
56
+ // Refresh the binary to latest before the other claude-config steps run against it.
57
+ claudeCodeUpdate,
55
58
  // Self-skips unless manual override is on and LiteLLM off, so it's safe in the always-run tail.
56
59
  claudeCodeModels,
57
60
  claudeCodeIdeExtension,