@kortix/sandbox 0.4.1 → 0.4.2

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.
@@ -14,130 +14,117 @@ if [ -f "$MARKER" ]; then
14
14
  exit 0
15
15
  fi
16
16
 
17
- echo "[heyagi] Applying desktop customization..."
18
- mkdir -p "$CONFIG_DIR/.config/autostart"
19
- mkdir -p "$CONFIG_DIR/.local/share/konsole"
17
+ echo "[heyagi] Applying XFCE desktop customization (Alpine)..."
20
18
 
21
19
  # ── Symlink presentations into Desktop for easy access ─────────────────────
22
20
  mkdir -p "$CONFIG_DIR/presentations"
23
- # Create a Desktop directory for KDE and symlink presentations into it
24
21
  mkdir -p "$CONFIG_DIR/Desktop"
25
22
  ln -sfn "$CONFIG_DIR/presentations" "$CONFIG_DIR/Desktop/presentations"
26
23
 
27
- # ── KDE Global: Breeze Dark ────────────────────────────────────────────────
28
- cat > "$CONFIG_DIR/.config/kdeglobals" << 'EOF'
29
- [General]
30
- ColorScheme=BreezeDark
31
- Name=Breeze Dark
32
- widgetStyle=Breeze
33
-
34
- [Icons]
35
- Theme=breeze-dark
36
-
37
- [KDE]
38
- LookAndFeelPackage=org.kde.breezedark.desktop
39
- widgetStyle=breeze
24
+ # ── XFCE: Wallpaper ───────────────────────────────────────────────────────
25
+ mkdir -p "$CONFIG_DIR/.config/xfce4/xfconf/xfce-perchannel-xml"
26
+ cat > "$CONFIG_DIR/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml" << 'EOF'
27
+ <?xml version="1.0" encoding="UTF-8"?>
28
+ <channel name="xfce4-desktop" version="1.0">
29
+ <property name="backdrop" type="empty">
30
+ <property name="screen0" type="empty">
31
+ <property name="monitorVNC-0" type="empty">
32
+ <property name="workspace0" type="empty">
33
+ <property name="last-image" type="string" value="/usr/share/wallpapers/heyagi/wallpaper.png"/>
34
+ <property name="image-style" type="int" value="5"/>
35
+ <property name="color-style" type="int" value="0"/>
36
+ <property name="rgba1" type="array">
37
+ <value type="double" value="0.10196"/>
38
+ <value type="double" value="0.10588"/>
39
+ <value type="double" value="0.14902"/>
40
+ <value type="double" value="1.0"/>
41
+ </property>
42
+ </property>
43
+ </property>
44
+ <property name="monitorscreen" type="empty">
45
+ <property name="workspace0" type="empty">
46
+ <property name="last-image" type="string" value="/usr/share/wallpapers/heyagi/wallpaper.png"/>
47
+ <property name="image-style" type="int" value="5"/>
48
+ <property name="color-style" type="int" value="0"/>
49
+ <property name="rgba1" type="array">
50
+ <value type="double" value="0.10196"/>
51
+ <value type="double" value="0.10588"/>
52
+ <value type="double" value="0.14902"/>
53
+ <value type="double" value="1.0"/>
54
+ </property>
55
+ </property>
56
+ </property>
57
+ </property>
58
+ </property>
59
+ </channel>
40
60
  EOF
41
61
 
42
- # ── KWin ────────────────────────────────────────────────────────────────────
43
- cat > "$CONFIG_DIR/.config/kwinrc" << 'EOF'
44
- [org.kde.kdecoration2]
45
- theme=Breeze
46
- library=org.kde.breeze
47
-
48
- [Windows]
49
- Placement=Centered
50
-
51
- [Desktops]
52
- Number=1
53
- Rows=1
62
+ # ── XFCE: Dark theme (adw-gtk3-dark — Alpine's equivalent of Adwaita-dark)
63
+ cat > "$CONFIG_DIR/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml" << 'EOF'
64
+ <?xml version="1.0" encoding="UTF-8"?>
65
+ <channel name="xsettings" version="1.0">
66
+ <property name="Net" type="empty">
67
+ <property name="ThemeName" type="string" value="adw-gtk3-dark"/>
68
+ <property name="IconThemeName" type="string" value="Adwaita"/>
69
+ <property name="CursorThemeName" type="string" value="Breeze_Light"/>
70
+ </property>
71
+ <property name="Gtk" type="empty">
72
+ <property name="FontName" type="string" value="Sans 10"/>
73
+ <property name="CursorThemeSize" type="int" value="24"/>
74
+ </property>
75
+ </channel>
54
76
  EOF
