add --allow for managing multiple whitelisted directories
feat: add --allow flag support for additional directory whitelisting Add --allow <dir> flag to both jail.sh and vibe.sh scripts: - jail.sh: Accepts multiple --allow flags before the command - vibe.sh: Accepts multiple --allow flags before optional session token - Each allowed directory gets both --whitelist and --read-write in firejail - Maintains backward compatibility when no --allow flags are used Usage examples: jail --allow /data --allow /projects opencode vibe --allow /tmp --allow /data my-session-token docs: add implementation plan for --allow flag CLI support feat(jail): add --allow flag for additional directory whitelisting feat(vibe): add --allow flag support and pass through to jail
4 files changed,  +424, -17
A docs/plans/2026-05-03-allow-flag-cli-design.md
+74, -0
 1@@ -0,0 +1,74 @@
 2+# CLI --allow Flag Support for jail.sh and vibe.sh
 3+
 4+## Summary
 5+
 6+Add `--allow <dir>` flag support to `jail.sh` and `vibe.sh` scripts to enable users to whitelist additional directories with read/write access in the firejail sandbox.
 7+
 8+## Current Behavior
 9+
10+- `jail.sh` sandboxes a command using firejail with a fixed set of whitelisted paths
11+- `vibe.sh` is a convenience wrapper that launches `opencode` through `jail.sh`
12+- Users cannot add custom directories to the sandbox whitelist
13+
14+## Proposed Behavior
15+
16+### jail.sh
17+
18+```bash
19+jail [--allow <dir>]... <command> [args...]
20+```
21+
22+Examples:
23+- `jail opencode` - Run opencode with default whitelist
24+- `jail --allow /tmp opencode` - Add /tmp to whitelist
25+- `jail --allow /data --allow /home/projects bash` - Add multiple directories, run bash
26+- `jail --allow /mnt opencode -s token123` - Combine with command args
27+
28+### vibe.sh
29+
30+```bash
31+vibe [--allow <dir>]... [<session_token>]
32+```
33+
34+Examples:
35+- `vibe` - Run opencode with default whitelist
36+- `vibe --allow /tmp` - Add /tmp to whitelist
37+- `vibe --allow /data --allow /projects token123` - Multiple dirs + session token
38+
39+## Design Decisions
40+
41+1. **One directory per flag**: Each `--allow` flag takes exactly one directory argument
42+2. **Flags before positional args**: All `--allow` flags must come before the command (jail) or token (vibe)
43+3. **No read-only option**: All allowed directories get read/write access
44+4. **getopts parsing**: Use bash's getopts for clean, POSIX-compliant argument parsing
45+
46+## Technical Details
47+
48+### jail.sh Changes
49+
50+- Parse `--allow` flags using getopts loop before processing command
51+- Collect directories into an array
52+- For each directory, add `--whitelist=<dir>` and `--read-write=<dir>` to FIREJAIL_ARGS
53+- Pass remaining arguments unchanged to firejail
54+
55+### vibe.sh Changes
56+
57+- Parse `--allow` flags using getopts loop before handling session token
58+- Collect directories and pass them to jail.sh using `--allow <dir>` syntax
59+- Maintain existing token handling logic (`-s <token>` or positional)
60+
61+## Edge Cases
62+
63+- **Duplicate directories**: Firejail handles gracefully (no error)
64+- **Non-existent directories**: Firejail will error with appropriate message
65+- **Directories with spaces**: Properly quoted via array handling
66+- **No --allow flags**: Backward compatible with existing behavior
67+- **No arguments after --allow flags**: Script should error with usage message
68+
69+## Success Criteria
70+
71+- [ ] `jail --allow /tmp opencode` works and /tmp is writable in sandbox
72+- [ ] `jail --allow /foo --allow /bar bash` allows access to both directories
73+- [ ] `vibe --allow /tmp` passes through to jail correctly
74+- [ ] `vibe --allow /data token123` works with session token
75+- [ ] Scripts remain backward compatible (no --allow flags)
A docs/plans/2026-05-03-allow-flag-cli-implementation-plan.md
+259, -0
  1@@ -0,0 +1,259 @@
  2+# --allow Flag CLI Support Implementation Plan
  3+
  4+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
  5+
  6+**Goal:** Add `--allow <dir>` flag support to `jail.sh` and `vibe.sh` to enable whitelisting additional directories with read/write access in the firejail sandbox.
  7+
  8+**Architecture:** Parse `--allow` flags using bash's getopts before processing commands, collect directories into an array, then add `--whitelist` and `--read-write` entries to firejail arguments for each directory.
  9+
 10+**Tech Stack:** bash, firejail
 11+
 12+---
 13+
 14+### Task 1: Modify jail.sh to support --allow flag
 15+
 16+**Files:**
 17+- Modify: `/home/btburke/projects/vibe/jail.sh`
 18+
 19+**Context:** The script currently takes all arguments as the command to run (`$@`). We need to parse `--allow` flags first, then treat remaining args as the command.
 20+
 21+- [ ] **Step 1: Add argument parsing before existing logic**
 22+
 23+After line 10 (`set -euo pipefail`) and before line 12 (the firejail check comment), add:
 24+
 25+```bash
 26+# Parse --allow flags to add extra whitelisted directories
 27+ALLOWED_DIRS=()
 28+while [[ $# -gt 0 ]]; do
 29+    case "$1" in
 30+        --allow)
 31+            if [[ $# -lt 2 ]]; then
 32+                echo "Error: --allow requires a directory argument" >&2
 33+                exit 1
 34+            fi
 35+            ALLOWED_DIRS+=("$2")
 36+            shift 2
 37+            ;;
 38+        --)
 39+            shift
 40+            break
 41+            ;;
 42+        -*)
 43+            echo "Error: Unknown flag: $1" >&2
 44+            echo "Usage: jail [--allow <dir>]... <command> [args...]" >&2
 45+            exit 1
 46+            ;;
 47+        *)
 48+            break
 49+            ;;
 50+    esac
 51+done
 52+
 53+# Check that a command was provided
 54+if [[ $# -eq 0 ]]; then
 55+    echo "Error: No command specified" >&2
 56+    echo "Usage: jail [--allow <dir>]... <command> [args...]" >&2
 57+    exit 1
 58+fi
 59+```
 60+
 61+- [ ] **Step 2: Add allowed directories to FIREJAIL_ARGS**
 62+
 63+After the FIREJAIL_ARGS array is fully built (around line 131, before the blacklist comment), add:
 64+
 65+```bash
 66+# Add user-specified allowed directories
 67+for dir in "${ALLOWED_DIRS[@]}"; do
 68+    FIREJAIL_ARGS+=(--whitelist="$dir")
 69+    FIREJAIL_ARGS+=(--read-write="$dir")
 70+done
 71+```
 72+
 73+Place this right before the "Blacklist specific tools" comment (around line 133).
 74+
 75+- [ ] **Step 3: Commit the changes**
 76+
 77+```bash
 78+jj commit -m "feat(jail): add --allow flag for additional directory whitelisting" jail.sh
 79+```
 80+
 81+---
 82+
 83+### Task 2: Modify vibe.sh to support --allow flag and pass through to jail
 84+
 85+**Files:**
 86+- Modify: `/home/btburke/projects/vibe/vibe.sh`
 87+
 88+**Context:** The script currently has specific handling for session tokens. We need to parse `--allow` flags first, collect them, then pass them to jail.sh along with the appropriate opencode arguments.
 89+
 90+- [ ] **Step 1: Replace the argument handling section**
 91+
 92+Replace lines 33-50 (everything from "Handle different argument patterns" comment to the `fi`) with:
 93+
 94+```bash
 95+# Parse --allow flags and handle session token
 96+ALLOWED_DIRS=()
 97+SESSION_TOKEN=""
 98+
 99+while [[ $# -gt 0 ]]; do
100+    case "$1" in
101+        --allow)
102+            if [[ $# -lt 2 ]]; then
103+                echo "Error: --allow requires a directory argument" >&2
104+                exit 1
105+            fi
106+            ALLOWED_DIRS+=("$2")
107+            shift 2
108+            ;;
109+        -s)
110+            if [[ $# -lt 2 ]]; then
111+                echo "Error: -s requires a token argument" >&2
112+                exit 1
113+            fi
114+            SESSION_TOKEN="$2"
115+            shift 2
116+            ;;
117+        -*)
118+            echo "Error: Unknown flag: $1" >&2
119+            echo "Usage: vibe [--allow <dir>]... [<session_token>]" >&2
120+            exit 1
121+            ;;
122+        *)
123+            if [[ -z "$SESSION_TOKEN" ]]; then
124+                SESSION_TOKEN="$1"
125+                shift
126+            else
127+                echo "Error: Unexpected argument: $1" >&2
128+                echo "Usage: vibe [--allow <dir>]... [<session_token>]" >&2
129+                exit 1
130+            fi
131+            ;;
132+    esac
133+done
134+
135+# Build jail command with allowed directories
136+JAIL_ARGS=()
137+for dir in "${ALLOWED_DIRS[@]}"; do
138+    JAIL_ARGS+=(--allow "$dir")
139+done
140+
141+# Execute with or without session token
142+if [[ -n "$SESSION_TOKEN" ]]; then
143+    exec jail "${JAIL_ARGS[@]}" opencode -s "$SESSION_TOKEN"
144+else
145+    exec jail "${JAIL_ARGS[@]}" opencode
146+fi
147+```
148+
149+- [ ] **Step 2: Commit the changes**
150+
151+```bash
152+jj commit -m "feat(vibe): add --allow flag support and pass through to jail" vibe.sh
153+```
154+
155+---
156+
157+### Task 3: Test the implementation
158+
159+**Files:**
160+- Test manually with bash commands
161+
162+- [ ] **Step 1: Test jail.sh with --allow flag**
163+
164+Run:
165+```bash
166+./jail.sh --allow /tmp bash -c "touch /tmp/test_jail_allow && echo 'Success: can write to /tmp' && rm /tmp/test_jail_allow"
167+```
168+
169+Expected: Command succeeds and prints "Success: can write to /tmp"
170+
171+- [ ] **Step 2: Test jail.sh with multiple --allow flags**
172+
173+Run:
174+```bash
175+./jail.sh --allow /tmp --allow /var/tmp bash -c "touch /tmp/test1 /var/tmp/test2 && echo 'Success' && rm /tmp/test1 /var/tmp/test2"
176+```
177+
178+Expected: Command succeeds and prints "Success"
179+
180+- [ ] **Step 3: Test jail.sh without --allow (backward compatibility)**
181+
182+Run:
183+```bash
184+./jail.sh bash -c "echo 'Hello from jail'"
185+```
186+
187+Expected: Command succeeds and prints "Hello from jail"
188+
189+- [ ] **Step 4: Test jail.sh error handling**
190+
191+Run:
192+```bash
193+./jail.sh --allow 2>&1 | head -1
194+```
195+
196+Expected: Error message about missing directory argument
197+
198+Run:
199+```bash
200+./jail.sh 2>&1 | head -1
201+```
202+
203+Expected: Error message about missing command
204+
205+- [ ] **Step 5: Test vibe.sh with --allow flag**
206+
207+Run (this will fail to start opencode without proper setup, but should show jail is called correctly):
208+```bash
209+./vibe.sh --allow /tmp 2>&1 | head -5
210+```
211+
212+Expected: Should see "Starting in firejail sandbox..." message (or firejail error if opencode not in PATH)
213+
214+- [ ] **Step 6: Commit test verification**
215+
216+```bash
217+jj commit -m "test: verify --allow flag implementation works correctly"
218+```
219+
220+---
221+
222+### Task 4: Final review and squash if needed
223+
224+- [ ] **Step 1: Review all changes**
225+
226+```bash
227+jj log -r '::@' -p
228+```
229+
230+- [ ] **Step 2: If multiple commits, consider squashing into one feature commit**
231+
232+```bash
233+jj squash -r @-- -r @
234+```
235+
236+Or keep separate commits if preferred.
237+
238+---
239+
240+## Implementation Notes
241+
242+### Why not use getopts?
243+
244+getopts doesn't support long options like `--allow` in a clean way without using GNU getopt. The manual case statement approach is more portable and clearer for this simple use case.
245+
246+### Argument parsing strategy
247+
248+Both scripts use a while loop with a case statement to:
249+1. Collect all `--allow <dir>` pairs into an array
250+2. Handle vibe.sh's `-s <token>` or positional token
251+3. Stop at first non-flag argument (for jail.sh, this is the command)
252+4. Validate that required arguments are present
253+
254+### Firejail integration
255+
256+The allowed directories are added to FIREJAIL_ARGS as:
257+- `--whitelist=<dir>` - Makes the directory visible in the sandbox
258+- `--read-write=<dir>` - Grants read/write permissions
259+
260+These are added after the default whitelist but before the blacklist, which is the correct order for firejail's rule processing.
M jail.sh
+40, -0
 1@@ -9,6 +9,40 @@
 2 
 3 set -euo pipefail
 4 
 5+# Parse --allow flags to add extra whitelisted directories
 6+ALLOWED_DIRS=()
 7+while [[ $# -gt 0 ]]; do
 8+    case "$1" in
 9+        --allow)
10+            if [[ $# -lt 2 ]]; then
11+                echo "Error: --allow requires a directory argument" >&2
12+                exit 1
13+            fi
14+            ALLOWED_DIRS+=("$2")
15+            shift 2
16+            ;;
17+        --)
18+            shift
19+            break
20+            ;;
21+        -*)
22+            echo "Error: Unknown flag: $1" >&2
23+            echo "Usage: jail [--allow <dir>]... <command> [args...]" >&2
24+            exit 1
25+            ;;
26+        *)
27+            break
28+            ;;
29+    esac
30+done
31+
32+# Check that a command was provided
33+if [[ $# -eq 0 ]]; then
34+    echo "Error: No command specified" >&2
35+    echo "Usage: jail [--allow <dir>]... <command> [args...]" >&2
36+    exit 1
37+fi
38+
39 # Check for required system dependencies
40 if ! command -v firejail &> /dev/null; then
41     echo "Error: firejail is not installed or not in PATH" >&2
42@@ -128,6 +162,12 @@ if [ -d "$HOME/.cache/ms-playwright" ]; then
43     FIREJAIL_ARGS+=(--read-write="$HOME/.cache/ms-playwright")
44 fi
45 
46+# Add user-specified allowed directories
47+for dir in "${ALLOWED_DIRS[@]}"; do
48+    FIREJAIL_ARGS+=(--whitelist="$dir")
49+    FIREJAIL_ARGS+=(--read-write="$dir")
50+done
51+
52 # Network access (allow by default, can be restricted with --net=none)
53 # FIREJAIL_ARGS+=(--net=none)  # Uncomment to disable network access
54 
M vibe.sh
+51, -17
 1@@ -1,7 +1,7 @@
 2 #!/usr/bin/env bash
 3 
 4 # vibe - Run OpenCode in a sandboxed firejail environment
 5-# Usage: vibe [<token>] or vibe -s <token>
 6+# Usage: vibe [--allow <dir>]... [<session_token>]
 7 #
 8 # NOTE: This script requires system-installed jail (from this package),
 9 # firejail, and opencode. Install firejail and opencode via your package manager.
10@@ -30,21 +30,55 @@ if ! command -v opencode &> /dev/null; then
11     exit 1
12 fi
13 
14-# Handle different argument patterns:
15-# - vibe (no args) -> jail opencode
16-# - vibe <token> -> jail opencode -s <token>
17-# - vibe -s <token> -> jail opencode -s <token>
18-
19-if [ $# -eq 0 ]; then
20-    # No arguments - run opencode without session token
21-    exec jail opencode
22-elif [ "$1" = "-s" ] && [ $# -eq 2 ]; then
23-    # Explicit -s flag with token
24-    exec jail opencode -s "$2"
25-elif [ $# -eq 1 ] && [ "$1" != "-s" ]; then
26-    # Single argument that is not -s, treat as token
27-    exec jail opencode -s "$1"
28+# Parse --allow flags and handle session token
29+ALLOWED_DIRS=()
30+SESSION_TOKEN=""
31+
32+while [[ $# -gt 0 ]]; do
33+    case "$1" in
34+        --allow)
35+            if [[ $# -lt 2 ]]; then
36+                echo "Error: --allow requires a directory argument" >&2
37+                exit 1
38+            fi
39+            ALLOWED_DIRS+=("$2")
40+            shift 2
41+            ;;
42+        -s)
43+            if [[ $# -lt 2 ]]; then
44+                echo "Error: -s requires a token argument" >&2
45+                exit 1
46+            fi
47+            SESSION_TOKEN="$2"
48+            shift 2
49+            ;;
50+        -*)
51+            echo "Error: Unknown flag: $1" >&2
52+            echo "Usage: vibe [--allow <dir>]... [<session_token>]" >&2
53+            exit 1
54+            ;;
55+        *)
56+            if [[ -z "$SESSION_TOKEN" ]]; then
57+                SESSION_TOKEN="$1"
58+                shift
59+            else
60+                echo "Error: Unexpected argument: $1" >&2
61+                echo "Usage: vibe [--allow <dir>]... [<session_token>]" >&2
62+                exit 1
63+            fi
64+            ;;
65+    esac
66+done
67+
68+# Build jail command with allowed directories
69+JAIL_ARGS=()
70+for dir in "${ALLOWED_DIRS[@]}"; do
71+    JAIL_ARGS+=(--allow "$dir")
72+done
73+
74+# Execute with or without session token
75+if [[ -n "$SESSION_TOKEN" ]]; then
76+    exec jail "${JAIL_ARGS[@]}" opencode -s "$SESSION_TOKEN"
77 else
78-    echo "Usage: vibe [<token>] or vibe -s <token>" >&2
79-    exit 1
80+    exec jail "${JAIL_ARGS[@]}" opencode
81 fi