aibox-cli 0.3.0 → 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.
- package/README.md +14 -4
- package/bin/aibox +69 -29
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -19,11 +19,8 @@ On macOS, if Docker isn't installed, aibox will offer to install [Colima](https:
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
# first time (once)
|
|
23
|
-
aibox build
|
|
24
|
-
|
|
25
22
|
# in any project directory
|
|
26
|
-
aibox up # start container
|
|
23
|
+
aibox up # start container (auto-builds image on first run)
|
|
27
24
|
aibox claude --yolo # no prompts, full sudo, no firewall
|
|
28
25
|
aibox claude --safe # keep prompts, restricted sudo, firewall on
|
|
29
26
|
aibox claude # asks you each time
|
|
@@ -71,6 +68,17 @@ aibox down --all # stop all containers for this project
|
|
|
71
68
|
aibox nuke # remove ALL aibox containers
|
|
72
69
|
```
|
|
73
70
|
|
|
71
|
+
### From a Git Repo
|
|
72
|
+
|
|
73
|
+
Start directly from a repo URL — aibox clones it and runs:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
aibox --repo https://github.com/user/project.git claude --yolo
|
|
77
|
+
aibox --repo git@github.com:user/project.git --branch dev claude
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Repos are cloned to `~/.config/aibox/repos/` with `--recursive` (submodules included). On subsequent runs, the existing clone is reused.
|
|
81
|
+
|
|
74
82
|
### Custom Image
|
|
75
83
|
|
|
76
84
|
```bash
|
|
@@ -151,6 +159,8 @@ SHARED_MODULES=false
|
|
|
151
159
|
|-------|------|-------------|
|
|
152
160
|
| `-n` | `--name NAME` | Named instance (multiple containers per project) |
|
|
153
161
|
| `-d` | `--dir PATH` | Run in a different project directory |
|
|
162
|
+
| `-r` | `--repo URL` | Clone a git repo and run in it |
|
|
163
|
+
| `-b` | `--branch NAME` | Branch to checkout (with `--repo`) |
|
|
154
164
|
| `-i` | `--image NAME` | Override base Docker image |
|
|
155
165
|
| `-c` | `--copy` | Copy repo into Docker volume (full isolation) |
|
|
156
166
|
| `-w` | `--worktree` | Use git worktree (lightweight isolation) |
|
package/bin/aibox
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
# -n, --name NAME Run a named instance (e.g. -n refactor)
|
|
24
24
|
# Allows multiple containers per project
|
|
25
25
|
# -d, --dir PATH Run in a different project directory
|
|
26
|
+
# -r, --repo URL Clone a git repo and run in it
|
|
27
|
+
# -b, --branch NAME Branch to checkout (with --repo)
|
|
26
28
|
# -i, --image NAME Override base Docker image (default: aibox:latest)
|
|
27
29
|
# -c, --copy Copy project into container (full isolation, no host sync)
|
|
28
30
|
# -w, --worktree Use git worktree (lightweight isolation, stays on host)
|
|
@@ -55,11 +57,25 @@ set -euo pipefail
|
|
|
55
57
|
|
|
56
58
|
# ── Script identity ──────────────────────────────────────────────
|
|
57
59
|
SCRIPT_NAME="$(basename "$0")"
|
|
58
|
-
AIBOX_VERSION="
|
|
60
|
+
AIBOX_VERSION="7"
|
|
59
61
|
CONFIG_DIR="${HOME}/.config/aibox"
|
|
60
62
|
DEFAULT_IMAGE="aibox:latest"
|
|
61
63
|
CONTAINER_PREFIX="aibox"
|
|
62
64
|
|
|
65
|
+
# ── Output helpers ─────────────────────────────────────────────
|
|
66
|
+
_step() {
|
|
67
|
+
local msg="$1"
|
|
68
|
+
local width=60
|
|
69
|
+
local pad
|
|
70
|
+
pad=$(( width - ${#msg} - 5 )) # 5 = "── " + " ─"
|
|
71
|
+
[[ $pad -lt 3 ]] && pad=3
|
|
72
|
+
local line
|
|
73
|
+
line=$(printf '─%.0s' $(seq 1 "$pad"))
|
|
74
|
+
echo ""
|
|
75
|
+
echo "── ${msg} ${line}"
|
|
76
|
+
echo ""
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
# ── Machine-aware Colima start ───────────────────────────────────
|
|
64
80
|
_colima_start() {
|
|
65
81
|
local total_cpu total_mem_gb vm_cpu vm_mem vm_disk
|
|
@@ -185,6 +201,8 @@ IMAGE="$DEFAULT_IMAGE"
|
|
|
185
201
|
SHARED_MODULES=false
|
|
186
202
|
INSTANCE_NAME=""
|
|
187
203
|
PROJECT_DIR_FLAG=""
|
|
204
|
+
REPO_URL=""
|
|
205
|
+
REPO_BRANCH=""
|
|
188
206
|
DOWN_ALL=false
|
|
189
207
|
DOWN_CLEAN=false
|
|
190
208
|
SKIP_PERMISSIONS=false
|
|
@@ -207,6 +225,14 @@ parse_flags() {
|
|
|
207
225
|
PROJECT_DIR_FLAG="${2:?'--dir requires a value'}"
|
|
208
226
|
shift 2
|
|
209
227
|
;;
|
|
228
|
+
-r|--repo)
|
|
229
|
+
REPO_URL="${2:?'--repo requires a value'}"
|
|
230
|
+
shift 2
|
|
231
|
+
;;
|
|
232
|
+
-b|--branch)
|
|
233
|
+
REPO_BRANCH="${2:?'--branch requires a value'}"
|
|
234
|
+
shift 2
|
|
235
|
+
;;
|
|
210
236
|
--shared-modules)
|
|
211
237
|
SHARED_MODULES=true
|
|
212
238
|
shift
|
|
@@ -259,6 +285,31 @@ elif [[ "$SAFE_MODE" == "true" ]]; then
|
|
|
259
285
|
export AIBOX_MODE="safe"
|
|
260
286
|
fi
|
|
261
287
|
|
|
288
|
+
# ── Repo clone ───────────────────────────────────────────────────
|
|
289
|
+
if [[ -n "$REPO_URL" ]]; then
|
|
290
|
+
# Derive a directory name from the repo URL (hash prevents collisions)
|
|
291
|
+
_repo_name=$(basename "$REPO_URL" .git)
|
|
292
|
+
_repo_hash=$(printf '%s' "$REPO_URL" | shasum | cut -c1-6)
|
|
293
|
+
_repo_dir="${CONFIG_DIR}/repos/${_repo_name}-${_repo_hash}"
|
|
294
|
+
|
|
295
|
+
if [[ -d "$_repo_dir/.git" ]]; then
|
|
296
|
+
echo "Repo already cloned (${_repo_dir}). Reusing."
|
|
297
|
+
# Fetch latest and checkout branch if specified
|
|
298
|
+
if [[ -n "$REPO_BRANCH" ]]; then
|
|
299
|
+
git -C "$_repo_dir" fetch --all --quiet 2>/dev/null || true
|
|
300
|
+
git -C "$_repo_dir" checkout "$REPO_BRANCH" 2>/dev/null || true
|
|
301
|
+
fi
|
|
302
|
+
else
|
|
303
|
+
echo "Cloning ${REPO_URL}..."
|
|
304
|
+
mkdir -p "${CONFIG_DIR}/repos"
|
|
305
|
+
_clone_args=(--recursive)
|
|
306
|
+
[[ -n "$REPO_BRANCH" ]] && _clone_args+=(--branch "$REPO_BRANCH")
|
|
307
|
+
git clone "${_clone_args[@]}" "$REPO_URL" "$_repo_dir"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
PROJECT_DIR_FLAG="$_repo_dir"
|
|
311
|
+
fi
|
|
312
|
+
|
|
262
313
|
# ── Per-project config file (.aibox) ─────────────────────────────
|
|
263
314
|
if [[ -n "$PROJECT_DIR_FLAG" ]]; then
|
|
264
315
|
PROJECT_DIR="$(cd "$PROJECT_DIR_FLAG" 2>/dev/null && pwd)" || {
|
|
@@ -347,7 +398,7 @@ if ! [[ "$INSTANCE_NAME" =~ ^[a-z0-9_-]+$ ]]; then
|
|
|
347
398
|
exit 1
|
|
348
399
|
fi
|
|
349
400
|
CONTAINER_NAME="${BASE_CONTAINER_NAME}-${INSTANCE_NAME}"
|
|
350
|
-
WORKSPACE_DIR="
|
|
401
|
+
WORKSPACE_DIR="${PROJECT_DIR}"
|
|
351
402
|
|
|
352
403
|
# ── Isolation mode setup ─────────────────────────────────────────
|
|
353
404
|
COPY_VOLUME=""
|
|
@@ -385,7 +436,7 @@ ensure_dockerfile() {
|
|
|
385
436
|
|
|
386
437
|
if [[ "$current_version" != "$AIBOX_VERSION" || ! -f "$dockerfile" ]]; then
|
|
387
438
|
if [[ -f "$dockerfile" && "$current_version" != "$AIBOX_VERSION" ]]; then
|
|
388
|
-
echo "Dockerfile outdated (v${current_version} → v${AIBOX_VERSION}). Regenerating..."
|
|
439
|
+
echo "Dockerfile outdated (v${current_version} → v${AIBOX_VERSION}). Regenerating (one-time)..."
|
|
389
440
|
fi
|
|
390
441
|
cat > "$dockerfile" << DOCKERFILE
|
|
391
442
|
FROM node:20-alpine
|
|
@@ -436,16 +487,6 @@ set -e
|
|
|
436
487
|
# Fix auth volume ownership (Docker creates volumes as root)
|
|
437
488
|
chown -R aibox:aibox /home/aibox/.claude 2>/dev/null || true
|
|
438
489
|
|
|
439
|
-
# ── IDE path symlink ─────────────────────────────────────────
|
|
440
|
-
# Create symlink so container paths match host paths for IDE integration.
|
|
441
|
-
# Claude Code reports cwd-based paths to the IDE plugin; if they don't
|
|
442
|
-
# match host paths, diffs break (empty left side, wrong file path).
|
|
443
|
-
if [[ -n "${AIBOX_HOST_DIR:-}" && -n "${AIBOX_WORKSPACE_DIR:-}" \
|
|
444
|
-
&& "$AIBOX_HOST_DIR" != "$AIBOX_WORKSPACE_DIR" ]]; then
|
|
445
|
-
mkdir -p "$(dirname "$AIBOX_HOST_DIR")"
|
|
446
|
-
ln -sfn "$AIBOX_WORKSPACE_DIR" "$AIBOX_HOST_DIR"
|
|
447
|
-
fi
|
|
448
|
-
|
|
449
490
|
# ── Mode-dependent setup ──────────────────────────────────────
|
|
450
491
|
MODE="${AIBOX_MODE:-safe}"
|
|
451
492
|
|
|
@@ -545,14 +586,14 @@ _prepare_copy_volume() {
|
|
|
545
586
|
|
|
546
587
|
# Check if volume already has content (--entrypoint bypasses su-exec drop)
|
|
547
588
|
local has_content
|
|
548
|
-
has_content=$(docker run --rm --entrypoint sh -v "${COPY_VOLUME}:${WORKSPACE_DIR}" "$IMAGE" -c "ls -A ${WORKSPACE_DIR} 2>/dev/null | head -1" 2>/dev/null || true)
|
|
589
|
+
has_content=$(docker run --rm --entrypoint sh -v "${COPY_VOLUME}:${WORKSPACE_DIR}" "$IMAGE" -c "ls -A '${WORKSPACE_DIR}' 2>/dev/null | head -1" 2>/dev/null || true)
|
|
549
590
|
|
|
550
591
|
if [[ -n "$has_content" ]]; then
|
|
551
592
|
echo "Copy volume already populated (${COPY_VOLUME}). Reusing."
|
|
552
593
|
return
|
|
553
594
|
fi
|
|
554
595
|
|
|
555
|
-
|
|
596
|
+
_step "Copying project into volume (branch: ${branch_name})"
|
|
556
597
|
|
|
557
598
|
# Create bundle to a temp file first so we can check for failure
|
|
558
599
|
local bundle_file
|
|
@@ -568,12 +609,12 @@ _prepare_copy_volume() {
|
|
|
568
609
|
-v "${COPY_VOLUME}:${WORKSPACE_DIR}" \
|
|
569
610
|
"$IMAGE" -c "
|
|
570
611
|
cat > /tmp/repo.bundle
|
|
571
|
-
mkdir -p ${WORKSPACE_DIR}
|
|
572
|
-
cd ${WORKSPACE_DIR}
|
|
612
|
+
mkdir -p '${WORKSPACE_DIR}'
|
|
613
|
+
cd '${WORKSPACE_DIR}'
|
|
573
614
|
git clone /tmp/repo.bundle .
|
|
574
615
|
rm /tmp/repo.bundle
|
|
575
616
|
git checkout -b '${branch_name}' 2>/dev/null || git checkout '${branch_name}'
|
|
576
|
-
chown -R aibox:aibox ${WORKSPACE_DIR}
|
|
617
|
+
chown -R aibox:aibox '${WORKSPACE_DIR}'
|
|
577
618
|
"; then
|
|
578
619
|
rm -f "$bundle_file"
|
|
579
620
|
docker volume rm "$COPY_VOLUME" 2>/dev/null || true
|
|
@@ -596,7 +637,7 @@ _prepare_worktree() {
|
|
|
596
637
|
|
|
597
638
|
mkdir -p "${CONFIG_DIR}/worktrees"
|
|
598
639
|
|
|
599
|
-
|
|
640
|
+
_step "Creating worktree (branch: ${branch_name})"
|
|
600
641
|
|
|
601
642
|
# Try creating with a new branch, fall back to existing branch
|
|
602
643
|
if git -C "$PROJECT_DIR" worktree add "$WORKTREE_DIR" -b "$branch_name" 2>/dev/null; then
|
|
@@ -659,8 +700,6 @@ _environment_yaml() {
|
|
|
659
700
|
echo "${indent}- CLAUDE_CONFIG_DIR=/home/aibox/.claude"
|
|
660
701
|
echo "${indent}- AIBOX_MODE=\${AIBOX_MODE:-safe}"
|
|
661
702
|
echo "${indent}- AIBOX_EXTRA_DOMAINS=\${AIBOX_EXTRA_DOMAINS:-}"
|
|
662
|
-
echo "${indent}- AIBOX_HOST_DIR=${PROJECT_DIR}"
|
|
663
|
-
echo "${indent}- AIBOX_WORKSPACE_DIR=${WORKSPACE_DIR}"
|
|
664
703
|
}
|
|
665
704
|
|
|
666
705
|
# Build -e flags for docker exec from current terminal environment
|
|
@@ -832,15 +871,16 @@ dc() {
|
|
|
832
871
|
cmd_build() {
|
|
833
872
|
_check_deps
|
|
834
873
|
ensure_dockerfile
|
|
835
|
-
|
|
874
|
+
_step "Building ${IMAGE}"
|
|
836
875
|
docker build -t "$IMAGE" "$CONFIG_DIR"
|
|
837
|
-
echo "Done.
|
|
876
|
+
echo "Done."
|
|
838
877
|
}
|
|
839
878
|
|
|
840
879
|
ensure_init() {
|
|
841
880
|
if [[ ! -f "${PROJECT_DIR}/compose.dev.yaml" ]]; then
|
|
842
881
|
_require_safe_dir
|
|
843
|
-
|
|
882
|
+
_step "Initializing project (one-time)"
|
|
883
|
+
echo "Will create compose.dev.yaml, .aibox, and .idea/workspace.xml."
|
|
844
884
|
if _confirm_yes "Initialize?"; then
|
|
845
885
|
_init_files
|
|
846
886
|
echo ""
|
|
@@ -856,14 +896,14 @@ cmd_up() {
|
|
|
856
896
|
ensure_init
|
|
857
897
|
|
|
858
898
|
if ! docker image inspect "$IMAGE" &>/dev/null; then
|
|
859
|
-
echo "Image '${IMAGE}' not found. Building..."
|
|
899
|
+
echo "Image '${IMAGE}' not found. Building (one-time)..."
|
|
860
900
|
cmd_build
|
|
861
901
|
else
|
|
862
902
|
# Check if image version matches script version
|
|
863
903
|
local img_version
|
|
864
904
|
img_version=$(docker inspect "$IMAGE" --format '{{index .Config.Labels "aibox.version"}}' 2>/dev/null || echo "")
|
|
865
905
|
if [[ "$img_version" != "$AIBOX_VERSION" ]]; then
|
|
866
|
-
echo "Image outdated (v${img_version:-0} → v${AIBOX_VERSION}). Rebuilding..."
|
|
906
|
+
echo "Image outdated (v${img_version:-0} → v${AIBOX_VERSION}). Rebuilding (one-time)..."
|
|
867
907
|
cmd_build
|
|
868
908
|
fi
|
|
869
909
|
fi
|
|
@@ -878,9 +918,9 @@ cmd_up() {
|
|
|
878
918
|
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
|
|
879
919
|
# Ensure shared auth volume exists (external: true requires it)
|
|
880
920
|
docker volume create "$AUTH_VOLUME" &>/dev/null || true
|
|
881
|
-
|
|
921
|
+
_step "Starting ${CONTAINER_NAME}"
|
|
882
922
|
dc up -d
|
|
883
|
-
echo "Container running.
|
|
923
|
+
echo "Container running."
|
|
884
924
|
else
|
|
885
925
|
echo "${CONTAINER_NAME} is already running."
|
|
886
926
|
fi
|
|
@@ -930,7 +970,7 @@ cmd_claude() {
|
|
|
930
970
|
local current_mode
|
|
931
971
|
current_mode=$(docker exec "$CONTAINER_NAME" sh -c 'echo ${AIBOX_MODE:-safe}' 2>/dev/null || echo "unknown")
|
|
932
972
|
if [[ "$current_mode" != "$AIBOX_MODE" ]]; then
|
|
933
|
-
|
|
973
|
+
_step "Restarting container in ${AIBOX_MODE} mode (was: ${current_mode})"
|
|
934
974
|
dc down --remove-orphans >/dev/null
|
|
935
975
|
fi
|
|
936
976
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aibox-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Run AI coding agents in isolated Docker containers",
|
|
5
5
|
"author": "repalash <palash@shaders.app>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,5 +35,8 @@
|
|
|
35
35
|
],
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"release": "git tag v$npm_package_version && git push origin v$npm_package_version"
|
|
38
41
|
}
|
|
39
42
|
}
|