55
77
 
56
- # ── Plasma theme ────────────────────────────────────────────────────────────
57
- cat > "$CONFIG_DIR/.config/plasmarc" << 'EOF'
58
- [Theme]
59
- name=breeze-dark
78
+ # ── XFCE: Window Manager dark theme (Daloa — only xfwm4 theme on Alpine)
79
+ cat > "$CONFIG_DIR/.config/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml" << 'EOF'
80
+ <?xml version="1.0" encoding="UTF-8"?>
81
+ <channel name="xfwm4" version="1.0">
82
+ <property name="general" type="empty">
83
+ <property name="theme" type="string" value="Daloa"/>
84
+ <property name="title_font" type="string" value="Sans Bold 9"/>
85
+ <property name="placement_ratio" type="int" value="50"/>
86
+ <property name="cycle_tabwin_mode" type="int" value="0"/>
87
+ </property>
88
+ </channel>
60
89
  EOF
61
90
 
62
- # ── Konsole dark profile ───────────────────────────────────────────────────
63
- cat > "$CONFIG_DIR/.local/share/konsole/HeyAGI.profile" << 'EOF'
64
- [Appearance]
65
- ColorScheme=Breeze
66
- Font=Monospace,11,-1,5,50,0,0,0,0,0
67
-
68
- [General]
69
- Name=HeyAGI
70
- Parent=FALLBACK/
71
-
72
- [Scrolling]
73
- HistoryMode=2
91
+ # ── XFCE: Terminal dark profile ──────────────────────────────────────────
92
+ mkdir -p "$CONFIG_DIR/.config/xfce4/terminal"
93
+ cat > "$CONFIG_DIR/.config/xfce4/terminal/terminalrc" << 'EOF'
94
+ [Configuration]
95
+ BackgroundMode=TERMINAL_BACKGROUND_TRANSPARENT
96
+ BackgroundDarkness=0.90
97
+ ColorForeground=#c0caf5
98
+ ColorBackground=#1a1b26
99
+ ColorCursor=#c0caf5
100
+ ColorPalette=#15161e;#f7768e;#9ece6a;#e0af68;#7aa2f7;#bb9af7;#7dcfff;#a9b1d6;#414868;#f7768e;#9ece6a;#e0af68;#7aa2f7;#bb9af7;#7dcfff;#c0caf5
101
+ FontName=Monospace 11
102
+ MiscAlwaysShowTabs=FALSE
103
+ MiscBordersDefault=TRUE
104
+ MiscShowUnsafePasteDialog=FALSE
105
+ ScrollingUnlimited=TRUE
74
106
  EOF
75
107
 
76
- cat > "$CONFIG_DIR/.config/konsolerc" << 'EOF'
77
- [Desktop Entry]
78
- DefaultProfile=HeyAGI.profile
79
-
80
- [MainWindow]
81
- MenuBar=Disabled
82
- ToolBarsMovable=Disabled
108
+ # ── GTK dark theme (fallback for GTK2 apps) ───────────────────────────────
109
+ cat > "$CONFIG_DIR/.gtkrc-2.0" << 'EOF'
110
+ gtk-theme-name="adw-gtk3-dark"
111
+ gtk-icon-theme-name="Adwaita"
112
+ gtk-font-name="Sans 10"
113
+ gtk-cursor-theme-name="Breeze_Light"
83
114
  EOF
84
115
 
