@@ -63,7 +63,7 @@ _agent_vm_ensure_running() {
6363 local vm_name=" $1 "
6464 local host_dir=" $2 "
6565 shift 2
66- local disk=" " memory=" " cpus=" " reset=" " offline=" " rdonly=" " git_ro=" "
66+ local disk=" " memory=" " cpus=" " reset=" " offline=" " rdonly=" " git_ro=" " portforward= " "
6767 while [[ $# -gt 0 ]]; do
6868 case " $1 " in
6969 --disk) disk=" $2 " ; shift 2 ;;
@@ -73,6 +73,7 @@ _agent_vm_ensure_running() {
7373 --offline) offline=1; shift ;;
7474 --readonly) rdonly=1; shift ;;
7575 --git-read-only|--git-ro) git_ro=1; shift ;;
76+ --port-forward) portforward=" $2 " ; shift 2 ;;
7677 * ) shift ;;
7778 esac
7879 done
@@ -112,7 +113,7 @@ _agent_vm_ensure_running() {
112113 if [[ -f " $base_ver " ]]; then
113114 cp " $base_ver " " $AGENT_VM_STATE_DIR /.agent-vm-version-${vm_name} "
114115 fi
115- elif [[ -n " $disk " || -n " $memory " || -n " $cpus " ]]; then
116+ elif [[ -n " $disk " || -n " $memory " || -n " $cpus " || -n " $portforward " ]]; then
116117 # Auto-resize existing VM if --disk, --memory, or --cpus changed
117118 if _agent_vm_running " $vm_name " ; then
118119 echo " VM '$vm_name ' is currently running. It must be stopped to apply new resource settings."
@@ -154,7 +155,9 @@ _agent_vm_ensure_running() {
154155
155156 if ! _agent_vm_running " $vm_name " ; then
156157 echo " Starting VM '$vm_name '..."
157- limactl start " $vm_name " & > /dev/null
158+ local start_args=(" $vm_name " )
159+ [[ -n " $portforward " ]] && start_args+=(--port-forward " $portforward " )
160+ limactl start " ${start_args[@]} " & > /dev/null
158161 fi
159162
160163 # Run per-user runtime script if it exists
@@ -219,6 +222,12 @@ agent-vm() {
219222 vm_opts+=(--git-read-only); shift ;;
220223 --rm)
221224 vm_opts+=(--rm); shift ;;
225+ --port-forward)
226+ vm_opts+=(--port-forward " $2 " ); shift 2 ;;
227+ --port-forward=* )
228+ vm_opts+=(--port-forward " ${1#* =} " ); shift ;;
229+ --background)
230+ vm_opts+=(--background); shift ;;
222231 * )
223232 break ;;
224233 esac
@@ -299,6 +308,8 @@ VM options (for claude, opencode, codex, shell, run):
299308 --readonly Mount the project directory as read-only
300309 --git-read-only Mount .git directory as read-only (allows git diff/log but not commit/stash)
301310 --rm Automatically destroy the VM after the command exits
311+ --port-forward Enable port forwarding (hostfwd)
312+ --background Run in background (detached from terminal)
302313
303314Examples:
304315 agent-vm setup # Create base VM
@@ -311,6 +322,8 @@ Examples:
311322 agent-vm --offline claude # No internet access
312323 agent-vm --readonly shell # Read-only project mount
313324 agent-vm --git-ro claude # Protect .git from writes
325+ agent-vm --port-forward '3000:3000' opencode serve --port 3000 # Forward vm ports to the host
326+ agent-vm --background opencode serve --port 3000 # Run in background
314327 agent-vm shell # Shell into the VM
315328 agent-vm run npm install # Run a command in the VM
316329 agent-vm claude -p "fix lint errors" # Pass args to claude
@@ -331,18 +344,20 @@ _agent_vm_setup() {
331344 local disk=10
332345 local memory=2
333346 local cpus=1
347+ local portforward=" "
334348
335349 while [[ $# -gt 0 ]]; do
336350 case " $1 " in
337351 --help|-h)
338- echo " Usage: agent-vm setup [--disk GB] [--memory GB] [--cpus N]"
352+ echo " Usage: agent-vm setup [--disk GB] [--memory GB] [--cpus N] [--port-forward] "
339353 echo " "
340354 echo " Create a base VM template with dev tools and agents pre-installed."
341355 echo " "
342356 echo " Options:"
343357 echo " --disk GB VM disk size (default: 10)"
344358 echo " --memory GB VM memory (default: 2)"
345359 echo " --cpus N Number of CPUs (default: 1)"
360+ echo " --port-forward Enable port forwarding (hostfwd)"
346361 echo " --help Show this help"
347362 return 0
348363 ;;
@@ -372,6 +387,14 @@ _agent_vm_setup() {
372387 ;;
373388 --reset|--offline|--readonly|--git-read-only|--git-ro)
374389 shift ;;
390+ --port-forward)
391+ portforward=" $2 "
392+ shift 2
393+ ;;
394+ --port-forward=* )
395+ portforward=" ${1#* =} "
396+ shift
397+ ;;
375398 * )
376399 echo " Unknown option: $1 " >&2
377400 echo " Usage: agent-vm setup [--disk GB] [--memory GB] [--cpus N]" >&2
@@ -401,6 +424,7 @@ _agent_vm_setup() {
401424 --tty=false
402425 )
403426 [[ -n " $cpus " ]] && create_args+=(--cpus=" $cpus " )
427+ [[ -n " $portforward " ]] && create_args+=(--port-forward=" $portforward " )
404428 limactl create --name=" $AGENT_VM_TEMPLATE " template:debian-13 \
405429 " ${create_args[@]} " & > /dev/null || { echo " Error: Failed to create base VM." >&2 ; return 1; }
406430
@@ -434,6 +458,7 @@ _agent_vm_claude() {
434458 local vm_opts=()
435459 local args=()
436460 local rm=" "
461+ local background=" "
437462 while [[ $# -gt 0 ]]; do
438463 case " $1 " in
439464 --disk) vm_opts+=(--disk " $2 " ); shift 2 ;;
@@ -444,9 +469,17 @@ _agent_vm_claude() {
444469 --readonly) vm_opts+=(--readonly); shift ;;
445470 --git-read-only|--git-ro) vm_opts+=(--git-read-only); shift ;;
446471 --rm) rm=1; shift ;;
472+ --port-forward) vm_opts+=(--port-forward " $2 " ); shift 2 ;;
473+ --background) background=1; shift ;;
447474 * ) args+=(" $1 " ); shift ;;
448475 esac
449476 done
477+
478+ if [[ -n " $background " && -n " $rm " ]]; then
479+ echo " Error: --background and --rm cannot be used together" >&2
480+ return 1
481+ fi
482+
450483 local host_dir
451484 host_dir=" $( pwd) "
452485 local vm_name
@@ -455,17 +488,28 @@ _agent_vm_claude() {
455488 _agent_vm_ensure_running " $vm_name " " $host_dir " " ${vm_opts[@]} " || return 1
456489 _agent_vm_print_resources " $vm_name "
457490
458- local exit_code=0
459- limactl shell --workdir " $host_dir " " $vm_name " claude --dangerously-skip-permissions " ${args[@]} "
460- exit_code=$?
461- [[ -n " $rm " ]] && { echo " Removing VM..." ; _agent_vm_destroy; }
462- return $exit_code
491+ if [[ -n " $background " ]]; then
492+ nohup limactl shell --workdir " $host_dir " " $vm_name " claude --dangerously-skip-permissions " ${args[@]} " & > /dev/null &
493+ disown
494+ echo " Started claude in background (pid: $! )"
495+ return 0
496+ elif [[ -n " $rm " ]]; then
497+ limactl shell --workdir " $host_dir " " $vm_name " claude --dangerously-skip-permissions " ${args[@]} "
498+ exit_code=$?
499+ echo " Removing VM..."
500+ _agent_vm_destroy
501+ return $exit_code
502+ else
503+ limactl shell --workdir " $host_dir " " $vm_name " claude --dangerously-skip-permissions " ${args[@]} "
504+ return $?
505+ fi
463506}
464507
465508_agent_vm_opencode () {
466509 local vm_opts=()
467510 local args=()
468511 local rm=" "
512+ local background=" "
469513 while [[ $# -gt 0 ]]; do
470514 case " $1 " in
471515 --disk) vm_opts+=(--disk " $2 " ); shift 2 ;;
@@ -476,9 +520,17 @@ _agent_vm_opencode() {
476520 --readonly) vm_opts+=(--readonly); shift ;;
477521 --git-read-only|--git-ro) vm_opts+=(--git-read-only); shift ;;
478522 --rm) rm=1; shift ;;
523+ --port-forward) vm_opts+=(--port-forward " $2 " ); shift 2 ;;
524+ --background) background=1; shift ;;
479525 * ) args+=(" $1 " ); shift ;;
480526 esac
481527 done
528+
529+ if [[ -n " $background " && -n " $rm " ]]; then
530+ echo " Error: --background and --rm cannot be used together" >&2
531+ return 1
532+ fi
533+
482534 local host_dir
483535 host_dir=" $( pwd) "
484536 local vm_name
@@ -489,17 +541,28 @@ _agent_vm_opencode() {
489541
490542 # TODO: add --dangerously-skip-permissions once released
491543 # (waiting on https://github.com/anomalyco/opencode/pull/11833)
492- local exit_code=0
493- limactl shell --tty --workdir " $host_dir " " $vm_name " opencode " ${args[@]} "
494- exit_code=$?
495- [[ -n " $rm " ]] && { echo " Removing VM..." ; _agent_vm_destroy; }
496- return $exit_code
544+ if [[ -n " $background " ]]; then
545+ nohup limactl shell --workdir " $host_dir " " $vm_name " opencode " ${args[@]} " & > /dev/null &
546+ disown
547+ echo " Started opencode in background (pid: $! )"
548+ return 0
549+ elif [[ -n " $rm " ]]; then
550+ limactl shell --tty --workdir " $host_dir " " $vm_name " opencode " ${args[@]} "
551+ exit_code=$?
552+ echo " Removing VM..."
553+ _agent_vm_destroy
554+ return $exit_code
555+ else
556+ limactl shell --tty --workdir " $host_dir " " $vm_name " opencode " ${args[@]} "
557+ return $?
558+ fi
497559}
498560
499561_agent_vm_codex () {
500562 local vm_opts=()
501563 local args=()
502564 local rm=" "
565+ local background=" "
503566 while [[ $# -gt 0 ]]; do
504567 case " $1 " in
505568 --disk) vm_opts+=(--disk " $2 " ); shift 2 ;;
@@ -510,9 +573,17 @@ _agent_vm_codex() {
510573 --readonly) vm_opts+=(--readonly); shift ;;
511574 --git-read-only|--git-ro) vm_opts+=(--git-read-only); shift ;;
512575 --rm) rm=1; shift ;;
576+ --port-forward) vm_opts+=(--port-forward " $2 " ); shift 2 ;;
577+ --background) background=1; shift ;;
513578 * ) args+=(" $1 " ); shift ;;
514579 esac
515580 done
581+
582+ if [[ -n " $background " && -n " $rm " ]]; then
583+ echo " Error: --background and --rm cannot be used together" >&2
584+ return 1
585+ fi
586+
516587 local host_dir
517588 host_dir=" $( pwd) "
518589 local vm_name
@@ -521,11 +592,21 @@ _agent_vm_codex() {
521592 _agent_vm_ensure_running " $vm_name " " $host_dir " " ${vm_opts[@]} " || return 1
522593 _agent_vm_print_resources " $vm_name "
523594
524- local exit_code=0
525- limactl shell --workdir " $host_dir " " $vm_name " codex --full-auto " ${args[@]} "
526- exit_code=$?
527- [[ -n " $rm " ]] && { echo " Removing VM..." ; _agent_vm_destroy; }
528- return $exit_code
595+ if [[ -n " $background " ]]; then
596+ nohup limactl shell --workdir " $host_dir " " $vm_name " codex --full-auto " ${args[@]} " & > /dev/null &
597+ disown
598+ echo " Started codex in background (pid: $! )"
599+ return 0
600+ elif [[ -n " $rm " ]]; then
601+ limactl shell --workdir " $host_dir " " $vm_name " codex --full-auto " ${args[@]} "
602+ exit_code=$?
603+ echo " Removing VM..."
604+ _agent_vm_destroy
605+ return $exit_code
606+ else
607+ limactl shell --workdir " $host_dir " " $vm_name " codex --full-auto " ${args[@]} "
608+ return $?
609+ fi
529610}
530611
531612_agent_vm_shell () {
@@ -540,6 +621,7 @@ _agent_vm_shell() {
540621 --offline) vm_opts+=(--offline); shift ;;
541622 --readonly) vm_opts+=(--readonly); shift ;;
542623 --git-read-only|--git-ro) vm_opts+=(--git-read-only); shift ;;
624+ --port-forward) vm_opts+=(--port-forward " $2 " ); shift 2 ;;
543625 --rm) rm=1; shift ;;
544626 * ) shift ;;
545627 esac
@@ -569,6 +651,7 @@ _agent_vm_run() {
569651 local vm_opts=()
570652 local args=()
571653 local rm=" "
654+ local background=" "
572655 while [[ $# -gt 0 ]]; do
573656 case " $1 " in
574657 --disk) vm_opts+=(--disk " $2 " ); shift 2 ;;
@@ -579,9 +662,16 @@ _agent_vm_run() {
579662 --readonly) vm_opts+=(--readonly); shift ;;
580663 --git-read-only|--git-ro) vm_opts+=(--git-read-only); shift ;;
581664 --rm) rm=1; shift ;;
665+ --port-forward) vm_opts+=(--port-forward " $2 " ); shift 2 ;;
666+ --background) background=1; shift ;;
582667 * ) args+=(" $1 " ); shift ;;
583668 esac
584669 done
670+ if [[ -n " $background " && -n " $rm " ]]; then
671+ echo " Error: --background and --rm cannot be used together" >&2
672+ return 1
673+ fi
674+
585675 if [[ ${# args[@]} -eq 0 ]]; then
586676 echo " Usage: agent-vm run <command> [args]" >&2
587677 return 1
@@ -594,11 +684,21 @@ _agent_vm_run() {
594684 _agent_vm_ensure_running " $vm_name " " $host_dir " " ${vm_opts[@]} " || return 1
595685 _agent_vm_print_resources " $vm_name "
596686
597- local exit_code=0
598- limactl shell --workdir " $host_dir " " $vm_name " " ${args[@]} "
599- exit_code=$?
600- [[ -n " $rm " ]] && { echo " Removing VM..." ; _agent_vm_destroy; }
601- return $exit_code
687+ if [[ -n " $background " ]]; then
688+ nohup limactl shell --workdir " $host_dir " " $vm_name " " ${args[@]} " & > /dev/null &
689+ disown
690+ echo " Started '${args[*]} ' in background (pid: $! )"
691+ return 0
692+ elif [[ -n " $rm " ]]; then
693+ limactl shell --workdir " $host_dir " " $vm_name " " ${args[@]} "
694+ exit_code=$?
695+ echo " Removing VM..."
696+ _agent_vm_destroy
697+ return $exit_code
698+ else
699+ limactl shell --workdir " $host_dir " " $vm_name " " ${args[@]} "
700+ return $?
701+ fi
602702}
603703
604704_agent_vm_stop () {
0 commit comments