diff --git a/.gitignore b/.gitignore index 3cc57d2..5a40802 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ yarn.lock .env .env.local +# Windows debug instance config +scripts/windows-debug/.instance + # opensrc - source code for packages opensrc/ diff --git a/AGENTS.md b/AGENTS.md index 94c2aea..79456dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,6 +66,68 @@ cd cli && cargo fmt -- --check # Check formatting cd cli && cargo clippy # Lint ``` +## Windows Debugging + +A remote Windows Server 2022 EC2 instance is available for debugging Windows-specific issues. It uses AWS Systems Manager (SSM) -- no SSH, no open ports. Commands run via `aws ssm send-command` and return stdout/stderr. + +### Prerequisites + +The instance must be provisioned first (one-time, by a human): + +```bash +./scripts/windows-debug/provision.sh +``` + +Requires: AWS CLI v2 configured with `ec2:*`, `iam:CreateRole`, `iam:AttachRolePolicy`, `ssm:SendCommand`, `ssm:GetCommandInvocation` permissions and a default VPC. + +### Usage + +Start the instance (if stopped): + +```bash +./scripts/windows-debug/start.sh +``` + +Run a command on Windows: + +```bash +./scripts/windows-debug/run.sh "" +``` + +Sync the current git branch and rebuild: + +```bash +./scripts/windows-debug/sync.sh +``` + +Stop the instance when done (avoids cost): + +```bash +./scripts/windows-debug/stop.sh +``` + +### Common Workflows + +Run unit tests on Windows: + +```bash +./scripts/windows-debug/run.sh "cd C:\agent-browser && cargo test --manifest-path cli\Cargo.toml" +``` + +Run e2e tests on Windows: + +```bash +./scripts/windows-debug/run.sh "cd C:\agent-browser && cargo test e2e --manifest-path cli\Cargo.toml -- --ignored --test-threads=1" +``` + +Check bootstrap progress (first boot only): + +```bash +./scripts/windows-debug/run.sh "Get-Content C:\bootstrap.log" +``` + +The repo lives at `C:\agent-browser` on the instance. Rust, Git, and Chrome are pre-installed. The `run.sh` wrapper automatically adds cargo and git to PATH. + ## Source Code Reference diff --git a/cli/src/install.rs b/cli/src/install.rs index 4f288d1..7ad3651 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -16,30 +16,85 @@ pub fn get_browsers_dir() -> PathBuf { pub fn find_installed_chrome() -> Option { let browsers_dir = get_browsers_dir(); + let debug = std::env::var("AGENT_BROWSER_DEBUG").is_ok(); + + if debug { + let _ = writeln!( + io::stderr(), + "[chrome-search] home_dir={:?} browsers_dir={}", + dirs::home_dir(), + browsers_dir.display() + ); + } + if !browsers_dir.exists() { + if debug { + let _ = writeln!(io::stderr(), "[chrome-search] browsers_dir does not exist"); + } return None; } - let mut versions: Vec<_> = fs::read_dir(&browsers_dir) - .ok()? + let entries = match fs::read_dir(&browsers_dir) { + Ok(entries) => entries, + Err(e) => { + let _ = writeln!( + io::stderr(), + "Warning: cannot read Chrome cache directory {}: {}", + browsers_dir.display(), + e + ); + return None; + } + }; + + let mut versions: Vec<_> = entries .filter_map(|e| e.ok()) .filter(|e| { - e.file_name() + let matches = e + .file_name() .to_str() - .is_some_and(|n| n.starts_with("chrome-")) + .is_some_and(|n| n.starts_with("chrome-")); + if debug { + let _ = writeln!( + io::stderr(), + "[chrome-search] entry {:?} matches={}", + e.file_name(), + matches + ); + } + matches }) .collect(); versions.sort_by_key(|b| std::cmp::Reverse(b.file_name())); for entry in versions { - if let Some(bin) = chrome_binary_in_dir(&entry.path()) { - if bin.exists() { + let dir = entry.path(); + if let Some(bin) = chrome_binary_in_dir(&dir) { + let exists = bin.exists(); + if debug { + let _ = writeln!( + io::stderr(), + "[chrome-search] candidate {} exists={}", + bin.display(), + exists + ); + } + if exists { return Some(bin); } + } else if debug { + let _ = writeln!( + io::stderr(), + "[chrome-search] no binary found in {}", + dir.display() + ); } } + if debug { + let _ = writeln!(io::stderr(), "[chrome-search] no installed Chrome found"); + } None } @@ -225,10 +280,14 @@ fn extract_zip(bytes: Vec, dest: &Path) -> Result<(), String> { None => continue, }; let raw_name = enclosed.to_string_lossy().to_string(); + // Strip the top-level "chrome-/" directory from zip entries. + // On Windows, enclosed_name() normalizes paths to backslashes, so we + // must split on either separator. let rel_path = raw_name .strip_prefix("chrome-") - .and_then(|s| s.split_once('/')) - .map(|(_, rest)| rest.to_string()) + .and_then(|s| s.find(['/', '\\']).map(|i| &s[i + 1..])) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) .unwrap_or(raw_name.clone()); if rel_path.is_empty() { diff --git a/cli/src/native/cdp/chrome.rs b/cli/src/native/cdp/chrome.rs index d454e18..801cae2 100644 --- a/cli/src/native/cdp/chrome.rs +++ b/cli/src/native/cdp/chrome.rs @@ -219,9 +219,18 @@ fn build_chrome_args(options: &LaunchOptions) -> Result { pub fn launch_chrome(options: &LaunchOptions) -> Result { let chrome_path = match &options.executable_path { Some(p) => PathBuf::from(p), - None => { - find_chrome().ok_or("Chrome not found. Run `agent-browser install` to download Chrome, or use --executable-path.")? - } + None => find_chrome().ok_or_else(|| { + let cache_dir = crate::install::get_browsers_dir(); + format!( + "Chrome not found. Checked:\n \ + - agent-browser cache: {}\n \ + - System Chrome installations\n \ + - Puppeteer browser cache\n \ + - Playwright browser cache\n\ + Run `agent-browser install` to download Chrome, or use --executable-path.", + cache_dir.display() + ) + })?, }; let max_attempts = 3; @@ -438,6 +447,18 @@ pub fn find_chrome() -> Option { return Some(p); } + // If the cache directory exists but no Chrome was found, warn -- this + // likely means the cache is corrupted or the directory layout is unexpected. + let cache_dir = crate::install::get_browsers_dir(); + if cache_dir.exists() { + let _ = writeln!( + std::io::stderr(), + "Warning: Chrome cache directory exists ({}) but no Chrome binary found inside. \ + Falling back to system Chrome. Run `agent-browser install` to re-download.", + cache_dir.display() + ); + } + // 2. Check system-installed Chrome #[cfg(target_os = "macos")] { @@ -502,7 +523,10 @@ pub fn find_chrome() -> Option { } } - // 3. Fallback: check Playwright's browser cache (for existing installs) + // 3. Fallback: check Puppeteer / Playwright browser caches + if let Some(p) = find_puppeteer_chrome() { + return Some(p); + } if let Some(p) = find_playwright_chromium() { return Some(p); } @@ -684,6 +708,76 @@ fn should_disable_dev_shm(existing_args: &[String]) -> bool { false } +/// Search Puppeteer's browser cache for a Chrome binary. +/// Puppeteer v19+ stores Chrome in ~/.cache/puppeteer/chrome/-/ +fn find_puppeteer_chrome() -> Option { + let mut search_dirs = Vec::new(); + + if let Ok(custom) = std::env::var("PUPPETEER_CACHE_DIR") { + search_dirs.push(PathBuf::from(custom).join("chrome")); + } + + if let Some(home) = dirs::home_dir() { + search_dirs.push(home.join(".cache/puppeteer/chrome")); + } + + for dir in &search_dirs { + if !dir.is_dir() { + continue; + } + if let Ok(entries) = std::fs::read_dir(dir) { + let mut matches: Vec = entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().is_dir()) + .filter_map(|e| { + let candidate = build_puppeteer_binary_path(&e.path()); + if candidate.exists() { + Some(candidate) + } else { + None + } + }) + .collect(); + matches.sort(); + matches.reverse(); + if let Some(p) = matches.into_iter().next() { + return Some(p); + } + } + } + + None +} + +#[cfg(target_os = "linux")] +fn build_puppeteer_binary_path(version_dir: &Path) -> PathBuf { + version_dir.join("chrome-linux64/chrome") +} + +#[cfg(target_os = "macos")] +fn build_puppeteer_binary_path(version_dir: &Path) -> PathBuf { + // Puppeteer uses chrome-mac-arm64 or chrome-mac-x64 depending on arch + let arm = version_dir.join( + "chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ); + if arm.exists() { + return arm; + } + version_dir.join( + "chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ) +} + +#[cfg(target_os = "windows")] +fn build_puppeteer_binary_path(version_dir: &Path) -> PathBuf { + version_dir.join(r"chrome-win64\chrome.exe") +} + +#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] +fn build_puppeteer_binary_path(version_dir: &Path) -> PathBuf { + version_dir.join("chrome") +} + /// Search Playwright's browser cache for a Chromium binary. /// Legacy fallback for users who previously installed Chromium via Playwright. fn find_playwright_chromium() -> Option { diff --git a/scripts/windows-debug/provision.sh b/scripts/windows-debug/provision.sh new file mode 100755 index 0000000..c8ccad0 --- /dev/null +++ b/scripts/windows-debug/provision.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTANCE_FILE="$SCRIPT_DIR/.instance" +NAME_PREFIX="agent-browser-debug" +INSTANCE_TYPE="${INSTANCE_TYPE:-t3.xlarge}" + +if [[ -f "$INSTANCE_FILE" ]]; then + echo "Error: Instance already provisioned. See $INSTANCE_FILE" + echo "Run ./scripts/windows-debug/start.sh to start it, or delete .instance to re-provision." + exit 1 +fi + +REGION=$(aws configure get region 2>/dev/null || echo "") +if [[ -z "$REGION" ]]; then + echo "Error: No AWS region configured. Run: aws configure set region us-east-1" + exit 1 +fi + +echo "Provisioning Windows debug instance in $REGION..." + +# --- IAM Role for SSM --- +ROLE_NAME="${IAM_ROLE_NAME:-$NAME_PREFIX-ssm-role}" +PROFILE_NAME="${INSTANCE_PROFILE_NAME:-$NAME_PREFIX-instance-profile}" + +if aws iam get-instance-profile --instance-profile-name "$PROFILE_NAME" &>/dev/null; then + echo "Instance profile $PROFILE_NAME already exists, reusing." +else + echo "Instance profile $PROFILE_NAME not found. Creating IAM resources..." + + if ! aws iam get-role --role-name "$ROLE_NAME" &>/dev/null; then + echo "Creating IAM role: $ROLE_NAME" + if ! aws iam create-role \ + --role-name "$ROLE_NAME" \ + --assume-role-policy-document '{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole" + }] + }' \ + --no-cli-pager; then + + echo "" + echo "Error: Failed to create IAM role (see error above)." + echo "" + echo "Ask an IAM admin to create the following, then re-run with:" + echo " INSTANCE_PROFILE_NAME= ./scripts/windows-debug/provision.sh" + echo "" + echo "What the admin needs to create:" + echo " 1. IAM Role: $ROLE_NAME" + echo " - Trusted entity: EC2 (ec2.amazonaws.com)" + echo " - Attached policy: AmazonSSMManagedInstanceCore" + echo " 2. Instance Profile: $PROFILE_NAME" + echo " - With the above role added to it" + echo "" + echo "Or run these commands with an account that has iam:CreateRole permission:" + echo "" + echo " aws iam create-role --role-name $ROLE_NAME \\" + echo " --assume-role-policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}'" + echo "" + echo " aws iam attach-role-policy --role-name $ROLE_NAME \\" + echo " --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + echo "" + echo " aws iam create-instance-profile --instance-profile-name $PROFILE_NAME" + echo "" + echo " aws iam add-role-to-instance-profile \\" + echo " --instance-profile-name $PROFILE_NAME --role-name $ROLE_NAME" + exit 1 + fi + + aws iam attach-role-policy \ + --role-name "$ROLE_NAME" \ + --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + else + echo "IAM role $ROLE_NAME already exists." + fi + + echo "Creating instance profile: $PROFILE_NAME" + aws iam create-instance-profile --instance-profile-name "$PROFILE_NAME" --no-cli-pager + aws iam add-role-to-instance-profile \ + --instance-profile-name "$PROFILE_NAME" \ + --role-name "$ROLE_NAME" + echo "Waiting for instance profile propagation..." + sleep 10 +fi + +# --- Security Group (no inbound rules) --- +VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query "Vpcs[0].VpcId" --output text) +if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then + echo "Error: No default VPC found. Create one with: aws ec2 create-default-vpc" + exit 1 +fi + +SG_NAME="$NAME_PREFIX-sg" +SG_ID=$(aws ec2 describe-security-groups \ + --filters "Name=group-name,Values=$SG_NAME" "Name=vpc-id,Values=$VPC_ID" \ + --query "SecurityGroups[0].GroupId" --output text 2>/dev/null || echo "None") + +if [[ "$SG_ID" == "None" || -z "$SG_ID" ]]; then + echo "Creating security group: $SG_NAME" + SG_ID=$(aws ec2 create-security-group \ + --group-name "$SG_NAME" \ + --description "agent-browser Windows debug instance (SSM only, no inbound)" \ + --vpc-id "$VPC_ID" \ + --query "GroupId" --output text) + + # Revoke default egress isn't needed; SSM requires outbound HTTPS. + # No inbound rules -- SSM uses outbound connections only. +else + echo "Security group $SG_NAME ($SG_ID) already exists, reusing." +fi + +# --- AMI (latest Windows Server 2022) --- +AMI_ID=$(aws ssm get-parameter \ + --name "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" \ + --query "Parameter.Value" --output text) +echo "Using AMI: $AMI_ID (Windows Server 2022)" + +# --- UserData bootstrap script --- +USERDATA_FILE=$(mktemp) +trap "rm -f $USERDATA_FILE" EXIT + +cat > "$USERDATA_FILE" <<'PWSH' + +$ErrorActionPreference = "Continue" +$logFile = "C:\bootstrap.log" + +function Log($msg) { + $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + "$ts $msg" | Tee-Object -FilePath $logFile -Append +} + +Log "--- Bootstrap starting ---" + +# Install Git +Log "Installing Git..." +$gitInstaller = "$env:TEMP\git-installer.exe" +Invoke-WebRequest -Uri "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/Git-2.47.1.2-64-bit.exe" -OutFile $gitInstaller +Start-Process -FilePath $gitInstaller -ArgumentList "/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS=`"icons,ext\reg\shellhere,assoc,assoc_sh`"" -Wait +$env:PATH = "C:\Program Files\Git\cmd;$env:PATH" +[Environment]::SetEnvironmentVariable("PATH", "C:\Program Files\Git\cmd;$([Environment]::GetEnvironmentVariable('PATH', 'Machine'))", "Machine") +Log "Git installed: $(git --version)" + +# Install Rust +Log "Installing Rust..." +$rustupInit = "$env:TEMP\rustup-init.exe" +Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile $rustupInit +Start-Process -FilePath $rustupInit -ArgumentList "-y --default-toolchain stable" -Wait +$env:PATH = "$env:USERPROFILE\.cargo\bin;$env:PATH" +[Environment]::SetEnvironmentVariable("PATH", "$env:USERPROFILE\.cargo\bin;$([Environment]::GetEnvironmentVariable('PATH', 'Machine'))", "Machine") +Log "Rust installed: $(rustc --version)" + +# Install MSVC build tools (required for Rust on Windows) +Log "Installing Visual Studio Build Tools..." +$vsInstaller = "$env:TEMP\vs_buildtools.exe" +Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vs_buildtools.exe" -OutFile $vsInstaller +Start-Process -FilePath $vsInstaller -ArgumentList "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" -Wait +Log "Build tools installed." + +# Clone repo +Log "Cloning agent-browser..." +git clone https://github.com/vercel-labs/agent-browser.git C:\agent-browser +Set-Location C:\agent-browser +Log "Repo cloned." + +# Build CLI +Log "Building agent-browser CLI..." +cargo build --release --manifest-path cli\Cargo.toml +Log "Build complete." + +# Install Chrome +Log "Installing Chrome via agent-browser..." +.\cli\target\release\agent-browser.exe install +Log "Chrome installed." + +Log "--- Bootstrap complete ---" + +PWSH + +# --- Launch instance --- +echo "Launching $INSTANCE_TYPE instance..." +INSTANCE_ID=$(aws ec2 run-instances \ + --image-id "$AMI_ID" \ + --instance-type "$INSTANCE_TYPE" \ + --iam-instance-profile "Name=$PROFILE_NAME" \ + --security-group-ids "$SG_ID" \ + --user-data "file://$USERDATA_FILE" \ + --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":80,"VolumeType":"gp3"}}]' \ + --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$NAME_PREFIX}]" \ + --metadata-options "HttpTokens=required" \ + --query "Instances[0].InstanceId" --output text) + +echo "Instance launched: $INSTANCE_ID" + +# Save instance config +cat > "$INSTANCE_FILE" <\"" + echo "" + echo "Examples:" + echo " ./scripts/windows-debug/run.sh \"cd C:\\agent-browser && cargo test\"" + echo " ./scripts/windows-debug/run.sh \"Get-Content C:\\bootstrap.log\"" + echo " ./scripts/windows-debug/run.sh \"cd C:\\agent-browser && cargo test e2e -- --ignored --test-threads=1\"" + exit 1 +fi + +source "$INSTANCE_FILE" +export AWS_DEFAULT_REGION="$REGION" + +COMMAND="$*" + +PARAMS_FILE=$(mktemp) +trap "rm -f $PARAMS_FILE" EXIT + +python3 -c ' +import json, sys +path_setup = "$env:PATH = \"$env:USERPROFILE\\.cargo\\bin;C:\\Program Files\\Git\\cmd;$env:PATH\"" +cmd = path_setup + "\n" + sys.argv[1] +json.dump({"commands": [cmd]}, open(sys.argv[2], "w")) +' "$COMMAND" "$PARAMS_FILE" + +COMMAND_ID=$(aws ssm send-command \ + --instance-ids "$INSTANCE_ID" \ + --document-name "AWS-RunPowerShellScript" \ + --parameters "file://$PARAMS_FILE" \ + --timeout-seconds 3600 \ + --query "Command.CommandId" --output text) + +echo "Command sent (ID: $COMMAND_ID). Waiting..." >&2 + +while true; do + RESULT=$(aws ssm get-command-invocation \ + --command-id "$COMMAND_ID" \ + --instance-id "$INSTANCE_ID" \ + --output json 2>&1) || true + + STATUS=$(echo "$RESULT" | python3 -c " +import sys, json +try: + print(json.loads(sys.stdin.read()).get('Status', 'Unknown')) +except: + print('Pending') +" 2>/dev/null) + + case "$STATUS" in + Success) + echo "$RESULT" | python3 -c " +import sys, json +r = json.loads(sys.stdin.read()) +out = r.get('StandardOutputContent', '').rstrip() +err = r.get('StandardErrorContent', '').rstrip() +if out: + print(out) +if err: + print(err, file=sys.stderr) +" + exit 0 + ;; + Failed|TimedOut|Cancelled) + echo "$RESULT" | python3 -c " +import sys, json +r = json.loads(sys.stdin.read()) +out = r.get('StandardOutputContent', '').rstrip() +err = r.get('StandardErrorContent', '').rstrip() +if out: + print(out) +if err: + print(err, file=sys.stderr) +" + echo "Command $STATUS." >&2 + exit 1 + ;; + *) + sleep 3 + ;; + esac +done diff --git a/scripts/windows-debug/start.sh b/scripts/windows-debug/start.sh new file mode 100755 index 0000000..42dca90 --- /dev/null +++ b/scripts/windows-debug/start.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTANCE_FILE="$SCRIPT_DIR/.instance" + +if [[ ! -f "$INSTANCE_FILE" ]]; then + echo "Error: No instance provisioned. Run ./scripts/windows-debug/provision.sh first." + exit 1 +fi + +source "$INSTANCE_FILE" +export AWS_DEFAULT_REGION="$REGION" + +STATE=$(aws ec2 describe-instances \ + --instance-ids "$INSTANCE_ID" \ + --query "Reservations[0].Instances[0].State.Name" --output text) + +if [[ "$STATE" == "running" ]]; then + echo "Instance $INSTANCE_ID is already running." +else + echo "Starting instance $INSTANCE_ID..." + aws ec2 start-instances --instance-ids "$INSTANCE_ID" --no-cli-pager + echo "Waiting for running state..." + aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" + echo "Instance is running." +fi + +echo "Waiting for SSM agent connectivity..." +for i in $(seq 1 30); do + SSM_STATUS=$(aws ssm describe-instance-information \ + --filters "Key=InstanceIds,Values=$INSTANCE_ID" \ + --query "InstanceInformationList[0].PingStatus" --output text 2>/dev/null || echo "None") + if [[ "$SSM_STATUS" == "Online" ]]; then + echo "SSM agent is online. Ready for commands." + echo " ./scripts/windows-debug/run.sh \"your-command-here\"" + exit 0 + fi + sleep 10 +done + +echo "Warning: SSM agent not online after 5 minutes. The instance may still be booting." +echo "Try again in a minute: ./scripts/windows-debug/run.sh \"hostname\"" diff --git a/scripts/windows-debug/stop.sh b/scripts/windows-debug/stop.sh new file mode 100755 index 0000000..1542b31 --- /dev/null +++ b/scripts/windows-debug/stop.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTANCE_FILE="$SCRIPT_DIR/.instance" + +if [[ ! -f "$INSTANCE_FILE" ]]; then + echo "Error: No instance provisioned. Nothing to stop." + exit 1 +fi + +source "$INSTANCE_FILE" +export AWS_DEFAULT_REGION="$REGION" + +STATE=$(aws ec2 describe-instances \ + --instance-ids "$INSTANCE_ID" \ + --query "Reservations[0].Instances[0].State.Name" --output text) + +if [[ "$STATE" == "stopped" ]]; then + echo "Instance $INSTANCE_ID is already stopped." + exit 0 +fi + +echo "Stopping instance $INSTANCE_ID..." +aws ec2 stop-instances --instance-ids "$INSTANCE_ID" --no-cli-pager +echo "Waiting for stopped state..." +aws ec2 wait instance-stopped --instance-ids "$INSTANCE_ID" +echo "Instance stopped. No compute charges while stopped (storage only: ~$0.64/mo)." diff --git a/scripts/windows-debug/sync.sh b/scripts/windows-debug/sync.sh new file mode 100755 index 0000000..c4a3ea7 --- /dev/null +++ b/scripts/windows-debug/sync.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +RUN="$SCRIPT_DIR/run.sh" + +BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main") +REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "https://github.com/vercel-labs/agent-browser.git") + +echo "Syncing branch '$BRANCH' on Windows instance..." + +"$RUN" " +cd C:\agent-browser +git remote set-url origin '$REMOTE_URL' +git fetch origin +git checkout -B '$BRANCH' 'origin/$BRANCH' +git log -1 --oneline +" + +echo "" +echo "Branch synced. Rebuilding..." + +"$RUN" " +cd C:\agent-browser +cargo build --release --manifest-path cli\Cargo.toml +Write-Host 'Build complete.' +"