85
- # ── Autostart: apply wallpaper + launcher icon after KDE session loads ──────
86
- cat > "$CONFIG_DIR/.config/autostart/heyagi-desktop.desktop" << 'EOF'
87
- [Desktop Entry]
88
- Type=Application
89
- Name=HeyAGI Desktop Setup
90
- Exec=/usr/share/wallpapers/heyagi/apply-desktop.sh
91
- X-KDE-autostart-phase=2
116
+ mkdir -p "$CONFIG_DIR/.config/gtk-3.0"
117
+ cat > "$CONFIG_DIR/.config/gtk-3.0/settings.ini" << 'EOF'
118
+ [Settings]
119
+ gtk-theme-name=adw-gtk3-dark
120
+ gtk-icon-theme-name=Adwaita
121
+ gtk-font-name=Sans 10
122
+ gtk-application-prefer-dark-theme=true
123
+ gtk-cursor-theme-name=Breeze_Light
92
124
  EOF
93
125
 
94
- cat > /usr/share/wallpapers/heyagi/apply-desktop.sh << 'SCRIPT'
95
- #!/bin/bash
96
- sleep 5
97
-
98
- plasma-apply-wallpaperimage /usr/share/wallpapers/heyagi/wallpaper.png
99
-
100
- PLASMA_RC="$HOME/.config/plasma-org.kde.plasma.desktop-appletsrc"
101
- ICON_PATH="/usr/share/icons/heyagi/kortix-symbol-white.svg"
102
-
103
- if [ -f "$PLASMA_RC" ]; then
104
- # Find the kickoff applet's [Configuration][General] section and inject icon
105
- # Get the containment/applet IDs for kickoff
106
- KICKOFF_SECTION=$(grep -B3 "plugin=org.kde.plasma.kickoff" "$PLASMA_RC" | grep "^\[Containments\]" | tail -1)
107
-
108
- if [ -n "$KICKOFF_SECTION" ]; then
109
- # Build the [Configuration][General] section name
110
- GENERAL_SECTION="${KICKOFF_SECTION%]}][Configuration][General]"
111
-
112
- if grep -q "$(echo "$GENERAL_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')" "$PLASMA_RC"; then
113
- # Section exists -- replace or add icon line
114
- ESCAPED=$(echo "$GENERAL_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')
115
- if grep -A10 "$ESCAPED" "$PLASMA_RC" | grep -q "^icon="; then
116
- sed -i "/$ESCAPED/,/^\[/{s|^icon=.*|icon=$ICON_PATH|}" "$PLASMA_RC"
117
- else
118
- sed -i "/$ESCAPED/a icon=$ICON_PATH" "$PLASMA_RC"
119
- fi
120
- else
121
- # Section doesn't exist -- create it
122
- CONF_SECTION="${KICKOFF_SECTION%]}][Configuration]"
123
- ESCAPED_CONF=$(echo "$CONF_SECTION" | sed 's/\[/\\[/g; s/\]/\\]/g')
124
- sed -i "/$ESCAPED_CONF/,/^\[/{/^\[.*\]/!b;i\\${GENERAL_SECTION}\nicon=$ICON_PATH
125
- }" "$PLASMA_RC"
126
- fi
127
- fi
128
-
129
- # Restart plasmashell to pick up icon change
130
- kquitapp5 plasmashell 2>/dev/null
131
- sleep 2
132
- kstart5 plasmashell 2>/dev/null &
133
- fi
134
- SCRIPT
135
- chmod +x /usr/share/wallpapers/heyagi/apply-desktop.sh
136
-
137
126
  # ── Fix ownership ──────────────────────────────────────────────────────────
138
- # Give abc full ownership of everything under /workspace so opencode and its
139
- # agents can freely create directories and files (presentations, output, etc.)
140
127
  chown -R abc:abc "$CONFIG_DIR" 2>/dev/null
141
128
 
142
129
  touch "$MARKER"
143
- echo "[heyagi] Customization complete."
130
+ echo "[heyagi] XFCE customization complete."
@@ -12,11 +12,11 @@ if [ "$ENV_MODE" = "cloud" ] || [ "$ENV_MODE" = "production" ]; then
12
12
  echo "[Kortix] Services will still start; set KORTIX_API_URL later to enable model routing"
13
13
  else
14
14
  # Write env overrides that s6 services will inherit via with-contenv
15
- printf '%s' "${KORTIX_API_URL}/tavily" > /var/run/s6/container_environment/TAVILY_API_URL
16
- printf '%s' "${KORTIX_API_URL}/serper" > /var/run/s6/container_environment/SERPER_API_URL
17
- printf '%s' "${KORTIX_API_URL}/firecrawl" > /var/run/s6/container_environment/FIRECRAWL_API_URL
18
- printf '%s' "${KORTIX_API_URL}/replicate" > /var/run/s6/container_environment/REPLICATE_API_URL
19
- printf '%s' "${KORTIX_API_URL}/context7" > /var/run/s6/container_environment/CONTEXT7_API_URL
15
+ printf '%s' "${KORTIX_API_URL}/tavily" > /run/s6/container_environment/TAVILY_API_URL
16
+ printf '%s' "${KORTIX_API_URL}/serper" > /run/s6/container_environment/SERPER_API_URL
17
+ printf '%s' "${KORTIX_API_URL}/firecrawl" > /run/s6/container_environment/FIRECRAWL_API_URL
18
+ printf '%s' "${KORTIX_API_URL}/replicate" > /run/s6/container_environment/REPLICATE_API_URL
19
+ printf '%s' "${KORTIX_API_URL}/context7" > /run/s6/container_environment/CONTEXT7_API_URL
20
20
 
21
21
  echo "[Kortix] SDK URLs routed through ${KORTIX_API_URL}"
22
22
  fi
@@ -18,8 +18,18 @@ await secretStore.loadIntoProcessEnv()
18
18
  app.use('*', logger())
19
19
  app.use('*', cors())
20
20
 
21
- // Health check
22
- app.get('/kortix/health', (c) => c.json({ status: 'ok' }))
21
+ // Health check — includes current sandbox version
22
+ app.get('/kortix/health', async (c) => {
23
+ let version = '0.0.0'
24
+ try {
25
+ const file = Bun.file('/opt/kortix/.version')
26
+ if (await file.exists()) {
27
+ const data = await file.json()
28
+ version = data.version || '0.0.0'
29
+ }
30
+ } catch {}
31
+ return c.json({ status: 'ok', version })
32
+ })
23
33
 
24
34
  // Update check — /kortix/update and /kortix/update/status
25
35
  app.route('/kortix/update', updateRouter)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/sandbox",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Kortix sandbox runtime — kortix-master, opencode config/agents/skills, and dependencies",
5
5
  "private": false,
6
6
  "scripts": {
@@ -1,9 +1,16 @@
1
1
  const fs = require('fs');
2
+ const { execSync } = require('child_process');
3
+
4
+ // Auto-detect npm global modules directory
5
+ const npmGlobalRoot = execSync('npm root -g').toString().trim();
6
+ console.log('npm global root:', npmGlobalRoot);
7
+
8
+ const agentBrowserDir = npmGlobalRoot + '/agent-browser/dist';
2
9
 
3
10
  // ── Patch 1: handleLaunch env var fallbacks ─────────────────────────────────
4
11
  // The Rust CLI sends a launch command to the Node.js daemon but doesn't
5
12
  // include executablePath/args/profile in the JSON. We inject env var fallbacks.
6
- const actionsFile = '/usr/lib/node_modules/agent-browser/dist/actions.js';
13
+ const actionsFile = agentBrowserDir + '/actions.js';
7
14
  let actions = fs.readFileSync(actionsFile, 'utf8');
8
15
 
9
16
  const oldLaunch = [
@@ -44,7 +51,7 @@ console.log('PATCH 2 SKIPPED - upstream already allows localhost origins');
44
51
  // When AGENT_BROWSER_STREAM_PORT is set globally, ALL sessions try to bind
45
52
  // that port. Named sessions crash with EADDRINUSE. Fix: only the default
46
53
  // session uses the env var port; named sessions use the hash-based port.
47
- const daemonFile = '/usr/lib/node_modules/agent-browser/dist/daemon.js';
54
+ const daemonFile = agentBrowserDir + '/daemon.js';
48
55
  let daemon = fs.readFileSync(daemonFile, 'utf8');
49
56
 
50
57
  const oldStreamPort = `const streamPort = options?.streamPort ??
@@ -85,7 +92,7 @@ console.log('PATCH 4 OK - auto-launch profile only for default session');
85
92
  // When using launchPersistentContext (profile mode), this.browser is null.
86
93
  // newTab() checks `!this.browser` and throws "Browser not launched".
87
94
  // Fix: also check this.contexts.length > 0 as an alternative.
88
- const browserFile = '/usr/lib/node_modules/agent-browser/dist/browser.js';
95
+ const browserFile = agentBrowserDir + '/browser.js';
89
96
  let browser = fs.readFileSync(browserFile, 'utf8');
90
97
 
91
98
  const oldNewTabCheck = "if (!this.browser || this.contexts.length === 0) {\n throw new Error('Browser not launched');\n }\n // Invalidate CDP session since we're switching to a new page";
@@ -1,37 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # KORTIX Presentation Viewer — s6 service
3
- #
4
- # Watches for presentations in /workspace/presentations/ and serves
5
- # the most recently updated one on port 3210.
6
- # If no presentation exists yet, waits until one appears.
7
-
8
- PRES_ROOT="/workspace/presentations"
9
- VIEWER="/opt/opencode/skills/KORTIX-presentation-viewer/serve.ts"
10
- PORT=3210
11
-
12
- wait_for_presentation() {
13
- while true; do
14
- if [ -d "$PRES_ROOT" ]; then
15
- LATEST=$(find "$PRES_ROOT" -maxdepth 2 -name "metadata.json" -newer /tmp/.pv-last-check 2>/dev/null | head -1)
16
- if [ -z "$LATEST" ]; then
17
- LATEST=$(find "$PRES_ROOT" -maxdepth 2 -name "metadata.json" 2>/dev/null | head -1)
18
- fi
19
- if [ -n "$LATEST" ]; then
20
- dirname "$LATEST"
21
- return 0
22
- fi
23
- fi
24
- sleep 3
25
- done
26
- }
27
-
28
- touch /tmp/.pv-last-check
29
-
30
- echo "[KORTIX-pv] Waiting for a presentation in $PRES_ROOT ..."
31
- PRES_DIR=$(wait_for_presentation)
32
- echo "[KORTIX-pv] Serving: $PRES_DIR on port $PORT"
33
-
34
- touch /tmp/.pv-last-check
35
-
36
- exec s6-setuidgid abc \
37
- /usr/local/bin/bun run "$VIEWER" "$PRES_DIR"
@@ -1,48 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # Agent Browser Viewer — s6 service
3
- #
4
- # Serves the browser viewer UI on port 9224.
5
- # - GET / → viewer HTML
6
- # - GET /sessions → JSON list of active sessions + stream ports
7
-
8
- echo "[agent-browser-viewer] Starting viewer on port 9224"
9
-
10
- exec s6-setuidgid abc \
11
- node -e '
12
- const http = require("http");
13
- const fs = require("fs");
14
- const path = require("path");
15
-
16
- const html = fs.readFileSync("/opt/agent-browser-viewer/index.html");
17
- const socketDir = process.env.AGENT_BROWSER_SOCKET_DIR || "/workspace/.agent-browser";
18
-
19
- function getSessions() {
20
- try {
21
- const files = fs.readdirSync(socketDir);
22
- const sessions = [];
23
- for (const f of files) {
24
- if (f.endsWith(".stream")) {
25
- const name = f.replace(".stream", "");
26
- const port = parseInt(fs.readFileSync(path.join(socketDir, f), "utf8").trim(), 10);
27
- const hasSock = files.includes(name + ".sock");
28
- if (port > 0 && hasSock) {
29
- sessions.push({ name, port });
30
- }
31
- }
32
- }
33
- return sessions;
34
- } catch(e) { return []; }
35
- }
36
-
37
- http.createServer((req, res) => {
38
- if (req.url === "/sessions") {
39
- res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-cache" });
40
- res.end(JSON.stringify(getSessions()));
41
- } else {
42
- res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-cache" });
43
- res.end(html);
44
- }
45
- }).listen(9224, "0.0.0.0", () => {
46
- console.log("[agent-browser-viewer] Ready at http://0.0.0.0:9224");
47
- });
48
- '
@@ -1,16 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # Kortix Master — s6 service
3
- #
4
- # Reverse proxy that sits in front of OpenCode on port 8000.
5
- # All external traffic enters through Kortix Master, which proxies
6
- # to the OpenCode API server on localhost:4096.
7
-
8
- echo "[kortix-master] Starting on port 8000, proxying to OpenCode at localhost:4096"
9
-
10
- exec s6-setuidgid abc \
11
- env HOME=/workspace \
12
- KORTIX_MASTER_PORT=8000 \
13
- OPENCODE_HOST=localhost \
14
- OPENCODE_PORT=4096 \
15
- PATH="/opt/bun/bin:/usr/local/bin:/usr/bin:/bin" \
16
- /opt/bun/bin/bun run /opt/kortix-master/src/index.ts
@@ -1,22 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # lss-sync — File-watcher daemon that keeps the semantic search index in sync.
3
- #
4
- # Uses watchdog (inotify/FSEvents) to detect file changes in real time and
5
- # triggers re-indexing with debounce. Changes are picked up within seconds.
6
- #
7
- # Watches:
8
- # /workspace — User files and projects
9
- # /workspace/.kortix — Agent memory
10
-
11
- exec s6-setuidgid abc \
12
- env HOME=/workspace \
13
- OPENAI_API_KEY="${OPENAI_API_KEY}" \
14
- LSS_DIR=/workspace/.lss \
15
- PATH="/lsiopy/bin:/usr/local/bin:/usr/bin:/bin" \
16
- lss-sync \
17
- --watch /workspace \
18
- --exclude node_modules \
19
- --exclude .git \
20
- --exclude __pycache__ \
21
- --startup-delay 15 \
22
- --debounce 2
@@ -1,25 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # OpenCode API Server — s6 service
3
- #
4
- # Runs `opencode serve` on port 4096 (internal only, proxied by kortix-master).
5
- # This is the headless API server, distinct from `opencode web` (the Web UI).
6
-
7
- # Wait a moment for filesystem to settle
8
- sleep 3
9
-
10
- cd /workspace
11
-
12
- # Ensure OpenCode data dirs exist and are owned by abc (guards against
13
- # Daytona race conditions where /workspace ownership may shift after startup.sh)
14
- mkdir -p /workspace/.local/share/opencode/log \
15
- /workspace/.local/share/opencode/storage \
16
- /workspace/.local/share/opencode/snapshot
17
- chown -R abc:abc /workspace/.local/share/opencode
18
-
19
- echo "[opencode-serve] Starting OpenCode API server on port 4096"
20
-
21
- exec s6-setuidgid abc \
22
- env HOME=/workspace \
23
- OPENCODE_CONFIG_DIR=/opt/opencode \
24
- PATH="/opt/bun/bin:/usr/local/bin:/usr/bin:/bin" \
25
- opencode serve --port 4096 --hostname 0.0.0.0
@@ -1,21 +0,0 @@
1
- #!/usr/bin/with-contenv bash
2
- # OpenCode Web UI — serves the web interface on port 3111
3
- #
4
- # Runs `opencode web` headless so it's accessible via the browser at
5
- # http://localhost:3111 (mapped from the container).
6
-
7
- cd /workspace
8
-
9
- # Ensure OpenCode data dirs exist and are owned by abc
10
- mkdir -p /workspace/.local/share/opencode/log \
11
- /workspace/.local/share/opencode/storage \
12
- /workspace/.local/share/opencode/snapshot
13
- chown -R abc:abc /workspace/.local/share/opencode
14
-
15
- exec s6-setuidgid abc \
16
- env HOME=/workspace \
17
- OPENCODE_CONFIG_DIR=/opt/opencode \
18
- OPENCODE_SERVER_USERNAME= \
19
- OPENCODE_SERVER_PASSWORD= \
20
- PATH="/opt/bun/bin:/usr/local/bin:/usr/bin:/bin" \
21
- opencode web --port 3111 --hostname 0.0.0.0