Compare commits

...

14 Commits

Author SHA1 Message Date
Nikhil Sonti
a68b31234b fix: limit VM PR checks to build-tools validation 2026-04-22 16:14:03 -07:00
Nikhil Sonti
1c6163149e fix: avoid guest DNS for VM package install 2026-04-22 16:07:46 -07:00
Nikhil Sonti
178cd95839 fix: set VM build DNS in apt command 2026-04-22 15:58:51 -07:00
Nikhil Sonti
3a8a0f8a7f fix: keep arm64 VM recipe simple 2026-04-22 15:52:33 -07:00
Nikhil Sonti
e590b313fe fix: prioritize arm64 build workflows 2026-04-22 15:47:20 -07:00
Nikhil Sonti
6a03275b2a fix: stabilize VM build DNS in CI 2026-04-22 15:43:34 -07:00
Nikhil Sonti
d54a2f3980 fix: address review feedback for PR #785 2026-04-22 15:37:58 -07:00
Nikhil Sonti
0279c0708b chore: remove legacy container packages + workflows 2026-04-22 15:09:16 -07:00
Nikhil Sonti
695c775b87 ci(build-tools): independent build-vm + build-agent workflows 2026-04-22 15:08:11 -07:00
Nikhil Sonti
aedb6634e5 feat(build-tools): emit-manifest + cache:sync 2026-04-22 15:05:28 -07:00
Nikhil Sonti
67153772a0 feat(build-tools): build-tarball script 2026-04-22 15:01:40 -07:00
Nikhil Sonti
29f8cc718f feat(build-tools): build-disk script with virt-customize + zstd 2026-04-22 15:00:52 -07:00
Nikhil Sonti
5adf119c3b feat(build-tools): manifest types + R2 helper 2026-04-22 14:59:30 -07:00
Nikhil Sonti
ab82f4576a feat(build-tools): scaffold package + cache dir helpers 2026-04-22 14:57:21 -07:00
85 changed files with 2198 additions and 4872 deletions

View File

@@ -1,170 +0,0 @@
name: Build Agent Container
on:
workflow_dispatch:
inputs:
agent:
description: Optional agent filter from recipe/agents.json
required: false
type: string
version:
description: Optional dry-run version override for the selected agent
required: false
type: string
publish:
description: Publish artifacts to R2
required: false
default: false
type: boolean
pull_request:
paths:
- .github/workflows/build-agent-container.yml
- packages/browseros-agent/packages/agent-container/**
schedule:
- cron: '0 7 * * 1'
permissions:
contents: read
jobs:
list-matrix:
runs-on: ubuntu-24.04
defaults:
run:
working-directory: packages/browseros-agent
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Typecheck package
run: bun run --filter @browseros/agent-container typecheck
- name: Run package tests
run: bun run --filter @browseros/agent-container test
- name: Compute matrix
id: matrix
env:
INPUT_AGENT: ${{ inputs.agent }}
run: |
set -euo pipefail
args=()
if [ -n "$INPUT_AGENT" ]; then
args+=(--agent "$INPUT_AGENT")
fi
matrix="$(bun run packages/agent-container/scripts/list-matrix.ts "${args[@]}")"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
build:
needs: list-matrix
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
timeout-minutes: 60
defaults:
run:
working-directory: packages/browseros-agent
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.list-matrix.outputs.matrix) }}
env:
ARTIFACT_ROOT: ${{ github.workspace }}/packages/browseros-agent/packages/agent-container/dist/agent-container
OUTPUT_DIR: ${{ github.workspace }}/packages/browseros-agent/packages/agent-container/dist/agent-container/${{ matrix.agent }}/${{ matrix.arch }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Install podman
run: |
sudo apt-get update
sudo apt-get install -y podman
- name: Build tarball
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
cmd=(
bun run --filter @browseros/agent-container build --
--agent "${{ matrix.agent }}"
--arch "${{ matrix.arch }}"
--output-dir "$OUTPUT_DIR"
)
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "$INPUT_VERSION" ]; then
cmd+=(--version "$INPUT_VERSION")
fi
"${cmd[@]}"
- name: Smoke test archive
run: |
set -euo pipefail
result_json="$OUTPUT_DIR/build-result.json"
tarball="$(bun -e "const result = await Bun.file(process.argv[1]).json(); console.log(result.tarballPath)" "$result_json")"
expected_image="$(bun -e 'const result = await Bun.file(process.argv[1]).json(); console.log(result.image + ":" + result.version)' "$result_json")"
expected_fingerprint="$(bun -e "const result = await Bun.file(process.argv[1]).json(); console.log(result.smokeFingerprint)" "$result_json")"
bun run --filter @browseros/agent-container smoke -- \
--tarball "$tarball" \
--expected-image "$expected_image" \
--expected-fingerprint "$expected_fingerprint"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: agent-container-${{ matrix.agent }}-${{ matrix.arch }}
path: ${{ env.ARTIFACT_ROOT }}
publish:
needs: build
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}
runs-on: ubuntu-24.04
defaults:
run:
working-directory: packages/browseros-agent
env:
ARTIFACT_ROOT: ${{ github.workspace }}/packages/browseros-agent/packages/agent-container/dist/agent-container
steps:
- name: Guard recipe source of truth
if: ${{ inputs.version != '' }}
run: |
echo "Refusing to publish a workflow_dispatch version override. Update recipe/agents.json instead." >&2
exit 1
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Download matrix artifacts
uses: actions/download-artifact@v4
with:
pattern: agent-container-*
merge-multiple: true
path: ${{ env.ARTIFACT_ROOT }}
- name: Publish to R2
env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
bun run --filter @browseros/agent-container upload -- \
--artifact-dir "$ARTIFACT_ROOT" \
--update-aggregate

158
.github/workflows/build-agent.yml vendored Normal file
View File

@@ -0,0 +1,158 @@
name: build-agent
on:
workflow_dispatch:
inputs:
agent:
description: "Agent name from bundle.json"
required: true
type: string
default: openclaw
publish:
description: "Upload to R2 and merge manifest slice"
required: false
default: false
type: boolean
pull_request:
paths:
- "packages/browseros-agent/packages/build-tools/**"
- "!packages/browseros-agent/packages/build-tools/scripts/build-disk.ts"
- ".github/workflows/build-agent.yml"
env:
BUN_VERSION: "1.3.6"
PKG_DIR: packages/browseros-agent/packages/build-tools
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- working-directory: packages/browseros-agent
run: bun run --filter @browseros/build-tools typecheck
- working-directory: packages/browseros-agent
run: bun run --filter @browseros/build-tools test
build:
needs: check
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install podman
run: |
sudo apt-get update
sudo apt-get install -y podman
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Build tarball
working-directory: ${{ env.PKG_DIR }}
env:
AGENT: ${{ inputs.agent || 'openclaw' }}
OUT: ${{ github.workspace }}/dist/images
run: bun run build:tarball -- --agent "$AGENT" --arch "${{ matrix.arch }}" --output-dir "$OUT"
- uses: actions/upload-artifact@v4
with:
name: tarball-${{ inputs.agent || 'openclaw' }}-${{ matrix.arch }}
path: dist/images/
retention-days: 7
smoke:
needs: build
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
name: tarball-${{ inputs.agent || 'openclaw' }}-arm64
path: dist/images
- name: Install podman
run: |
sudo apt-get update
sudo apt-get install -y podman
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Smoke test tarball
working-directory: ${{ env.PKG_DIR }}
env:
AGENT: ${{ inputs.agent || 'openclaw' }}
run: |
set -euo pipefail
tarball="$(find "$GITHUB_WORKSPACE/dist/images" -name "${AGENT}-*-arm64.tar.gz" -print -quit)"
if [ -z "$tarball" ]; then
echo "missing arm64 tarball artifact for ${AGENT}" >&2
exit 1
fi
bun run smoke:tarball -- --agent "$AGENT" --arch arm64 --tarball "$tarball"
publish:
needs: [build, smoke]
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}
runs-on: ubuntu-24.04
environment: release
concurrency:
group: r2-manifest-publish
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
pattern: tarball-*
path: dist/images
merge-multiple: true
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Upload tarballs to R2
working-directory: ${{ env.PKG_DIR }}
env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
for file in "$GITHUB_WORKSPACE"/dist/images/*.tar.gz; do
base="$(basename "$file")"
bun run upload -- --file "$file" --key "vm/images/$base" --content-type "application/gzip" --sidecar-sha
done
- name: Merge agent slice into manifest
working-directory: ${{ env.PKG_DIR }}
env:
AGENT: ${{ inputs.agent || 'openclaw' }}
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
mkdir -p dist/images
cp -R "$GITHUB_WORKSPACE"/dist/images/* dist/images/
bun run download -- --key vm/manifest.json --out dist/baseline-manifest.json
bun run emit-manifest -- \
--slice "agents:${AGENT}" \
--dist-dir dist \
--merge-from dist/baseline-manifest.json \
--out dist/manifest.json
bun run upload -- --file dist/manifest.json --key vm/manifest.json --content-type "application/json"

View File

@@ -1,184 +0,0 @@
name: build-vm-container
on:
workflow_dispatch:
inputs:
version:
description: "CalVer identifier (e.g., 2026.04.22-1)"
required: true
type: string
base_image_sha256:
description: "Verify base image locally against this sha256 (debugging only)"
required: false
type: string
publish:
description: "Upload to R2 and update latest.json"
required: false
type: boolean
default: false
pull_request:
paths:
- "packages/browseros-agent/packages/vm-container/**"
- ".github/workflows/build-vm-container.yml"
schedule:
- cron: "0 6 * * 1"
env:
BUN_VERSION: "1.3.6"
PKG_DIR: packages/browseros-agent/packages/vm-container
ARTIFACT_RETENTION_DAYS: 7
jobs:
build:
strategy:
fail-fast: false
# PRs run the x64 lane only until this repo has dependable arm64 runner
# capacity. Manual/scheduled runs still request the native arm64 lane.
matrix: ${{ fromJSON(github.event_name == 'pull_request' && '{"include":[{"arch":"x64","runner":"ubuntu-24.04"}]}' || '{"include":[{"arch":"arm64","runner":"ubuntu-24.04-arm64"},{"arch":"x64","runner":"ubuntu-24.04"}]}') }}
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install libguestfs + qemu-utils + zstd
run: |
sudo apt-get update
sudo apt-get install -y libguestfs-tools qemu-utils zstd
echo "LIBGUESTFS_BACKEND=direct" >> "$GITHUB_ENV"
# Ubuntu 24.04 ships /boot/vmlinuz-* as mode 0600, which breaks
# libguestfs's supermin appliance build when virt-customize runs
# as a non-root user. Loosen to 0644 so supermin can read them.
sudo chmod 0644 /boot/vmlinuz-* || true
sudo chmod 0644 /boot/initrd.img-* || true
# Give the runner user access to /dev/kvm so libguestfs can use
# hardware acceleration instead of slow TCG emulation.
if [ -e /dev/kvm ]; then
sudo chmod 0666 /dev/kvm
fi
# libguestfs 1.54+ prefers `passt` for guest networking, but the
# passt build on GHA ubuntu-24.04 fails under AppArmor when spawned
# from the appliance (exits with status 1, no useful diagnostics).
# Move the binary aside so libguestfs falls back to QEMU's built-in
# user-mode SLIRP networking, which works fine.
if [ -x /usr/bin/passt ]; then
sudo mv /usr/bin/passt /usr/bin/passt.disabled
fi
- name: Install deps
working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Typecheck
working-directory: packages/browseros-agent
run: bun run --filter @browseros/vm-container typecheck
- name: Unit tests
working-directory: packages/browseros-agent
run: bun run --filter @browseros/vm-container test
- name: Determine version
id: version
env:
VERSION_INPUT: ${{ inputs.version || '' }}
run: |
if [ -n "$VERSION_INPUT" ]; then
echo "value=$VERSION_INPUT" >> "$GITHUB_OUTPUT"
else
echo "value=$(date -u +%Y.%m.%d)-${{ github.run_number }}" >> "$GITHUB_OUTPUT"
fi
- name: Build
working-directory: ${{ env.PKG_DIR }}
env:
VERSION_VALUE: ${{ steps.version.outputs.value }}
ARCH_VALUE: ${{ matrix.arch }}
OUTPUT_DIR: ${{ github.workspace }}/dist/vm/${{ matrix.arch }}
BASE_IMAGE_SHA256_INPUT: ${{ inputs.base_image_sha256 || '' }}
run: |
cmd=(
bun run build --
--version "$VERSION_VALUE"
--arch "$ARCH_VALUE"
--output-dir "$OUTPUT_DIR"
)
if [ -n "$BASE_IMAGE_SHA256_INPUT" ]; then
cmd+=(--base-image-sha256 "$BASE_IMAGE_SHA256_INPUT")
fi
"${cmd[@]}"
- name: Dump build log on failure
if: ${{ failure() }}
run: |
LOG_PATH="$GITHUB_WORKSPACE/dist/vm/${{ matrix.arch }}/build-${{ matrix.arch }}.log"
if [ -f "$LOG_PATH" ]; then
echo "::group::$(basename "$LOG_PATH")"
tail -n 200 "$LOG_PATH"
echo "::endgroup::"
else
echo "missing build log: $LOG_PATH"
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: vm-disk-${{ matrix.arch }}
path: dist/vm/${{ matrix.arch }}/
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
smoke:
needs: [build]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
name: vm-disk-x64
path: dist/vm/x64/
- name: Install qemu + zstd + Lima
run: |
sudo apt-get update
sudo apt-get install -y qemu-system-x86 qemu-utils zstd curl
LIMA_VERSION=$(cat "${PKG_DIR}/recipe/LIMA_VERSION.pin")
LIMA_SHA256=$(cat "${PKG_DIR}/recipe/LIMA_LINUX_X86_64_SHA256.pin")
curl -fsSL -o /tmp/lima.tar.gz \
"https://github.com/lima-vm/lima/releases/download/${LIMA_VERSION}/lima-${LIMA_VERSION#v}-Linux-x86_64.tar.gz"
echo "${LIMA_SHA256} /tmp/lima.tar.gz" | sha256sum --check --strict
sudo tar -C /usr/local -xzf /tmp/lima.tar.gz
- name: Install deps
working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Smoke test
working-directory: ${{ env.PKG_DIR }}
run: |
QCOW=$(ls "$GITHUB_WORKSPACE/dist/vm/x64/"browseros-vm-*-x64.qcow2.zst | head -n1)
bun run smoke -- --qcow "$QCOW" --limactl /usr/local/bin/limactl
publish:
needs: [build, smoke]
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}
runs-on: ubuntu-24.04
environment: release
concurrency:
group: vm-publish-${{ inputs.version }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
pattern: vm-disk-*
path: dist/vm/
- name: Install deps
working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Upload to R2
working-directory: ${{ env.PKG_DIR }}
env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
VERSION_INPUT: ${{ inputs.version }}
run: |
bun run upload -- \
--version "$VERSION_INPUT" \
--artifact-dir "$GITHUB_WORKSPACE/dist/vm"

179
.github/workflows/build-vm.yml vendored Normal file
View File

@@ -0,0 +1,179 @@
name: build-vm
on:
workflow_dispatch:
inputs:
version:
description: "VM version (e.g. 2026.04.22)"
required: true
type: string
publish:
description: "Upload to R2 and merge manifest slice"
required: false
default: false
type: boolean
pull_request:
paths:
- "packages/browseros-agent/packages/build-tools/**"
- "!packages/browseros-agent/packages/build-tools/scripts/build-tarball.ts"
- ".github/workflows/build-vm.yml"
env:
BUN_VERSION: "1.3.6"
PKG_DIR: packages/browseros-agent/packages/build-tools
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- working-directory: packages/browseros-agent
run: bun run --filter @browseros/build-tools typecheck
- working-directory: packages/browseros-agent
run: bun run --filter @browseros/build-tools test
build:
needs: check
if: ${{ github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install libguestfs, qemu, and zstd
run: |
sudo apt-get update
sudo apt-get install -y libguestfs-tools qemu-utils zstd
sudo chmod 0644 /boot/vmlinuz-* /boot/initrd.img-* || true
[ -e /dev/kvm ] && sudo chmod 0666 /dev/kvm || true
[ -x /usr/bin/passt ] && sudo mv /usr/bin/passt /usr/bin/passt.disabled || true
echo "LIBGUESTFS_BACKEND=direct" >> "$GITHUB_ENV"
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Validate VM version input
if: ${{ github.event_name == 'workflow_dispatch' }}
working-directory: ${{ env.PKG_DIR }}
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
bundle_version="$(bun -e "const b = await Bun.file('bundle.json').json(); console.log(b.vmVersion)")"
if [ "$VERSION" != "$bundle_version" ]; then
echo "inputs.version ($VERSION) must match bundle.json vmVersion ($bundle_version)" >&2
exit 1
fi
- name: Build disk
working-directory: ${{ env.PKG_DIR }}
env:
VERSION: ${{ inputs.version || format('pr-{0}', github.event.pull_request.number) }}
OUT: ${{ github.workspace }}/dist/vm
run: bun run build:disk -- --version "$VERSION" --arch "${{ matrix.arch }}" --output-dir "$OUT"
- uses: actions/upload-artifact@v4
with:
name: vm-disk-${{ matrix.arch }}
path: dist/vm/
retention-days: 7
smoke:
needs: build
if: ${{ github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
name: vm-disk-arm64
path: dist/vm
- name: Install qemu, zstd, curl, and Lima
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y qemu-system-arm qemu-utils zstd curl
lima_version="$(cat "${PKG_DIR}/recipe/LIMA_VERSION.pin")"
lima_sha256="$(cat "${PKG_DIR}/recipe/LIMA_LINUX_AARCH64_SHA256.pin")"
curl -fsSL -o /tmp/lima.tar.gz \
"https://github.com/lima-vm/lima/releases/download/${lima_version}/lima-${lima_version#v}-Linux-aarch64.tar.gz"
echo "${lima_sha256} /tmp/lima.tar.gz" | sha256sum --check --strict
sudo tar -C /usr/local -xzf /tmp/lima.tar.gz
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Smoke test VM disk
working-directory: ${{ env.PKG_DIR }}
run: |
set -euo pipefail
qcow="$(find "$GITHUB_WORKSPACE/dist/vm" -name 'browseros-vm-*-arm64.qcow2.zst' -print -quit)"
if [ -z "$qcow" ]; then
echo "missing arm64 VM disk artifact" >&2
exit 1
fi
bun run smoke:vm -- --arch arm64 --qcow "$qcow" --limactl /usr/local/bin/limactl
publish:
needs: [build, smoke]
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish == true }}
runs-on: ubuntu-24.04
environment: release
concurrency:
group: r2-manifest-publish
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- uses: actions/download-artifact@v4
with:
pattern: vm-disk-*
path: dist
merge-multiple: true
- working-directory: packages/browseros-agent
run: bun install --frozen-lockfile
- name: Upload VM disks to R2
working-directory: ${{ env.PKG_DIR }}
env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
for file in "$GITHUB_WORKSPACE"/dist/*.qcow2.zst; do
base="$(basename "$file")"
bun run upload -- --file "$file" --key "vm/$base" --content-type "application/zstd" --sidecar-sha
done
- name: Merge VM slice into manifest
working-directory: ${{ env.PKG_DIR }}
env:
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
mkdir -p dist
cp -R "$GITHUB_WORKSPACE"/dist/* dist/
bun run download -- --key vm/manifest.json --out dist/baseline-manifest.json
bun run emit-manifest -- \
--slice vm \
--dist-dir dist \
--merge-from dist/baseline-manifest.json \
--out dist/manifest.json
bun run upload -- --file dist/manifest.json --key vm/manifest.json --content-type "application/json"

View File

@@ -6,12 +6,10 @@ import { PATHS } from '@browseros/shared/constants/paths'
import type { ServerDiscoveryConfig } from '@browseros/shared/types/server-config'
import { logger } from './logger'
const DEV_BROWSEROS_DIR_NAME = '.browseros-dev'
export function getBrowserosDir(): string {
const dirName =
process.env.NODE_ENV === 'development'
? DEV_BROWSEROS_DIR_NAME
? PATHS.DEV_BROWSEROS_DIR_NAME
: PATHS.BROWSEROS_DIR_NAME
return join(homedir(), dirName)
}
@@ -49,6 +47,18 @@ export function getOpenClawDir(): string {
return join(getBrowserosDir(), PATHS.OPENCLAW_DIR_NAME)
}
export function getCacheDir(): string {
return join(getBrowserosDir(), PATHS.CACHE_DIR_NAME)
}
export function getVmCacheDir(): string {
return join(getCacheDir(), 'vm')
}
export function getAgentCacheDir(): string {
return join(getVmCacheDir(), 'images')
}
export function getLazyMonitoringDir(): string {
return join(getBrowserosDir(), 'lazy-monitoring')
}
@@ -86,6 +96,7 @@ export async function ensureBrowserosDir(): Promise<void> {
await mkdir(getBuiltinSkillsDir(), { recursive: true })
await mkdir(getSessionsDir(), { recursive: true })
await mkdir(getLazyMonitoringRunsDir(), { recursive: true })
await mkdir(getAgentCacheDir(), { recursive: true })
}
export async function cleanOldSessions(): Promise<void> {

View File

@@ -8,7 +8,10 @@ import { homedir } from 'node:os'
import { join } from 'node:path'
import { PATHS } from '@browseros/shared/constants/paths'
import {
getAgentCacheDir,
getBrowserosDir,
getCacheDir,
getVmCacheDir,
logDevelopmentBrowserosDir,
} from '../src/lib/browseros-dir'
import { logger } from '../src/lib/logger'
@@ -72,4 +75,34 @@ describe('getBrowserosDir', () => {
logger.info = originalInfo
}
})
it('uses the development cache directory in development', () => {
process.env.NODE_ENV = 'development'
expect(getCacheDir()).toBe(join(homedir(), '.browseros-dev', 'cache'))
})
it('uses the standard cache directory outside development', () => {
process.env.NODE_ENV = 'test'
expect(getCacheDir()).toBe(
join(homedir(), PATHS.BROWSEROS_DIR_NAME, 'cache'),
)
})
it('uses a vm cache directory below cache', () => {
process.env.NODE_ENV = 'development'
expect(getVmCacheDir()).toBe(
join(homedir(), '.browseros-dev', 'cache', 'vm'),
)
})
it('uses an agent image cache directory below vm cache', () => {
process.env.NODE_ENV = 'development'
expect(getAgentCacheDir()).toBe(
join(homedir(), '.browseros-dev', 'cache', 'vm', 'images'),
)
})
})

View File

@@ -216,17 +216,6 @@
"chrome-devtools-mcp": "latest",
},
},
"packages/agent-container": {
"name": "@browseros/agent-container",
"version": "0.0.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"zod": "^3.24.2",
},
"devDependencies": {
"@types/node": "^24.3.3",
},
},
"packages/agent-sdk": {
"name": "@browseros-ai/agent-sdk",
"version": "0.0.7",
@@ -243,6 +232,17 @@
"zod": "^3.x",
},
},
"packages/build-tools": {
"name": "@browseros/build-tools",
"version": "0.0.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"@browseros/shared": "workspace:*",
},
"devDependencies": {
"@types/node": "^24.3.3",
},
},
"packages/cdp-protocol": {
"name": "@browseros/cdp-protocol",
"version": "0.0.1",
@@ -254,18 +254,6 @@
"zod": "^3.24.2",
},
},
"packages/vm-container": {
"name": "@browseros/vm-container",
"version": "0.0.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"@browseros/shared": "workspace:*",
"zod": "^3.24.2",
},
"devDependencies": {
"aws-sdk-client-mock": "^4.1.0",
},
},
},
"trustedDependencies": [
"esbuild",
@@ -486,7 +474,7 @@
"@browseros/agent": ["@browseros/agent@workspace:apps/agent"],
"@browseros/agent-container": ["@browseros/agent-container@workspace:packages/agent-container"],
"@browseros/build-tools": ["@browseros/build-tools@workspace:packages/build-tools"],
"@browseros/cdp-protocol": ["@browseros/cdp-protocol@workspace:packages/cdp-protocol"],
@@ -496,8 +484,6 @@
"@browseros/shared": ["@browseros/shared@workspace:packages/shared"],
"@browseros/vm-container": ["@browseros/vm-container@workspace:packages/vm-container"],
"@bunup/dts": ["@bunup/dts@0.14.49", "", { "dependencies": { "@babel/parser": "^7.28.6", "coffi": "^0.1.37", "oxc-minify": "^0.93.0", "oxc-resolver": "^11.16.2", "oxc-transform": "^0.93.0", "picocolors": "^1.1.1", "std-env": "^3.10.0", "ts-import-resolver": "^0.1.23" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-mAni3Zpf1MLxFDNCNStcxjbgNaSZpygc1qqF66WHMoUs3j+NSNKZPKwex9uLGXazJdE8g9MqQ//1QQcETdwY6Q=="],
"@bunup/shared": ["@bunup/shared@0.16.19", "", { "peerDependencies": { "typescript": "latest" }, "optionalPeers": ["typescript"] }, "sha512-gk0ALbDgEifVtZYRZ/ivvwF53pvOdpfqtLIIK67Q417nvtn/XPGE9NZLbdsowTKy2befmT8eiTxMXeoLyZPibA=="],
@@ -2126,8 +2112,6 @@
"await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
"aws-sdk-client-mock": ["aws-sdk-client-mock@4.1.0", "", { "dependencies": { "@types/sinon": "^17.0.3", "sinon": "^18.0.1", "tslib": "^2.1.0" } }, "sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw=="],
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
@@ -3108,8 +3092,6 @@
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
"just-extend": ["just-extend@6.2.0", "", {}, "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
@@ -3468,8 +3450,6 @@
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
"nise": ["nise@6.1.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^15.1.1", "just-extend": "^6.2.0", "path-to-regexp": "^8.3.0" } }, "sha512-SnRDPDBjxZZoU2n0+gzzLtSvo1OZo7j6jnbXsoh3AFxEGhaFU7ZF0TmefuKERq79wxR2U+MPn7ArW+Tl+clC3A=="],
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
@@ -4522,7 +4502,7 @@
"@browseros/agent/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
"@browseros/agent-container/@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
"@browseros/build-tools/@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
"@browseros/eval/@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
@@ -4530,8 +4510,6 @@
"@browseros/server/@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
"@browseros/vm-container/@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
"@codesandbox/sandpack-react/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
"@devicefarmer/adbkit/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
@@ -4942,10 +4920,6 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"aws-sdk-client-mock/@types/sinon": ["@types/sinon@17.0.4", "", { "dependencies": { "@types/sinonjs__fake-timers": "*" } }, "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew=="],
"aws-sdk-client-mock/sinon": ["sinon@18.0.1", "", { "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.2.0", "nise": "^6.0.0", "supports-color": "^7" } }, "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw=="],
"babel-plugin-macros/cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
"better-auth/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
@@ -5092,8 +5066,6 @@
"multimatch/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"nise/@sinonjs/fake-timers": ["@sinonjs/fake-timers@15.3.2", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw=="],
"node-notifier/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"node-notifier/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
@@ -5254,102 +5226,102 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.23", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.24", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-ini": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fB7FNLH1+VPUs0QL3PLrHW+DD4gKu6daFgWtyq3R0Y0Lx8DLZPvyGAxCZNFBxH+M2xt9KvBJX6USwjuqvitmCQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-50QgHGPQAb2veqFOmTF1A3GsAklLHZXL47KbY35khIkfbXH5PLvqpEc/gOAEBPj/yFxrlgxz/8mqWcWTNxBkwQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.11", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-SKgZY7x6AloLUXO20FJGnkKJ3a6CXzNDt6PYs2yqoPzgU0xKWcUoGGJGEBTsfM5eihKW42lbwp+sXzACLbSsaA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.10", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="],
"@browseros/agent/@types/bun/bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.23", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.24", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-ini": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fB7FNLH1+VPUs0QL3PLrHW+DD4gKu6daFgWtyq3R0Y0Lx8DLZPvyGAxCZNFBxH+M2xt9KvBJX6USwjuqvitmCQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-50QgHGPQAb2veqFOmTF1A3GsAklLHZXL47KbY35khIkfbXH5PLvqpEc/gOAEBPj/yFxrlgxz/8mqWcWTNxBkwQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.11", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-SKgZY7x6AloLUXO20FJGnkKJ3a6CXzNDt6PYs2yqoPzgU0xKWcUoGGJGEBTsfM5eihKW42lbwp+sXzACLbSsaA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.10", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="],
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.23", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w=="],
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.24", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-ini": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw=="],
@@ -5448,100 +5420,6 @@
"@browseros/server/@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.23", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.24", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-ini": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fB7FNLH1+VPUs0QL3PLrHW+DD4gKu6daFgWtyq3R0Y0Lx8DLZPvyGAxCZNFBxH+M2xt9KvBJX6USwjuqvitmCQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-50QgHGPQAb2veqFOmTF1A3GsAklLHZXL47KbY35khIkfbXH5PLvqpEc/gOAEBPj/yFxrlgxz/8mqWcWTNxBkwQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.11", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-SKgZY7x6AloLUXO20FJGnkKJ3a6CXzNDt6PYs2yqoPzgU0xKWcUoGGJGEBTsfM5eihKW42lbwp+sXzACLbSsaA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.10", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="],
"@google/gemini-cli-core/@modelcontextprotocol/sdk/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
"@google/gemini-cli-core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ=="],
@@ -5742,10 +5620,6 @@
"@types/request/form-data/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"aws-sdk-client-mock/sinon/@sinonjs/fake-timers": ["@sinonjs/fake-timers@11.2.2", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw=="],
"aws-sdk-client-mock/sinon/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
"babel-plugin-macros/cosmiconfig/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"boxen/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
@@ -5832,69 +5706,69 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-login": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-login": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/token-providers": "3.1014.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/token-providers": "3.1014.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums/@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums/@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="],
@@ -5960,70 +5834,6 @@
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-login": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/token-providers": "3.1014.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums/@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
"@google/gemini-cli-core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
"@google/gemini-cli-core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
@@ -6068,21 +5878,21 @@
"wxt/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1014.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1014.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/agent-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/build-tools/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
@@ -6100,22 +5910,6 @@
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1014.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@browseros/vm-container/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
"@google/genai/google-auth-library/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"@google/genai/google-auth-library/gaxios/rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@@ -6128,12 +5922,10 @@
"publish-browser-extension/listr2/cli-truncate/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"@browseros/agent-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"@browseros/build-tools/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"@browseros/vm-container/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"@google/genai/google-auth-library/gaxios/rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"@google/genai/google-auth-library/gaxios/rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],

View File

@@ -1,14 +0,0 @@
# Required for `bun run --filter @browseros/agent-container upload`
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
# Optional overrides
R2_PUBLIC_BASE_URL=https://cdn.browseros.com
PODMAN_BIN=podman
# Optional recipe-driven registry auth
# If an agent entry in recipe/agents.json sets `requires_auth.secret`,
# define that env var before running `build`.
# EXAMPLE_REGISTRY_TOKEN=

View File

@@ -1,52 +0,0 @@
# @browseros/agent-container
OCI tarball producer for BrowserOS-bundled agent containers.
This package owns the WS2 pipeline:
- Read the active agent set from `recipe/agents.json`
- Pull the upstream image with `podman`
- Save it as an OCI archive and gzip it
- Smoke-test the archive with `podman load`
- Publish tarballs, checksum sidecars, and manifests to R2
Package env requirements are documented in [.env.sample](./.env.sample).
## Local usage
```bash
cd packages/browseros-agent
# Print the GitHub Actions matrix JSON
bun run --filter @browseros/agent-container list-matrix
# Build one artifact locally
bun run --filter @browseros/agent-container build -- \
--agent openclaw \
--arch arm64 \
--output-dir packages/agent-container/dist/agent-container/openclaw/arm64
# Smoke-test a built tarball
bun run --filter @browseros/agent-container smoke -- \
--tarball packages/agent-container/dist/agent-container/openclaw/arm64/openclaw-2026.4.12-arm64.tar.gz \
--expected-image ghcr.io/openclaw/openclaw:2026.4.12 \
--expected-fingerprint ...
# Upload pre-built artifacts
# Fill these from packages/agent-container/.env.sample
R2_ACCOUNT_ID=... \
R2_ACCESS_KEY_ID=... \
R2_SECRET_ACCESS_KEY=... \
R2_BUCKET=... \
bun run --filter @browseros/agent-container upload -- \
--artifact-dir packages/agent-container/dist/agent-container \
--update-aggregate
```
## Notes
- `recipe/agents.json` is the source of truth for the active set.
- `workflow_dispatch` version overrides are intended for dry runs. Publishing still needs the recipe to be authoritative.
- `src/load.ts` is intentionally stubbed. WS6 fills in the runtime consumer path.
- Private registry auth is recipe-driven: if `requires_auth.secret` is set for an agent, export that env var before running `build`.
- When invoking package scripts with `bun run --filter @browseros/agent-container`, pass artifact paths that are explicit from `packages/browseros-agent`. Filtered scripts run inside the package directory, so bare `dist/...` paths are ambiguous.

View File

@@ -1,32 +0,0 @@
{
"name": "@browseros/agent-container",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "BrowserOS agent container OCI tarball producer",
"exports": {
"./schema": {
"types": "./src/schema/index.ts",
"default": "./src/schema/index.ts"
},
"./load": {
"types": "./src/load.ts",
"default": "./src/load.ts"
}
},
"scripts": {
"build": "bun run scripts/build.ts",
"upload": "bun run scripts/upload.ts",
"smoke": "bun run scripts/smoke.ts",
"list-matrix": "bun run scripts/list-matrix.ts",
"test": "bun test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^24.3.3"
}
}

View File

@@ -1,12 +0,0 @@
{
"schema": "v1",
"agents": [
{
"name": "openclaw",
"image": "ghcr.io/openclaw/openclaw",
"version": "2026.4.12",
"arches": ["amd64", "arm64"],
"publishAs": "openclaw"
}
]
}

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env bun
import { resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { buildTarball } from '../src/build'
import { readAgentsConfig } from '../src/catalog'
import { parseArch } from '../src/schema/arch'
const packageRoot = resolve(import.meta.dir, '..')
const recipePath = resolve(packageRoot, 'recipe', 'agents.json')
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
agent: { type: 'string' },
version: { type: 'string' },
arch: { type: 'string' },
'output-dir': { type: 'string' },
help: { type: 'boolean', short: 'h' },
},
})
if (values.help) {
console.log(
'Usage: bun run build -- --agent <name> --arch <amd64|arm64> --output-dir <path> [--version <override>]',
)
process.exit(0)
}
if (!values.agent || !values.arch || !values['output-dir']) {
throw new Error('--agent, --arch, and --output-dir are required')
}
const config = await readAgentsConfig(recipePath)
const selected = config.agents.find((agent) => agent.name === values.agent)
if (!selected) {
throw new Error(`unknown agent: ${values.agent}`)
}
const result = await buildTarball({
agent: {
...selected,
version: values.version ?? selected.version,
},
arch: parseArch(values.arch),
outputDir: values['output-dir'],
recipePath,
})
console.log(JSON.stringify(result, null, 2))

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env bun
import { resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { expandMatrix, readAgentsConfig } from '../src/catalog'
const packageRoot = resolve(import.meta.dir, '..')
const recipePath = resolve(packageRoot, 'recipe', 'agents.json')
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
agent: { type: 'string' },
help: { type: 'boolean', short: 'h' },
},
})
if (values.help) {
console.log('Usage: bun run list-matrix [--agent <name>]')
process.exit(0)
}
const config = await readAgentsConfig(recipePath)
const include = expandMatrix(config, { agent: values.agent })
if (include.length === 0) {
throw new Error(
values.agent
? `no agents matched filter: ${values.agent}`
: 'recipe/agents.json produced an empty matrix',
)
}
console.log(JSON.stringify({ include }))

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bun
import { parseArgs } from 'node:util'
import { roundTripPodmanLoad } from '../src/smoke'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
tarball: { type: 'string' },
'expected-image': { type: 'string' },
'expected-image-id': { type: 'string' },
'expected-fingerprint': { type: 'string' },
'expected-digest': { type: 'string' },
help: { type: 'boolean', short: 'h' },
},
})
if (values.help) {
console.log(
'Usage: bun run smoke -- --tarball <path> --expected-image <ref> [--expected-fingerprint <sha256-hex>] [--expected-image-id <sha256:...> | --expected-digest <sha256:...>]',
)
process.exit(0)
}
const expectedImageId = values['expected-image-id'] ?? values['expected-digest']
if (
!values.tarball ||
!values['expected-image'] ||
(!expectedImageId && !values['expected-fingerprint'])
) {
throw new Error(
'--tarball, --expected-image, and one verification flag are required',
)
}
await roundTripPodmanLoad({
tarballPath: values.tarball,
expectedImage: values['expected-image'],
expectedImageId,
expectedSmokeFingerprint: values['expected-fingerprint'],
})

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env bun
import { readdir } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { loadBuildResult } from '../src/build'
import { publishAgents } from '../src/publish'
async function findBuildResultPaths(root: string): Promise<string[]> {
const entries = await readdir(root, { withFileTypes: true })
const paths: string[] = []
for (const entry of entries) {
const path = join(root, entry.name)
if (entry.isDirectory()) {
paths.push(...(await findBuildResultPaths(path)))
continue
}
if (entry.isFile() && entry.name === 'build-result.json') {
paths.push(path)
}
}
return paths.sort()
}
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
'artifact-dir': { type: 'string' },
'update-aggregate': { type: 'boolean' },
help: { type: 'boolean', short: 'h' },
},
})
if (values.help) {
console.log(
'Usage: bun run upload -- --artifact-dir <path> [--update-aggregate]',
)
process.exit(0)
}
if (!values['artifact-dir']) {
throw new Error('--artifact-dir is required')
}
const artifactDir = resolve(values['artifact-dir'])
const buildResultPaths = await findBuildResultPaths(artifactDir)
if (buildResultPaths.length === 0) {
throw new Error(`no build-result.json files found under ${artifactDir}`)
}
const buildResults = await Promise.all(
buildResultPaths.map((path) => loadBuildResult(path)),
)
await publishAgents({
buildResults,
updateAggregate: Boolean(values['update-aggregate']),
})

View File

@@ -1,470 +0,0 @@
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import { access, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve } from 'node:path'
import { type AgentEntry, publishNameForAgent } from './catalog'
import type { ContainerArch } from './schema/arch'
const PODMAN_BIN = process.env.PODMAN_BIN ?? 'podman'
interface PodmanCommandResult {
stdout: string
stderr: string
}
interface PodmanInspectShape {
Id?: string
Digest?: string
RepoDigests?: string[]
Architecture?: string
Os?: string
Config?: unknown
RootFS?: unknown
}
interface PodmanImageMetadata {
imageId: string
sourceOciDigest: string
smokeFingerprint: string
}
interface RepoDigestCount {
digest: string
count: number
}
export interface BuildOptions {
agent: AgentEntry
arch: ContainerArch
outputDir: string
recipePath?: string
builtBy?: string
}
export interface BuildResult {
name: string
publishAs: string
image: string
version: string
arch: ContainerArch
sourceOciDigest: string
imageId: string
smokeFingerprint: string
filename: string
tarballPath: string
tarballShaPath: string
compressedSha256: string
compressedSizeBytes: number
uncompressedSha256: string
uncompressedSizeBytes: number
podmanVersion: string
builtAt: string
builtBy: string
gitSha: string
gitDirty: boolean
configSha256: string
}
function stableJson(value: unknown): string {
if (Array.isArray(value)) {
return `[${value.map((entry) => stableJson(entry)).join(',')}]`
}
if (value && typeof value === 'object') {
const entries = Object.entries(value as Record<string, unknown>).sort(
([left], [right]) => left.localeCompare(right),
)
return `{${entries
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
.join(',')}}`
}
return JSON.stringify(value)
}
function smokeFingerprintForInspect(inspected: PodmanInspectShape): string {
const payload = stableJson({
Architecture: inspected.Architecture ?? '',
Os: inspected.Os ?? '',
Config: inspected.Config ?? null,
RootFS: inspected.RootFS ?? null,
})
return createHash('sha256').update(payload).digest('hex')
}
function normalizeSha256Like(value: string): string {
const trimmed = value.trim()
if (/^sha256:[a-f0-9]{64}$/.test(trimmed)) {
return trimmed
}
if (/^[a-f0-9]{64}$/.test(trimmed)) {
return `sha256:${trimmed}`
}
throw new Error(`unexpected sha256-like value: ${value}`)
}
function selectSourceOciDigest(
platformDigest: string,
repoDigests: string[],
): string {
const counts = new Map<string, number>()
for (const digest of repoDigests) {
counts.set(digest, (counts.get(digest) ?? 0) + 1)
}
const candidates: RepoDigestCount[] = [...counts.entries()]
.filter(([digest]) => digest !== platformDigest)
.map(([digest, count]) => ({ digest, count }))
.sort(
(left, right) =>
right.count - left.count || left.digest.localeCompare(right.digest),
)
const [firstCandidate, secondCandidate] = candidates
if (!firstCandidate) {
return platformDigest
}
if (!secondCandidate || firstCandidate.count > secondCandidate.count) {
return firstCandidate.digest
}
throw new Error(
`ambiguous source OCI digest for ${platformDigest}: ${candidates
.map((candidate) => `${candidate.digest} (${candidate.count})`)
.join(', ')}`,
)
}
async function runPodman(
args: string[],
options: { stdin?: string } = {},
): Promise<PodmanCommandResult> {
const proc = Bun.spawn([PODMAN_BIN, ...args], {
stdin: options.stdin ? Buffer.from(`${options.stdin}\n`) : undefined,
stdout: 'pipe',
stderr: 'pipe',
})
const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
])
const exitCode = await proc.exited
if (exitCode !== 0) {
throw new Error(
`podman ${args.join(' ')} exited ${exitCode}\n${stderr.trim() || stdout.trim()}`,
)
}
return { stdout, stderr }
}
async function runCommand(command: string[]): Promise<string> {
const proc = Bun.spawn(command, {
stdout: 'pipe',
stderr: 'pipe',
})
const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
])
const exitCode = await proc.exited
if (exitCode !== 0) {
throw new Error(
`${command.join(' ')} exited ${exitCode}\n${stderr.trim() || stdout.trim()}`,
)
}
return stdout.trim()
}
async function sha256OfFile(path: string): Promise<string> {
const hash = createHash('sha256')
const stream = createReadStream(path)
for await (const chunk of stream) {
hash.update(chunk)
}
return hash.digest('hex')
}
async function gzipArchive(tarPath: string): Promise<void> {
const proc = Bun.spawn(['gzip', '-9', '-f', '-k', tarPath], {
stdout: 'pipe',
stderr: 'pipe',
})
const stderr = await new Response(proc.stderr).text()
const exitCode = await proc.exited
if (exitCode !== 0) {
throw new Error(`gzip exited ${exitCode}\n${stderr.trim()}`)
}
}
async function gitSha(): Promise<string> {
return runCommand(['git', 'rev-parse', 'HEAD'])
}
async function gitDirty(): Promise<boolean> {
const stdout = await runCommand(['git', 'status', '--short'])
return stdout.length > 0
}
function recipePathForPackage(): string {
return resolve(import.meta.dir, '..', 'recipe', 'agents.json')
}
function imageRefForBuild(options: BuildOptions): string {
return `${options.agent.image}:${options.agent.version}`
}
function builtByForBuild(explicitBuiltBy?: string): string {
if (explicitBuiltBy) {
return explicitBuiltBy
}
const workflowRef = process.env.GITHUB_WORKFLOW_REF?.trim()
if (workflowRef) {
return workflowRef
}
const workflow = process.env.GITHUB_WORKFLOW?.trim()
const ref = process.env.GITHUB_REF?.trim()
if (workflow && ref) {
return `${workflow}@${ref}`
}
const user = process.env.USER ?? process.env.LOGNAME ?? 'unknown'
return `local:${user}`
}
export function registryForImage(image: string): string {
const firstSegment = image.split('/')[0]
if (
!firstSegment ||
(!firstSegment.includes('.') &&
!firstSegment.includes(':') &&
firstSegment !== 'localhost')
) {
return 'docker.io'
}
return firstSegment
}
async function podmanVersion(): Promise<string> {
const { stdout } = await runPodman(['--version'])
return stdout.trim()
}
async function podmanLogin(options: {
registry: string
username: string
password: string
}): Promise<void> {
await runPodman(
[
'login',
'--username',
options.username,
'--password-stdin',
options.registry,
],
{ stdin: options.password },
)
}
async function podmanPull(
imageRef: string,
arch: ContainerArch,
): Promise<void> {
await runPodman([
'pull',
'--quiet',
'--os',
'linux',
'--arch',
arch,
imageRef,
])
}
export async function podmanInspectImage(
imageRef: string,
): Promise<PodmanImageMetadata> {
const { stdout } = await runPodman([
'inspect',
'--type',
'image',
'--format',
'{{json .}}',
imageRef,
])
const inspected = JSON.parse(stdout.trim()) as PodmanInspectShape
const imageId = normalizeSha256Like(inspected.Id ?? '')
const platformDigest = normalizeSha256Like(inspected.Digest ?? imageId)
const repoDigests = (inspected.RepoDigests ?? [])
.map((entry) => entry.split('@')[1] ?? '')
.filter(Boolean)
.map((entry) => normalizeSha256Like(entry))
const sourceOciDigest = selectSourceOciDigest(platformDigest, repoDigests)
return {
imageId,
sourceOciDigest,
smokeFingerprint: smokeFingerprintForInspect(inspected),
}
}
async function podmanSaveOci(options: {
imageRef: string
outPath: string
}): Promise<void> {
await runPodman([
'save',
'--format',
'oci-archive',
'--output',
options.outPath,
options.imageRef,
])
}
export async function podmanLoadArchive(tarballPath: string): Promise<void> {
await runPodman(['load', '--input', tarballPath])
}
export async function podmanRemoveImage(imageRef: string): Promise<void> {
await runPodman(['rmi', '-f', imageRef])
}
async function maybeLoginForAgent(options: BuildOptions): Promise<void> {
const auth = options.agent.requires_auth
if (!auth) {
return
}
const password = process.env[auth.secret]?.trim()
if (!password) {
throw new Error(`missing registry credential env var: ${auth.secret}`)
}
await podmanLogin({
registry: registryForImage(options.agent.image),
username: auth.username ?? 'oauth2accesstoken',
password,
})
}
export async function buildTarball(
options: BuildOptions,
): Promise<BuildResult> {
const imageRef = imageRefForBuild(options)
const publishAs = publishNameForAgent(options.agent)
const outputDir = resolve(options.outputDir)
const recipePath = resolve(options.recipePath ?? recipePathForPackage())
const baseName = `${publishAs}-${options.agent.version}-${options.arch}.tar`
const tarPath = join(outputDir, baseName)
const tarballPath = `${tarPath}.gz`
const tarballShaPath = `${tarballPath}.sha256`
const buildResultPath = join(outputDir, 'build-result.json')
await mkdir(outputDir, { recursive: true })
await Promise.all([
rm(tarPath, { force: true }),
rm(tarballPath, { force: true }),
rm(tarballShaPath, { force: true }),
rm(buildResultPath, { force: true }),
])
const [gitShaValue, gitDirtyValue, configSha256, podmanVersionValue] =
await Promise.all([
gitSha(),
gitDirty(),
sha256OfFile(recipePath),
podmanVersion(),
])
const builtAt = new Date().toISOString()
const builtBy = builtByForBuild(options.builtBy)
await maybeLoginForAgent(options)
await podmanPull(imageRef, options.arch)
const inspection = await podmanInspectImage(imageRef)
await podmanSaveOci({ imageRef, outPath: tarPath })
await gzipArchive(tarPath)
const [
compressedSha256,
uncompressedSha256,
compressedStats,
uncompressedStats,
] = await Promise.all([
sha256OfFile(tarballPath),
sha256OfFile(tarPath),
stat(tarballPath),
stat(tarPath),
])
const filename = basename(tarballPath)
await writeFile(tarballShaPath, `${compressedSha256} ${filename}\n`, 'utf8')
await rm(tarPath, { force: true })
const result: BuildResult = {
name: options.agent.name,
publishAs,
image: options.agent.image,
version: options.agent.version,
arch: options.arch,
sourceOciDigest: inspection.sourceOciDigest,
imageId: inspection.imageId,
smokeFingerprint: inspection.smokeFingerprint,
filename,
tarballPath,
tarballShaPath,
compressedSha256,
compressedSizeBytes: compressedStats.size,
uncompressedSha256,
uncompressedSizeBytes: uncompressedStats.size,
podmanVersion: podmanVersionValue,
builtAt,
builtBy,
gitSha: gitShaValue,
gitDirty: gitDirtyValue,
configSha256,
}
await writeFile(
buildResultPath,
`${JSON.stringify(result, null, 2)}\n`,
'utf8',
)
return result
}
export async function loadBuildResult(path: string): Promise<BuildResult> {
const raw = await readFile(path, 'utf8')
const result = JSON.parse(raw) as BuildResult
const resultDir = dirname(path)
const tarballPath = (await pathExists(result.tarballPath))
? result.tarballPath
: join(resultDir, result.filename)
const tarballShaPath = (await pathExists(result.tarballShaPath))
? result.tarballShaPath
: `${tarballPath}.sha256`
return {
...result,
tarballPath,
tarballShaPath,
}
}
async function pathExists(path: string): Promise<boolean> {
try {
await access(path)
return true
} catch {
return false
}
}

View File

@@ -1,94 +0,0 @@
import { readFile } from 'node:fs/promises'
import { z } from 'zod'
import { ARCHES, type ContainerArch } from './schema/arch'
export const agentEntrySchema = z.object({
name: z.string().regex(/^[a-z0-9-]+$/),
image: z.string().min(1),
version: z.string().min(1),
arches: z.array(z.enum(ARCHES)).min(1),
publishAs: z
.string()
.regex(/^[a-z0-9-]+$/)
.optional(),
requires_auth: z
.object({
secret: z.string().min(1),
username: z.string().min(1).optional(),
})
.optional(),
})
export const agentsConfigSchema = z
.object({
schema: z.literal('v1'),
agents: z.array(agentEntrySchema).min(1),
})
.superRefine((config, ctx) => {
const seen = new Set<string>()
for (const [index, agent] of config.agents.entries()) {
if (seen.has(agent.name)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['agents', index, 'name'],
message: `duplicate agent name: ${agent.name}`,
})
}
seen.add(agent.name)
}
})
export type AgentEntry = z.infer<typeof agentEntrySchema>
export type AgentsConfig = z.infer<typeof agentsConfigSchema>
export interface MatrixEntry {
agent: string
image: string
version: string
arch: ContainerArch
publishAs: string
}
export function publishNameForAgent(agent: AgentEntry): string {
return agent.publishAs ?? agent.name
}
export async function readAgentsConfig(path: string): Promise<AgentsConfig> {
const raw = await readFile(path, 'utf8')
const parsed = JSON.parse(raw) as unknown
return agentsConfigSchema.parse(parsed)
}
export function expandMatrix(
config: AgentsConfig,
filter: { agent?: string } = {},
): MatrixEntry[] {
const entries: MatrixEntry[] = []
for (const agent of config.agents) {
if (filter.agent && agent.name !== filter.agent) {
continue
}
for (const arch of agent.arches) {
entries.push({
agent: agent.name,
image: agent.image,
version: agent.version,
arch,
publishAs: publishNameForAgent(agent),
})
}
}
return entries.sort((left, right) => {
const byAgent = left.agent.localeCompare(right.agent)
if (byAgent !== 0) {
return byAgent
}
return left.arch.localeCompare(right.arch)
})
}

View File

@@ -1,39 +0,0 @@
import type {
AgentArtifact,
AgentManifest,
AggregateManifest,
ContainerArch,
} from './schema'
export async function fetchAggregateManifest(): Promise<AggregateManifest> {
throw new Error('fetchAggregateManifest: implemented in WS6')
}
export async function fetchAgentManifest(
_agent: string,
_version: string,
): Promise<AgentManifest> {
throw new Error('fetchAgentManifest: implemented in WS6')
}
export async function verifySha256(
_path: string,
_expectedSha256: string,
): Promise<void> {
throw new Error('verifySha256: implemented in WS6')
}
export async function findStagedTarball(
_name: string,
_version: string,
_arch: ContainerArch,
): Promise<string> {
throw new Error('findStagedTarball: implemented in WS6')
}
export async function loadTarball(
_artifact: AgentArtifact,
_destinationPath: string,
): Promise<void> {
throw new Error('loadTarball: implemented in WS6')
}

View File

@@ -1,439 +0,0 @@
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import {
DeleteObjectCommand,
GetObjectCommand,
PutObjectCommand,
S3Client,
type S3ClientConfig,
} from '@aws-sdk/client-s3'
import type { BuildResult } from './build'
import { ARCHES } from './schema/arch'
import {
type AgentManifest,
type AggregateEntry,
type AggregateManifest,
agentManifestSchema,
aggregateManifestSchema,
} from './schema/manifest'
import {
keyForAggregateManifest,
keyForSha,
keyForTarball,
keyForVersionManifest,
} from './schema/r2-keys'
const CDN_BASE_URL =
process.env.R2_PUBLIC_BASE_URL ?? 'https://cdn.browseros.com'
const JSON_CONTENT_TYPE = 'application/json; charset=utf-8'
const SHA_CONTENT_TYPE = 'text/plain; charset=utf-8'
export interface PublishOptions {
buildResults: BuildResult[]
updateAggregate: boolean
bucket?: string
cdnBaseURL?: string
client?: S3Client
now?: () => Date
}
interface ResultGroup {
name: string
publishAs: string
image: string
version: string
sourceOciDigest: string
podmanVersions: string[]
gitSha: string
gitDirty: boolean
configSha256: string
builtBy: string
results: BuildResult[]
}
function requiredEnv(name: string): string {
const value = process.env[name]?.trim()
if (!value) {
throw new Error(`missing required env var: ${name}`)
}
return value
}
function createR2Client(): S3Client {
const config: S3ClientConfig = {
region: 'auto',
endpoint: `https://${requiredEnv('R2_ACCOUNT_ID')}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: requiredEnv('R2_ACCESS_KEY_ID'),
secretAccessKey: requiredEnv('R2_SECRET_ACCESS_KEY'),
},
}
return new S3Client(config)
}
function getBucket(): string {
return requiredEnv('R2_BUCKET')
}
async function uploadFile(
client: S3Client,
bucket: string,
key: string,
path: string,
contentType = 'application/gzip',
): Promise<void> {
const { size } = await stat(path)
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: createReadStream(path),
ContentLength: size,
ContentType: contentType,
}),
)
}
async function uploadBody(
client: S3Client,
bucket: string,
key: string,
body: string | Uint8Array,
contentType = JSON_CONTENT_TYPE,
): Promise<void> {
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType,
}),
)
}
async function deleteObject(
client: S3Client,
bucket: string,
key: string,
): Promise<void> {
await client.send(
new DeleteObjectCommand({
Bucket: bucket,
Key: key,
}),
)
}
function keyForGroup(name: string, version: string): string {
return `${name}:${version}`
}
function compareByArch(left: BuildResult, right: BuildResult): number {
return ARCHES.indexOf(left.arch) - ARCHES.indexOf(right.arch)
}
function cdnUrl(baseUrl: string, key: string): string {
return `${baseUrl.replace(/\/+$/, '')}/${key}`
}
function createManifestForGroup(
group: ResultGroup,
builtAt: string,
cdnBaseURL: string,
): AgentManifest {
return agentManifestSchema.parse({
name: group.name,
schema: 'v1',
build: {
git_sha: group.gitSha,
git_dirty: group.gitDirty,
built_at: builtAt,
built_by: group.builtBy,
config_sha256: group.configSha256,
podman_versions: group.podmanVersions,
},
source: {
image: group.image,
version: group.version,
oci_digest: group.sourceOciDigest,
},
artifacts: [...group.results].sort(compareByArch).map((result) => {
const key = keyForTarball(
result.name,
result.version,
result.arch,
result.publishAs,
)
return {
arch: result.arch,
filename: result.filename,
format: 'oci-archive+gzip',
compressed_sha256: result.compressedSha256,
compressed_size_bytes: result.compressedSizeBytes,
uncompressed_sha256: result.uncompressedSha256,
uncompressed_size_bytes: result.uncompressedSizeBytes,
url: cdnUrl(cdnBaseURL, key),
}
}),
})
}
function mergeAggregateEntries(
existing: AggregateEntry[],
nextEntries: AggregateEntry[],
builtAt: string,
builtBy: string,
): AggregateManifest {
const merged = new Map<string, AggregateEntry>()
for (const entry of existing) {
merged.set(entry.name, entry)
}
for (const entry of nextEntries) {
merged.set(entry.name, entry)
}
return aggregateManifestSchema.parse({
schema: 'v1',
built_at: builtAt,
built_by: builtBy,
agents: [...merged.values()].sort((left, right) =>
left.name.localeCompare(right.name),
),
})
}
function buildAggregateEntries(
groups: ResultGroup[],
cdnBaseURL: string,
): AggregateEntry[] {
return groups
.map((group) => ({
name: group.name,
version: group.version,
oci_digest: group.sourceOciDigest,
manifest_url: cdnUrl(
cdnBaseURL,
keyForVersionManifest(group.name, group.version),
),
}))
.sort((left, right) => left.name.localeCompare(right.name))
}
function aggregateBuiltBy(groups: ResultGroup[]): string {
const builtByValues = [
...new Set(groups.map((group) => group.builtBy)),
].sort()
return builtByValues.join(', ')
}
function buildGroup(results: BuildResult[]): ResultGroup {
const [firstResult, ...rest] = results
if (!firstResult) {
throw new Error('cannot publish an empty build result group')
}
for (const result of rest) {
if (result.name !== firstResult.name) {
throw new Error('mixed agent names in publish group')
}
if (result.publishAs !== firstResult.publishAs) {
throw new Error('mixed publishAs values in publish group')
}
if (result.image !== firstResult.image) {
throw new Error('mixed source images in publish group')
}
if (result.version !== firstResult.version) {
throw new Error('mixed versions in publish group')
}
if (result.sourceOciDigest !== firstResult.sourceOciDigest) {
throw new Error('mixed source OCI digests in publish group')
}
if (
result.gitSha !== firstResult.gitSha ||
result.gitDirty !== firstResult.gitDirty
) {
throw new Error('mixed git metadata in publish group')
}
if (result.configSha256 !== firstResult.configSha256) {
throw new Error('mixed recipe config hashes in publish group')
}
if (result.builtBy !== firstResult.builtBy) {
throw new Error('mixed build provenance in publish group')
}
}
const podmanVersions = [
...new Set(results.map((result) => result.podmanVersion)),
].sort()
return {
name: firstResult.name,
publishAs: firstResult.publishAs,
image: firstResult.image,
version: firstResult.version,
sourceOciDigest: firstResult.sourceOciDigest,
podmanVersions,
gitSha: firstResult.gitSha,
gitDirty: firstResult.gitDirty,
configSha256: firstResult.configSha256,
builtBy: firstResult.builtBy,
results: [...results].sort(compareByArch),
}
}
function groupByAgentVersion(buildResults: BuildResult[]): ResultGroup[] {
const grouped = new Map<string, BuildResult[]>()
for (const result of buildResults) {
const key = keyForGroup(result.name, result.version)
const existing = grouped.get(key)
if (existing) {
existing.push(result)
continue
}
grouped.set(key, [result])
}
return [...grouped.values()]
.map((results) => buildGroup(results))
.sort((left, right) => left.name.localeCompare(right.name))
}
async function readBodyAsString(body: unknown): Promise<string> {
const withTransform = body as {
transformToByteArray?: () => Promise<Uint8Array>
}
if (!withTransform?.transformToByteArray) {
throw new Error('R2 response body is not readable')
}
const bytes = await withTransform.transformToByteArray()
return new TextDecoder().decode(bytes)
}
async function readExistingAggregateEntries(
client: S3Client,
bucket: string,
): Promise<AggregateEntry[]> {
try {
const response = await client.send(
new GetObjectCommand({
Bucket: bucket,
Key: keyForAggregateManifest(),
}),
)
const body = await readBodyAsString(response.Body)
const parsed = aggregateManifestSchema.parse(JSON.parse(body))
return parsed.agents
} catch (error) {
const maybeError = error as {
name?: string
$metadata?: { httpStatusCode?: number }
}
if (
maybeError?.name === 'NoSuchKey' ||
maybeError?.$metadata?.httpStatusCode === 404
) {
return []
}
throw error
}
}
async function rollbackKeys(
client: S3Client,
bucket: string,
uploadedKeys: string[],
): Promise<void> {
await Promise.allSettled(
[...uploadedKeys].reverse().map((key) => deleteObject(client, bucket, key)),
)
}
export async function publishAgents(options: PublishOptions): Promise<void> {
if (options.buildResults.length === 0) {
throw new Error('buildResults must not be empty')
}
const client = options.client ?? createR2Client()
const bucket = options.bucket ?? getBucket()
const cdnBaseURL = options.cdnBaseURL ?? CDN_BASE_URL
const now = options.now ?? (() => new Date())
const uploadedKeys: string[] = []
try {
const groups = groupByAgentVersion(options.buildResults)
const builtAt = now().toISOString()
for (const group of groups) {
for (const result of group.results) {
const tarKey = keyForTarball(
result.name,
result.version,
result.arch,
result.publishAs,
)
const shaKey = keyForSha(
result.name,
result.version,
result.arch,
result.publishAs,
)
await uploadFile(client, bucket, tarKey, result.tarballPath)
uploadedKeys.push(tarKey)
await uploadFile(
client,
bucket,
shaKey,
result.tarballShaPath,
SHA_CONTENT_TYPE,
)
uploadedKeys.push(shaKey)
}
const manifest = createManifestForGroup(group, builtAt, cdnBaseURL)
const manifestKey = keyForVersionManifest(group.name, group.version)
await uploadBody(
client,
bucket,
manifestKey,
`${JSON.stringify(manifest, null, 2)}\n`,
JSON_CONTENT_TYPE,
)
uploadedKeys.push(manifestKey)
}
if (options.updateAggregate) {
const existingEntries = await readExistingAggregateEntries(client, bucket)
const aggregate = mergeAggregateEntries(
existingEntries,
buildAggregateEntries(groups, cdnBaseURL),
builtAt,
aggregateBuiltBy(groups),
)
const aggregateKey = keyForAggregateManifest()
await uploadBody(
client,
bucket,
aggregateKey,
`${JSON.stringify(aggregate, null, 2)}\n`,
JSON_CONTENT_TYPE,
)
uploadedKeys.push(aggregateKey)
}
} catch (error) {
await rollbackKeys(client, bucket, uploadedKeys)
throw error
} finally {
if (!options.client) {
client.destroy()
}
}
}

View File

@@ -1,11 +0,0 @@
export const ARCHES = ['amd64', 'arm64'] as const
export type ContainerArch = (typeof ARCHES)[number]
export function parseArch(value: string): ContainerArch {
if (value === 'amd64' || value === 'arm64') {
return value
}
throw new Error(`invalid container arch: ${value}`)
}

View File

@@ -1,26 +0,0 @@
export type { ContainerArch } from './arch'
export { ARCHES, parseArch } from './arch'
export type {
AgentArtifact,
AgentManifest,
AggregateEntry,
AggregateManifest,
} from './manifest'
export {
agentArtifactSchema,
agentManifestSchema,
aggregateEntrySchema,
aggregateManifestSchema,
MANIFEST_SCHEMA_VERSION,
ociDigestSchema,
parseAgentManifest,
parseAggregateManifest,
sha256HexSchema,
} from './manifest'
export {
keyForAggregateManifest,
keyForSha,
keyForTarball,
keyForVersionManifest,
R2_AGENTS_PREFIX,
} from './r2-keys'

View File

@@ -1,65 +0,0 @@
import { z } from 'zod'
import { ARCHES } from './arch'
export const MANIFEST_SCHEMA_VERSION = 'v1' as const
export const sha256HexSchema = z.string().regex(/^[a-f0-9]{64}$/)
export const ociDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/)
export const agentArtifactSchema = z.object({
arch: z.enum(ARCHES),
filename: z.string().min(1),
format: z.literal('oci-archive+gzip'),
compressed_sha256: sha256HexSchema,
compressed_size_bytes: z.number().int().positive(),
uncompressed_sha256: sha256HexSchema,
uncompressed_size_bytes: z.number().int().positive(),
url: z.string().url(),
})
export const agentManifestSchema = z.object({
name: z.string().min(1),
schema: z.literal(MANIFEST_SCHEMA_VERSION),
build: z.object({
git_sha: z.string().min(1),
git_dirty: z.boolean(),
built_at: z.string().datetime(),
built_by: z.string().min(1),
config_sha256: sha256HexSchema,
podman_versions: z.array(z.string().min(1)).min(1),
}),
source: z.object({
image: z.string().min(1),
version: z.string().min(1),
oci_digest: ociDigestSchema,
}),
artifacts: z.array(agentArtifactSchema).min(1),
})
export const aggregateEntrySchema = z.object({
name: z.string().min(1),
version: z.string().min(1),
oci_digest: ociDigestSchema,
manifest_url: z.string().url(),
})
export const aggregateManifestSchema = z.object({
schema: z.literal(MANIFEST_SCHEMA_VERSION),
built_at: z.string().datetime(),
built_by: z.string().min(1),
agents: z.array(aggregateEntrySchema).min(1),
})
export type AgentArtifact = z.infer<typeof agentArtifactSchema>
export type AgentManifest = z.infer<typeof agentManifestSchema>
export type AggregateEntry = z.infer<typeof aggregateEntrySchema>
export type AggregateManifest = z.infer<typeof aggregateManifestSchema>
export function parseAgentManifest(raw: unknown): AgentManifest {
return agentManifestSchema.parse(raw)
}
export function parseAggregateManifest(raw: unknown): AggregateManifest {
return aggregateManifestSchema.parse(raw)
}

View File

@@ -1,29 +0,0 @@
import type { ContainerArch } from './arch'
export const R2_AGENTS_PREFIX = 'agents'
export function keyForTarball(
agent: string,
version: string,
arch: ContainerArch,
publishAs = agent,
): string {
return `${R2_AGENTS_PREFIX}/${agent}/${version}/${publishAs}-${version}-${arch}.tar.gz`
}
export function keyForSha(
agent: string,
version: string,
arch: ContainerArch,
publishAs = agent,
): string {
return `${keyForTarball(agent, version, arch, publishAs)}.sha256`
}
export function keyForVersionManifest(agent: string, version: string): string {
return `${R2_AGENTS_PREFIX}/${agent}/${version}/manifest.json`
}
export function keyForAggregateManifest(): string {
return `${R2_AGENTS_PREFIX}/manifest.json`
}

View File

@@ -1,78 +0,0 @@
import { createReadStream, createWriteStream } from 'node:fs'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pipeline } from 'node:stream/promises'
import { createGunzip } from 'node:zlib'
import {
podmanInspectImage,
podmanLoadArchive,
podmanRemoveImage,
} from './build'
export interface RoundTripPodmanLoadOptions {
tarballPath: string
expectedImage: string
expectedImageId?: string
expectedSmokeFingerprint?: string
}
async function maybeDecompressTarball(tarballPath: string): Promise<{
tarPath: string
cleanupDir?: string
}> {
if (!tarballPath.endsWith('.gz')) {
return { tarPath: tarballPath }
}
const tempDir = await mkdtemp(join(tmpdir(), 'agent-container-smoke-'))
const tarPath = join(tempDir, 'image.tar')
await pipeline(
createReadStream(tarballPath),
createGunzip(),
createWriteStream(tarPath),
)
return { tarPath, cleanupDir: tempDir }
}
export async function roundTripPodmanLoad(
options: RoundTripPodmanLoadOptions,
): Promise<void> {
if (!options.expectedImageId && !options.expectedSmokeFingerprint) {
throw new Error(
'expectedImageId or expectedSmokeFingerprint is required for smoke verification',
)
}
const decompressed = await maybeDecompressTarball(options.tarballPath)
try {
await podmanRemoveImage(options.expectedImage).catch(() => {})
await podmanLoadArchive(decompressed.tarPath)
const inspection = await podmanInspectImage(options.expectedImage)
if (
options.expectedSmokeFingerprint &&
inspection.smokeFingerprint !== options.expectedSmokeFingerprint
) {
throw new Error(
`loaded image fingerprint mismatch: expected ${options.expectedSmokeFingerprint}, got ${inspection.smokeFingerprint}`,
)
}
if (
options.expectedImageId &&
inspection.imageId !== options.expectedImageId
) {
throw new Error(
`loaded image ID mismatch: expected ${options.expectedImageId}, got ${inspection.imageId}`,
)
}
} finally {
await podmanRemoveImage(options.expectedImage).catch(() => {})
if (decompressed.cleanupDir) {
await rm(decompressed.cleanupDir, { recursive: true, force: true })
}
}
}

View File

@@ -1,18 +0,0 @@
import { describe, expect, it } from 'bun:test'
import { ARCHES, parseArch } from '../src/schema/arch'
describe('schema/arch', () => {
it('exports the supported arches', () => {
expect(ARCHES).toEqual(['amd64', 'arm64'])
})
it('parses valid arches', () => {
expect(parseArch('amd64')).toBe('amd64')
expect(parseArch('arm64')).toBe('arm64')
})
it('rejects invalid arches', () => {
expect(() => parseArch('x64')).toThrow('invalid container arch')
})
})

View File

@@ -1,112 +0,0 @@
import { afterEach, describe, expect, it } from 'bun:test'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expandMatrix, readAgentsConfig } from '../src/catalog'
const tempPaths: string[] = []
async function writeTempConfig(contents: unknown): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'agent-container-catalog-'))
const filePath = join(dir, 'agents.json')
tempPaths.push(dir)
await writeFile(filePath, `${JSON.stringify(contents, null, 2)}\n`, 'utf8')
return filePath
}
afterEach(async () => {
await Promise.all(
tempPaths
.splice(0)
.map((path) => rm(path, { recursive: true, force: true })),
)
})
describe('catalog', () => {
it('reads and expands the agent matrix', async () => {
const path = await writeTempConfig({
schema: 'v1',
agents: [
{
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arches: ['amd64', 'arm64'],
},
],
})
const config = await readAgentsConfig(path)
expect(expandMatrix(config)).toEqual([
{
agent: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arch: 'amd64',
publishAs: 'openclaw',
},
{
agent: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arch: 'arm64',
publishAs: 'openclaw',
},
])
})
it('filters the matrix by agent name', async () => {
const path = await writeTempConfig({
schema: 'v1',
agents: [
{
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arches: ['amd64'],
},
{
name: 'claude-code',
image: 'ghcr.io/example/claude-code',
version: '1.2.3',
arches: ['arm64'],
publishAs: 'claude',
},
],
})
const config = await readAgentsConfig(path)
expect(expandMatrix(config, { agent: 'claude-code' })).toEqual([
{
agent: 'claude-code',
image: 'ghcr.io/example/claude-code',
version: '1.2.3',
arch: 'arm64',
publishAs: 'claude',
},
])
})
it('rejects duplicate agent names', async () => {
const path = await writeTempConfig({
schema: 'v1',
agents: [
{
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arches: ['amd64'],
},
{
name: 'openclaw',
image: 'ghcr.io/example/openclaw',
version: '2026.4.13',
arches: ['arm64'],
},
],
})
await expect(readAgentsConfig(path)).rejects.toThrow('duplicate agent name')
})
})

View File

@@ -1,47 +0,0 @@
import { describe, expect, it } from 'bun:test'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { readAgentsConfig } from '../src/catalog'
const packageRoot = resolve(import.meta.dir, '..')
const recipePath = resolve(packageRoot, 'recipe', 'agents.json')
const runtimePath = resolve(
import.meta.dir,
'..',
'..',
'..',
'apps',
'server',
'src',
'api',
'services',
'openclaw',
'openclaw-service.ts',
)
describe('OpenClaw drift guard', () => {
it('keeps recipe/agents.json in sync with the runtime image pin', async () => {
const [config, runtimeSource] = await Promise.all([
readAgentsConfig(recipePath),
readFile(runtimePath, 'utf8'),
])
const openclaw = config.agents.find((agent) => agent.name === 'openclaw')
expect(openclaw).toBeDefined()
const match = runtimeSource.match(
/return process\.env\.OPENCLAW_IMAGE \|\| ['"]([^'"]+)['"]/,
)
if (!match?.[1]) {
throw new Error(
`failed to extract OpenClaw image fallback from ${runtimePath}`,
)
}
const recipeImage = `${openclaw?.image}:${openclaw?.version}`
expect(recipeImage).toBe(match[1], {
message: `OpenClaw image drifted between ${recipePath} and ${runtimePath}`,
})
})
})

View File

@@ -1,223 +0,0 @@
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test'
import { existsSync } from 'node:fs'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
buildTarball,
podmanInspectImage,
registryForImage,
} from '../src/build'
const tempDirs: string[] = []
function processResult(
stdout: string,
stderr = '',
exitCode = 0,
): Bun.Subprocess {
return {
stdout: new Response(stdout).body,
stderr: new Response(stderr).body,
exited: Promise.resolve(exitCode),
} as Bun.Subprocess
}
async function createTempDir(): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'agent-container-build-'))
tempDirs.push(dir)
return dir
}
afterEach(async () => {
mock.restore()
await Promise.all(
tempDirs
.splice(0)
.map((path) => rm(path, { recursive: true, force: true })),
)
})
describe('build', () => {
it('resolves registry hosts correctly', () => {
expect(registryForImage('ghcr.io/openclaw/openclaw')).toBe('ghcr.io')
expect(registryForImage('localhost:5000/example/image')).toBe(
'localhost:5000',
)
expect(registryForImage('busybox')).toBe('docker.io')
})
it('builds a tarball result and writes the sidecar files', async () => {
const dir = await createTempDir()
const outputDir = join(dir, 'dist')
const recipePath = join(dir, 'agents.json')
await writeFile(
recipePath,
JSON.stringify({
schema: 'v1',
agents: [
{
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arches: ['arm64'],
},
],
}),
'utf8',
)
const originalSpawn = Bun.spawn
const podmanCommands: string[][] = []
spyOn(Bun, 'spawn').mockImplementation((command, options) => {
if (Array.isArray(command) && command[0] === 'podman') {
podmanCommands.push(command)
if (command[1] === '--version') {
return processResult('podman version 5.8.1\n')
}
if (command[1] === 'pull') {
return processResult('')
}
if (command[1] === 'inspect') {
return processResult(
JSON.stringify({
Id: 'f'.repeat(64),
Digest: `sha256:${'1'.repeat(64)}`,
RepoDigests: [
`ghcr.io/openclaw/openclaw@sha256:${'2'.repeat(64)}`,
`ghcr.io/openclaw/openclaw@sha256:${'1'.repeat(64)}`,
],
Architecture: 'arm64',
Os: 'linux',
Config: {
Entrypoint: ['/entrypoint.sh'],
Env: ['NODE_ENV=production'],
},
RootFS: {
Type: 'layers',
Layers: ['sha256:abc'],
},
}),
)
}
if (command[1] === 'save') {
const outPath = String(command[5])
void writeFile(outPath, 'oci archive payload', 'utf8')
return processResult('')
}
}
return originalSpawn(
command as string[],
options as SpawnOptions.OptionsObject<string[]>,
)
})
const result = await buildTarball({
agent: {
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arches: ['arm64'],
},
arch: 'arm64',
outputDir,
recipePath,
builtBy: 'test-run',
})
expect(result.filename).toBe('openclaw-2026.4.12-arm64.tar.gz')
expect(result.sourceOciDigest).toBe(`sha256:${'2'.repeat(64)}`)
expect(result.imageId).toBe(`sha256:${'f'.repeat(64)}`)
expect(result.smokeFingerprint).toHaveLength(64)
expect(existsSync(result.tarballPath)).toBe(true)
expect(existsSync(result.tarballShaPath)).toBe(true)
expect(existsSync(join(outputDir, 'openclaw-2026.4.12-arm64.tar'))).toBe(
false,
)
expect(existsSync(join(outputDir, 'build-result.json'))).toBe(true)
expect(
podmanCommands.some(
(command) =>
command[1] === 'pull' &&
command.includes('--arch') &&
command.includes('arm64'),
),
).toBe(true)
})
it('prefers the repeated non-platform repo digest as the source OCI digest', async () => {
const originalSpawn = Bun.spawn
spyOn(Bun, 'spawn').mockImplementation((command, options) => {
if (
Array.isArray(command) &&
command[0] === 'podman' &&
command[1] === 'inspect'
) {
return processResult(
JSON.stringify({
Id: 'f'.repeat(64),
Digest: `sha256:${'1'.repeat(64)}`,
RepoDigests: [
`ghcr.io/openclaw/openclaw@sha256:${'2'.repeat(64)}`,
`mirror.example/openclaw/openclaw@sha256:${'2'.repeat(64)}`,
`docker.io/openclaw/openclaw@sha256:${'1'.repeat(64)}`,
],
Architecture: 'arm64',
Os: 'linux',
Config: {},
RootFS: {},
}),
)
}
return originalSpawn(
command as string[],
options as SpawnOptions.OptionsObject<string[]>,
)
})
const inspection = await podmanInspectImage(
'ghcr.io/openclaw/openclaw:2026.4.12',
)
expect(inspection.sourceOciDigest).toBe(`sha256:${'2'.repeat(64)}`)
})
it('fails when repo digests disagree without a clear winner', async () => {
const originalSpawn = Bun.spawn
spyOn(Bun, 'spawn').mockImplementation((command, options) => {
if (
Array.isArray(command) &&
command[0] === 'podman' &&
command[1] === 'inspect'
) {
return processResult(
JSON.stringify({
Id: 'f'.repeat(64),
Digest: `sha256:${'1'.repeat(64)}`,
RepoDigests: [
`ghcr.io/openclaw/openclaw@sha256:${'2'.repeat(64)}`,
`mirror.example/openclaw/openclaw@sha256:${'3'.repeat(64)}`,
`docker.io/openclaw/openclaw@sha256:${'1'.repeat(64)}`,
],
Architecture: 'arm64',
Os: 'linux',
Config: {},
RootFS: {},
}),
)
}
return originalSpawn(
command as string[],
options as SpawnOptions.OptionsObject<string[]>,
)
})
await expect(
podmanInspectImage('ghcr.io/openclaw/openclaw:2026.4.12'),
).rejects.toThrow('ambiguous source OCI digest')
})
})

View File

@@ -1,365 +0,0 @@
import { afterEach, describe, expect, it } from 'bun:test'
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
DeleteObjectCommand,
GetObjectCommand,
PutObjectCommand,
type S3Client,
} from '@aws-sdk/client-s3'
import type { BuildResult } from '../src/build'
import { publishAgents } from '../src/publish'
const tempDirs: string[] = []
function sha(char: string): string {
return char.repeat(64)
}
async function createBuildResult(
root: string,
arch: 'amd64' | 'arm64',
overrides: Partial<BuildResult> = {},
): Promise<BuildResult> {
const dir = join(root, arch)
await mkdir(dir, { recursive: true })
const tarballPath = join(dir, `openclaw-2026.4.12-${arch}.tar.gz`)
const tarballShaPath = `${tarballPath}.sha256`
await writeFile(tarballPath, `${arch}-tarball`, 'utf8')
await writeFile(
tarballShaPath,
`${sha(arch === 'amd64' ? 'a' : 'b')} file\n`,
'utf8',
)
return {
name: 'openclaw',
publishAs: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
arch,
sourceOciDigest: `sha256:${sha('c')}`,
imageId: `sha256:${sha(arch === 'amd64' ? 'd' : 'e')}`,
smokeFingerprint: sha(arch === 'amd64' ? '6' : '7'),
filename: `openclaw-2026.4.12-${arch}.tar.gz`,
tarballPath,
tarballShaPath,
compressedSha256: sha(arch === 'amd64' ? '1' : '2'),
compressedSizeBytes: 100,
uncompressedSha256: sha(arch === 'amd64' ? '3' : '4'),
uncompressedSizeBytes: 200,
podmanVersion: 'podman version 5.8.1',
builtAt: '2026-04-22T17:30:00.000Z',
builtBy: 'workflow@refs/heads/dev',
gitSha: 'abc123',
gitDirty: false,
configSha256: sha('5'),
...overrides,
}
}
async function createTempDir(): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'agent-container-publish-'))
tempDirs.push(dir)
return dir
}
afterEach(async () => {
await Promise.all(
tempDirs
.splice(0)
.map((path) => rm(path, { recursive: true, force: true })),
)
})
describe('publish', () => {
it('uploads version manifests and updates aggregate last', async () => {
const root = await createTempDir()
const buildResults = await Promise.all([
createBuildResult(root, 'amd64'),
createBuildResult(root, 'arm64'),
])
const puts: Array<{ key: string; body: unknown }> = []
const client = {
send: async (command: unknown) => {
if (command instanceof GetObjectCommand) {
throw { name: 'NoSuchKey', $metadata: { httpStatusCode: 404 } }
}
if (command instanceof PutObjectCommand) {
puts.push({
key: String(command.input.Key),
body: command.input.Body,
})
return {}
}
if (command instanceof DeleteObjectCommand) {
return {}
}
throw new Error('unexpected command')
},
destroy: () => {},
} as unknown as S3Client
await publishAgents({
buildResults,
updateAggregate: true,
bucket: 'test-bucket',
cdnBaseURL: 'https://cdn.example.com',
client,
now: () => new Date('2026-04-22T18:00:00.000Z'),
})
expect(puts.map((entry) => entry.key)).toEqual([
'agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
'agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz.sha256',
'agents/openclaw/2026.4.12/openclaw-2026.4.12-arm64.tar.gz',
'agents/openclaw/2026.4.12/openclaw-2026.4.12-arm64.tar.gz.sha256',
'agents/openclaw/2026.4.12/manifest.json',
'agents/manifest.json',
])
const versionManifest = JSON.parse(
String(puts.find((entry) => entry.key.endsWith('/manifest.json'))?.body),
)
expect(versionManifest.source.oci_digest).toBe(`sha256:${sha('c')}`)
expect(versionManifest.artifacts[0].url).toBe(
'https://cdn.example.com/agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
)
const aggregateManifest = JSON.parse(String(puts.at(-1)?.body))
expect(aggregateManifest.agents).toEqual([
{
name: 'openclaw',
version: '2026.4.12',
oci_digest: `sha256:${sha('c')}`,
manifest_url:
'https://cdn.example.com/agents/openclaw/2026.4.12/manifest.json',
},
])
})
it('rolls back uploaded keys when a later upload fails', async () => {
const root = await createTempDir()
const buildResults = [await createBuildResult(root, 'amd64')]
const deleted: string[] = []
const client = {
send: async (command: unknown) => {
if (command instanceof GetObjectCommand) {
throw { name: 'NoSuchKey', $metadata: { httpStatusCode: 404 } }
}
if (command instanceof PutObjectCommand) {
if (String(command.input.Key).endsWith('/manifest.json')) {
throw new Error('manifest upload failed')
}
return {}
}
if (command instanceof DeleteObjectCommand) {
deleted.push(String(command.input.Key))
return {}
}
throw new Error('unexpected command')
},
destroy: () => {},
} as unknown as S3Client
await expect(
publishAgents({
buildResults,
updateAggregate: true,
bucket: 'test-bucket',
client,
}),
).rejects.toThrow('manifest upload failed')
expect(deleted).toEqual([
'agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz.sha256',
'agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
])
})
it('merges new entries into an existing aggregate manifest', async () => {
const root = await createTempDir()
const buildResults = [await createBuildResult(root, 'amd64')]
const puts: Array<{ key: string; body: unknown }> = []
const client = {
send: async (command: unknown) => {
if (command instanceof GetObjectCommand) {
return {
Body: {
transformToByteArray: async () =>
new TextEncoder().encode(
JSON.stringify({
schema: 'v1',
built_at: '2026-04-21T00:00:00.000Z',
built_by: 'previous',
agents: [
{
name: 'claude-code',
version: '1.0.0',
oci_digest: `sha256:${sha('9')}`,
manifest_url:
'https://cdn.example.com/agents/claude-code/1.0.0/manifest.json',
},
{
name: 'openclaw',
version: '2026.4.11',
oci_digest: `sha256:${sha('8')}`,
manifest_url:
'https://cdn.example.com/agents/openclaw/2026.4.11/manifest.json',
},
],
}),
),
},
}
}
if (command instanceof PutObjectCommand) {
puts.push({
key: String(command.input.Key),
body: command.input.Body,
})
return {}
}
if (command instanceof DeleteObjectCommand) {
return {}
}
throw new Error('unexpected command')
},
destroy: () => {},
} as unknown as S3Client
await publishAgents({
buildResults,
updateAggregate: true,
bucket: 'test-bucket',
cdnBaseURL: 'https://cdn.example.com',
client,
now: () => new Date('2026-04-22T18:00:00.000Z'),
})
const aggregateManifest = JSON.parse(String(puts.at(-1)?.body))
expect(aggregateManifest.agents).toEqual([
{
name: 'claude-code',
version: '1.0.0',
oci_digest: `sha256:${sha('9')}`,
manifest_url:
'https://cdn.example.com/agents/claude-code/1.0.0/manifest.json',
},
{
name: 'openclaw',
version: '2026.4.12',
oci_digest: `sha256:${sha('c')}`,
manifest_url:
'https://cdn.example.com/agents/openclaw/2026.4.12/manifest.json',
},
])
})
it('records distinct podman versions across arches', async () => {
const root = await createTempDir()
const buildResults = await Promise.all([
createBuildResult(root, 'amd64', {
podmanVersion: 'podman version 5.8.1',
}),
createBuildResult(root, 'arm64', {
podmanVersion: 'podman version 5.9.0',
}),
])
const puts: Array<{ key: string; body: unknown }> = []
const client = {
send: async (command: unknown) => {
if (command instanceof GetObjectCommand) {
throw { name: 'NoSuchKey', $metadata: { httpStatusCode: 404 } }
}
if (command instanceof PutObjectCommand) {
puts.push({
key: String(command.input.Key),
body: command.input.Body,
})
return {}
}
if (command instanceof DeleteObjectCommand) {
return {}
}
throw new Error('unexpected command')
},
destroy: () => {},
} as unknown as S3Client
await publishAgents({
buildResults,
updateAggregate: false,
bucket: 'test-bucket',
client,
})
const versionManifest = JSON.parse(
String(puts.find((entry) => entry.key.endsWith('/manifest.json'))?.body),
)
expect(versionManifest.build.podman_versions).toEqual([
'podman version 5.8.1',
'podman version 5.9.0',
])
})
it('records all build provenance values in the aggregate manifest', async () => {
const root = await createTempDir()
const buildResults = [
await createBuildResult(root, 'amd64', {
name: 'claude-code',
publishAs: 'claude-code',
image: 'ghcr.io/example/claude-code',
version: '1.0.0',
builtBy: 'workflow-a@refs/heads/dev',
}),
await createBuildResult(root, 'arm64', {
name: 'openclaw',
publishAs: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
builtBy: 'workflow-b@refs/heads/dev',
}),
]
const puts: Array<{ key: string; body: unknown }> = []
const client = {
send: async (command: unknown) => {
if (command instanceof GetObjectCommand) {
throw { name: 'NoSuchKey', $metadata: { httpStatusCode: 404 } }
}
if (command instanceof PutObjectCommand) {
puts.push({
key: String(command.input.Key),
body: command.input.Body,
})
return {}
}
if (command instanceof DeleteObjectCommand) {
return {}
}
throw new Error('unexpected command')
},
destroy: () => {},
} as unknown as S3Client
await publishAgents({
buildResults,
updateAggregate: true,
bucket: 'test-bucket',
client,
now: () => new Date('2026-04-22T18:00:00.000Z'),
})
const aggregateManifest = JSON.parse(String(puts.at(-1)?.body))
expect(aggregateManifest.built_by).toBe(
'workflow-a@refs/heads/dev, workflow-b@refs/heads/dev',
)
})
})

View File

@@ -1,32 +0,0 @@
import { describe, expect, it } from 'bun:test'
import {
keyForAggregateManifest,
keyForSha,
keyForTarball,
keyForVersionManifest,
} from '../src/schema/r2-keys'
describe('schema/r2-keys', () => {
it('builds tarball keys', () => {
expect(keyForTarball('openclaw', '2026.4.12', 'amd64')).toBe(
'agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
)
})
it('supports a custom publishAs filename prefix', () => {
expect(keyForTarball('claude-code', '1.2.3', 'arm64', 'claude')).toBe(
'agents/claude-code/1.2.3/claude-1.2.3-arm64.tar.gz',
)
expect(keyForSha('claude-code', '1.2.3', 'arm64', 'claude')).toBe(
'agents/claude-code/1.2.3/claude-1.2.3-arm64.tar.gz.sha256',
)
})
it('builds manifest keys', () => {
expect(keyForVersionManifest('openclaw', '2026.4.12')).toBe(
'agents/openclaw/2026.4.12/manifest.json',
)
expect(keyForAggregateManifest()).toBe('agents/manifest.json')
})
})

View File

@@ -1,100 +0,0 @@
import { describe, expect, it } from 'bun:test'
import {
parseAgentManifest,
parseAggregateManifest,
} from '../src/schema/manifest'
function hex(char: string): string {
return char.repeat(64)
}
describe('schema/manifest', () => {
it('parses a valid agent manifest', () => {
const manifest = parseAgentManifest({
name: 'openclaw',
schema: 'v1',
build: {
git_sha: 'abc123',
git_dirty: false,
built_at: '2026-04-22T17:30:00.000Z',
built_by: 'workflow@refs/heads/dev',
config_sha256: hex('0'),
podman_versions: ['podman version 5.8.1'],
},
source: {
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
oci_digest: `sha256:${hex('1')}`,
},
artifacts: [
{
arch: 'amd64',
filename: 'openclaw-2026.4.12-amd64.tar.gz',
format: 'oci-archive+gzip',
compressed_sha256: hex('2'),
compressed_size_bytes: 123,
uncompressed_sha256: hex('3'),
uncompressed_size_bytes: 456,
url: 'https://cdn.browseros.com/agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
},
],
})
expect(manifest.source.version).toBe('2026.4.12')
expect(manifest.artifacts).toHaveLength(1)
})
it('rejects invalid artifact hashes', () => {
expect(() =>
parseAgentManifest({
name: 'openclaw',
schema: 'v1',
build: {
git_sha: 'abc123',
git_dirty: false,
built_at: '2026-04-22T17:30:00.000Z',
built_by: 'workflow@refs/heads/dev',
config_sha256: hex('0'),
podman_versions: ['podman version 5.8.1'],
},
source: {
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
oci_digest: `sha256:${hex('1')}`,
},
artifacts: [
{
arch: 'amd64',
filename: 'openclaw-2026.4.12-amd64.tar.gz',
format: 'oci-archive+gzip',
compressed_sha256: 'bad',
compressed_size_bytes: 123,
uncompressed_sha256: hex('3'),
uncompressed_size_bytes: 456,
url: 'https://cdn.browseros.com/agents/openclaw/2026.4.12/openclaw-2026.4.12-amd64.tar.gz',
},
],
}),
).toThrow()
})
it('parses a valid aggregate manifest', () => {
const manifest = parseAggregateManifest({
schema: 'v1',
built_at: '2026-04-22T17:30:00.000Z',
built_by: 'workflow@refs/heads/dev',
agents: [
{
name: 'openclaw',
version: '2026.4.12',
oci_digest: `sha256:${hex('4')}`,
manifest_url:
'https://cdn.browseros.com/agents/openclaw/2026.4.12/manifest.json',
},
],
})
expect(manifest.agents[0]?.name).toBe('openclaw')
})
})

View File

@@ -1,7 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,11 @@
# R2 / Cloudflare object storage - required by upload and publish jobs
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=browseros-artifacts
# Public CDN base - used by cache:sync to GET manifest and artifacts
R2_PUBLIC_BASE_URL=https://cdn.browseros.com
# Dev mode routes cache to ~/.browseros-dev/cache/; unset for ~/.browseros/cache/
NODE_ENV=development

View File

@@ -0,0 +1,57 @@
# @browseros/build-tools
Builds BrowserOS VM disks and agent image tarballs, publishes release artifacts to R2, and hydrates the local VM artifact cache for development.
## Setup
```bash
cp packages/build-tools/.env.sample packages/build-tools/.env
bun install
```
## Build a VM disk
Requires `libguestfs`, `qemu-img`, and `zstd` in an arm64 Linux environment.
On Apple Silicon, run this from an arm64 Lima/Debian VM rather than directly on macOS.
```bash
bun run --filter @browseros/build-tools build:disk -- --version 2026.04.22 --arch arm64
```
## Build an agent tarball
Requires `podman`.
```bash
bun run --filter @browseros/build-tools build:tarball -- --agent openclaw --arch arm64
```
## Smoke test artifacts
VM smoke tests require `limactl`, `qemu`, and `zstd`. Agent tarball smoke tests require `podman`.
```bash
bun run --filter @browseros/build-tools smoke:vm -- --arch arm64 --qcow ./dist/browseros-vm-2026.04.22-arm64.qcow2.zst
bun run --filter @browseros/build-tools smoke:tarball -- --agent openclaw --arch arm64 --tarball ./dist/images/openclaw-2026.4.12-arm64.tar.gz
```
## Emit a manifest
```bash
bun run --filter @browseros/build-tools emit-manifest -- --dist-dir packages/build-tools/dist
```
Publish workflows can update only one manifest slice at a time. Sliced publishing requires an existing R2 `vm/manifest.json` baseline; bootstrap first releases with `--slice full`.
```bash
bun run --filter @browseros/build-tools emit-manifest -- --slice vm --merge-from https://cdn.browseros.com/vm/manifest.json
bun run --filter @browseros/build-tools emit-manifest -- --slice agents:openclaw --merge-from https://cdn.browseros.com/vm/manifest.json
```
## Sync the dev cache
```bash
NODE_ENV=development bun run --filter @browseros/build-tools cache:sync
```
Development cache files land under `~/.browseros-dev/cache/vm/`. Production-mode cache files land under `~/.browseros/cache/vm/`.

View File

@@ -0,0 +1,10 @@
{
"vmVersion": "2026.04.22",
"agents": [
{
"name": "openclaw",
"image": "ghcr.io/openclaw/openclaw",
"version": "2026.4.12"
}
]
}

View File

@@ -0,0 +1,26 @@
{
"name": "@browseros/build-tools",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "BrowserOS release artifact producer and dev cache sync",
"scripts": {
"build:disk": "bun run scripts/build-disk.ts",
"build:tarball": "bun run scripts/build-tarball.ts",
"emit-manifest": "bun run scripts/emit-manifest.ts",
"upload": "bun run scripts/upload-to-r2.ts",
"download": "bun run scripts/download-from-r2.ts",
"cache:sync": "bun run scripts/cache-sync.ts",
"smoke:tarball": "bun run scripts/smoke-tarball.ts",
"smoke:vm": "bun run scripts/smoke-vm.ts",
"test": "bun test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"@browseros/shared": "workspace:*"
},
"devDependencies": {
"@types/node": "^24.3.3"
}
}

View File

@@ -0,0 +1 @@
ef5acb5908f6ef1f7ffcf3a63913cdf618da3229ffa3b04e3727959e36bb9de1

View File

@@ -0,0 +1,12 @@
{
"arm64": {
"upstreamVersion": "20260413-2447",
"url": "https://cloud.debian.org/images/cloud/bookworm/20260413-2447/debian-12-genericcloud-arm64-20260413-2447.qcow2",
"sha512": "15ad6c52e255c84eb0e91001c5907b27199d8a7164d8ac172cfe9c92850dfaf606a6c3161d6af7f0fd5a5fef2aa8dcd9a23c2eb0fedbfcddb38e2bc306cba98f"
},
"x64": {
"upstreamVersion": "20260413-2447",
"url": "https://cloud.debian.org/images/cloud/bookworm/20260413-2447/debian-12-genericcloud-amd64-20260413-2447.qcow2",
"sha512": "db11b13c4efcc37828ffadae521d101e85079d349e1418074087bb7d306f11caccdc2b0b539d6fd50d623d40a898f83c6137268a048d7700397dc35b7dcbc927"
}
}

View File

@@ -1,6 +1,6 @@
# BrowserOS VM recipe — Debian 12 (bookworm) genericcloud
# Consumed by src/build/orchestrator.ts. One virt-customize primitive per line.
# Ops: run-command | copy-in <src>:<dest> | write <path>:<content> | truncate <path>
# Consumed by scripts/build-disk.ts. One virt-customize primitive per line.
# Ops: run-command | copy-in <src>:<dest-dir> | upload <src>:<dest-file> | write <path>:<content> | truncate <path>
# {version} and {manifest_tmp} are substituted at build time.
run-command apt-get update
@@ -12,9 +12,8 @@ run-command usermod -aG sudo browseros
copy-in sudoers-browseros:/etc/sudoers.d/
run-command chmod 0440 /etc/sudoers.d/sudoers-browseros
write /etc/browseros-vm-version:{version}
copy-in {manifest_tmp}:/etc/browseros-vm-manifest.json
upload {manifest_tmp}:/etc/browseros-vm-manifest.json
run-command apt-get clean
run-command rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
run-command rm -f /etc/ssh/ssh_host_*
run-command rm -f /etc/machine-id
truncate /etc/machine-id

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env bun
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import {
copyFile,
mkdir,
readFile,
rm,
stat,
writeFile,
} from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { $ } from 'bun'
import { type Arch, parseArch } from './common/arch'
import { fetchWithTimeout } from './common/fetch'
import { qcow2Key } from './common/manifest'
import { sha256File } from './common/sha256'
type ChunkSink = ReturnType<ReturnType<typeof Bun.file>['writer']>
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
version: { type: 'string' },
arch: { type: 'string' },
'output-dir': { type: 'string', default: './dist' },
},
})
if (!values.version || !values.arch) {
console.error(
'usage: build:disk -- --version <YYYY.MM.DD[-N]> --arch <arm64|x64> [--output-dir ./dist]',
)
process.exit(1)
}
const arch = parseArch(values.arch)
const version = values.version
const outDir = values['output-dir']
const pkgRoot = path.resolve(import.meta.dir, '..')
await mkdir(outDir, { recursive: true })
const baseImages = JSON.parse(
await readFile(path.join(pkgRoot, 'recipe/base-images.json'), 'utf8'),
) as Record<Arch, { upstreamVersion: string; url: string; sha512: string }>
const base = baseImages[arch]
if (!base) throw new Error(`missing base image for arch ${arch}`)
const basePath = path.join(outDir, `base-${arch}.qcow2`)
const workPath = path.join(outDir, `work-${version}-${arch}.qcow2`)
const buildMarkerPath = path.join(outDir, `build-marker-${arch}.json`)
const recipePath = path.join(pkgRoot, 'recipe/browseros-vm.recipe')
const rawOut = path.join(outDir, `browseros-vm-${version}-${arch}.qcow2`)
const zstOut = `${rawOut}.zst`
try {
await download(base.url, basePath)
await verifySha512(basePath, base.sha512)
await copyFile(basePath, workPath)
await writeFile(
buildMarkerPath,
`${JSON.stringify({ name: 'browseros-vm', version, arch, phase: 'build' }, null, 2)}\n`,
)
const recipeText = await readFile(recipePath, 'utf8')
const args = composeVirtCustomizeArgs({
diskPath: workPath,
recipeText,
recipeDir: path.dirname(recipePath),
substitutions: { version, manifest_tmp: buildMarkerPath },
})
await spawnChecked(['virt-customize', ...args])
await $`virt-sparsify --in-place ${workPath}`.quiet()
await $`qemu-img convert -O qcow2 -c ${workPath} ${rawOut}`.quiet()
await $`zstd -19 --long=30 -T0 -f -o ${zstOut} ${rawOut}`.quiet()
const sha = await sha256File(zstOut)
const size = (await stat(zstOut)).size
await writeFile(`${zstOut}.sha256`, `${sha} ${path.basename(zstOut)}\n`)
console.log(
JSON.stringify(
{
key: qcow2Key(version, arch),
path: zstOut,
sha256: sha,
sizeBytes: size,
},
null,
2,
),
)
} finally {
await rm(workPath, { force: true })
await rm(basePath, { force: true })
await rm(rawOut, { force: true })
await rm(buildMarkerPath, { force: true })
}
function composeVirtCustomizeArgs(opts: {
diskPath: string
recipeText: string
recipeDir: string
substitutions: Record<string, string>
}): string[] {
const out = ['-a', opts.diskPath, '--network']
for (const rawLine of opts.recipeText.split('\n')) {
const line = rawLine.trim()
if (!line || line.startsWith('#')) continue
const spaceAt = line.indexOf(' ')
if (spaceAt === -1) throw new Error(`invalid recipe line: ${line}`)
const op = line.slice(0, spaceAt)
const rest = subst(line.slice(spaceAt + 1), opts.substitutions)
if (op === 'run-command') {
out.push('--run-command', rest)
continue
}
if (op === 'copy-in') {
const colonAt = rest.indexOf(':')
if (colonAt === -1) throw new Error(`invalid copy-in line: ${line}`)
const source = rest.slice(0, colonAt)
const target = rest.slice(colonAt + 1)
out.push('--copy-in', `${path.resolve(opts.recipeDir, source)}:${target}`)
continue
}
if (op === 'upload') {
const colonAt = rest.indexOf(':')
if (colonAt === -1) throw new Error(`invalid upload line: ${line}`)
const source = rest.slice(0, colonAt)
const target = rest.slice(colonAt + 1)
out.push('--upload', `${path.resolve(opts.recipeDir, source)}:${target}`)
continue
}
if (op === 'write') {
out.push('--write', rest)
continue
}
if (op === 'truncate') {
out.push('--truncate', rest)
continue
}
throw new Error(`unknown recipe op: ${op}`)
}
return out
}
function subst(value: string, vars: Record<string, string>): string {
return value.replace(/\{(\w+)\}/g, (_match, key: string) => {
const replacement = vars[key]
if (!replacement) throw new Error(`no substitution for {${key}}`)
return replacement
})
}
async function download(url: string, dest: string): Promise<void> {
const response = await fetchWithTimeout(url)
if (!response.ok || !response.body) {
throw new Error(`download failed: ${url} (${response.status})`)
}
const sink = Bun.file(dest).writer()
const reader = response.body.getReader()
try {
await pumpStream(reader, sink)
} finally {
await sink.end()
}
}
async function verifySha512(filePath: string, expected: string): Promise<void> {
const hash = createHash('sha512')
for await (const chunk of createReadStream(filePath)) {
hash.update(chunk)
}
const actual = hash.digest('hex')
if (actual !== expected) {
throw new Error(
`sha512 mismatch for ${filePath}: expected ${expected}, got ${actual}`,
)
}
}
async function spawnChecked(argv: string[]): Promise<void> {
const proc = Bun.spawn(argv, {
stdout: 'inherit',
stderr: 'inherit',
env: {
...process.env,
LIBGUESTFS_BACKEND: process.env.LIBGUESTFS_BACKEND ?? 'direct',
},
})
const code = await proc.exited
if (code !== 0) throw new Error(`${argv[0]} exited ${code}`)
}
async function pumpStream(
reader: ReadableStreamDefaultReader<Uint8Array>,
sink: ChunkSink,
): Promise<void> {
for (;;) {
const { done, value } = await reader.read()
if (done) break
sink.write(value)
}
}

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bun
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { parseArch, podmanArch } from './common/arch'
import { type Bundle, tarballKey } from './common/manifest'
import { sha256File } from './common/sha256'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
agent: { type: 'string' },
arch: { type: 'string' },
'output-dir': { type: 'string', default: './dist/images' },
},
})
if (!values.agent || !values.arch) {
console.error(
'usage: build:tarball -- --agent <name> --arch <arm64|x64> [--output-dir ./dist/images]',
)
process.exit(1)
}
const arch = parseArch(values.arch)
const outDir = values['output-dir']
await mkdir(outDir, { recursive: true })
const pkgRoot = path.resolve(import.meta.dir, '..')
const bundle = JSON.parse(
await readFile(path.join(pkgRoot, 'bundle.json'), 'utf8'),
) as Bundle
const agent = bundle.agents.find(({ name }) => name === values.agent)
if (!agent) throw new Error(`unknown agent: ${values.agent}`)
const ref = `${agent.image}:${agent.version}`
const tarballPath = path.join(
outDir,
path.basename(tarballKey(agent.name, agent.version, arch)),
)
const tarPath = tarballPath.slice(0, -'.gz'.length)
await rm(tarballPath, { force: true })
await rm(`${tarballPath}.sha256`, { force: true })
await rm(tarPath, { force: true })
await spawnChecked([
'podman',
'pull',
'--os',
'linux',
'--arch',
podmanArch(arch),
ref,
])
await spawnChecked([
'podman',
'save',
'--format=oci-archive',
'--output',
tarPath,
ref,
])
await spawnChecked(['gzip', '-9', '-f', tarPath])
const sha = await sha256File(tarballPath)
const size = (await stat(tarballPath)).size
await writeFile(
`${tarballPath}.sha256`,
`${sha} ${path.basename(tarballPath)}\n`,
)
console.log(
JSON.stringify(
{
key: tarballKey(agent.name, agent.version, arch),
path: tarballPath,
sha256: sha,
sizeBytes: size,
},
null,
2,
),
)
async function spawnChecked(argv: string[]): Promise<void> {
const proc = Bun.spawn(argv, {
stdout: 'inherit',
stderr: 'inherit',
})
const code = await proc.exited
if (code !== 0) throw new Error(`${argv[0]} exited ${code}`)
}

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env bun
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
import { homedir, arch as hostArch } from 'node:os'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { PATHS } from '@browseros/shared/constants/paths'
import { ARCHES, type Arch } from './common/arch'
import { fetchWithTimeout } from './common/fetch'
import type { Artifact, VmManifest } from './common/manifest'
import { verifySha256 } from './common/sha256'
type ChunkSink = ReturnType<ReturnType<typeof Bun.file>['writer']>
export interface PlanItem {
key: string
destPath: string
sha256: string
}
export function planSync(opts: {
local: VmManifest | null
remote: VmManifest
cacheRoot: string
arches: Arch[]
}): PlanItem[] {
const out: PlanItem[] = []
for (const arch of opts.arches) {
maybeAdd(
out,
opts.remote.vmDisk[arch],
opts.local?.vmDisk[arch],
opts.cacheRoot,
)
for (const [name, agent] of Object.entries(opts.remote.agents)) {
maybeAdd(
out,
agent.tarballs[arch],
opts.local?.agents[name]?.tarballs[arch],
opts.cacheRoot,
)
}
}
return out
}
export function selectSyncArches(
allArches: boolean,
rawHostArch = hostArch(),
): Arch[] {
if (allArches) return [...ARCHES]
if (rawHostArch === 'arm64') return ['arm64']
if (rawHostArch === 'x64' || rawHostArch === 'ia32') return ['x64']
throw new Error(`unsupported host arch: ${rawHostArch}`)
}
if (import.meta.main) {
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
'manifest-url': { type: 'string' },
'all-arches': { type: 'boolean' },
'cache-dir': { type: 'string' },
},
})
const cdnBase =
process.env.R2_PUBLIC_BASE_URL?.trim() ?? 'https://cdn.browseros.com'
const manifestUrl = values['manifest-url'] ?? `${cdnBase}/vm/manifest.json`
const cacheRoot = values['cache-dir'] ?? getCacheDir()
const arches = selectSyncArches(values['all-arches'] ?? false)
const response = await fetchWithTimeout(manifestUrl)
if (!response.ok) {
throw new Error(
`manifest fetch failed: ${manifestUrl} (${response.status})`,
)
}
const remote = (await response.json()) as VmManifest
const localManifestPath = path.join(cacheRoot, 'vm', 'manifest.json')
const local = await readLocalManifest(localManifestPath)
const plan = planSync({ local, remote, cacheRoot, arches })
if (plan.length === 0) {
console.log(`cache up to date at vmVersion ${remote.vmVersion}`)
process.exit(0)
}
console.log(
`syncing ${plan.length} artifact(s) for vmVersion ${remote.vmVersion}`,
)
for (const item of plan) {
await mkdir(path.dirname(item.destPath), { recursive: true })
const partial = `${item.destPath}.partial`
await downloadToFile(`${cdnBase}/${item.key}`, partial)
await verifySha256(partial, item.sha256)
await rename(partial, item.destPath)
console.log(`synced ${item.key}`)
}
await mkdir(path.dirname(localManifestPath), { recursive: true })
await writeFile(localManifestPath, `${JSON.stringify(remote, null, 2)}\n`)
console.log(`manifest written to ${localManifestPath}`)
}
function maybeAdd(
out: PlanItem[],
remote: Artifact,
local: Artifact | undefined,
cacheRoot: string,
): void {
if (local?.sha256 === remote.sha256) return
out.push({
key: remote.key,
destPath: path.join(cacheRoot, remote.key),
sha256: remote.sha256,
})
}
function getCacheDir(): string {
const dirName =
process.env.NODE_ENV === 'development'
? PATHS.DEV_BROWSEROS_DIR_NAME
: PATHS.BROWSEROS_DIR_NAME
return path.join(homedir(), dirName, PATHS.CACHE_DIR_NAME)
}
export async function readLocalManifest(
manifestPath: string,
): Promise<VmManifest | null> {
try {
return JSON.parse(await readFile(manifestPath, 'utf8')) as VmManifest
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null
throw error
}
}
async function downloadToFile(url: string, dest: string): Promise<void> {
const response = await fetchWithTimeout(url)
if (!response.ok || !response.body) {
throw new Error(`download failed: ${url} (${response.status})`)
}
const sink = Bun.file(dest).writer()
const reader = response.body.getReader()
try {
await pumpStream(reader, sink)
} finally {
await sink.end()
}
}
async function pumpStream(
reader: ReadableStreamDefaultReader<Uint8Array>,
sink: ChunkSink,
): Promise<void> {
for (;;) {
const { done, value } = await reader.read()
if (done) break
sink.write(value)
}
}

View File

@@ -0,0 +1,12 @@
export type Arch = 'arm64' | 'x64'
export const ARCHES: readonly Arch[] = ['arm64', 'x64']
export function parseArch(raw: string): Arch {
if (raw === 'arm64' || raw === 'x64') return raw
throw new Error(`unknown arch: ${raw} (expected arm64|x64)`)
}
export function podmanArch(arch: Arch): 'arm64' | 'amd64' {
return arch === 'x64' ? 'amd64' : 'arm64'
}

View File

@@ -0,0 +1,22 @@
export async function fetchWithTimeout(
url: string,
init: RequestInit = {},
timeoutMs = 30_000,
): Promise<Response> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeoutMs)
try {
return await fetch(url, {
...init,
signal: init.signal ?? controller.signal,
})
} catch (error) {
if ((error as { name?: string }).name === 'AbortError') {
throw new Error(`fetch timed out after ${timeoutMs}ms: ${url}`)
}
throw error
} finally {
clearTimeout(timer)
}
}

View File

@@ -0,0 +1,96 @@
import { ARCHES, type Arch } from './arch'
export interface Artifact {
key: string
sha256: string
sizeBytes: number
}
export interface AgentEntry {
image: string
version: string
tarballs: Record<Arch, Artifact>
}
export interface VmManifest {
schemaVersion: 1
vmVersion: string
updatedAt: string
vmDisk: Record<Arch, Artifact>
agents: Record<string, AgentEntry>
}
export interface BundleAgent {
name: string
image: string
version: string
}
export interface Bundle {
vmVersion: string
agents: BundleAgent[]
}
export interface ArtifactInput {
sha256: string
sizeBytes: number
}
export interface ArtifactInputs {
vmDisk: Record<Arch, ArtifactInput>
agents: Record<string, Record<Arch, ArtifactInput>>
}
export function qcow2Key(vmVersion: string, arch: Arch): string {
return `vm/browseros-vm-${vmVersion}-${arch}.qcow2.zst`
}
export function tarballKey(name: string, version: string, arch: Arch): string {
return `vm/images/${name}-${version}-${arch}.tar.gz`
}
export function buildManifest(
bundle: Bundle,
inputs: ArtifactInputs,
now: Date = new Date(),
): VmManifest {
const vmDisk = {} as Record<Arch, Artifact>
for (const arch of ARCHES) {
const entry = inputs.vmDisk[arch]
if (!entry) throw new Error(`missing vmDisk inputs for arch ${arch}`)
vmDisk[arch] = {
key: qcow2Key(bundle.vmVersion, arch),
sha256: entry.sha256,
sizeBytes: entry.sizeBytes,
}
}
const agents: Record<string, AgentEntry> = {}
for (const agent of bundle.agents) {
const tarballs = {} as Record<Arch, Artifact>
for (const arch of ARCHES) {
const entry = inputs.agents[agent.name]?.[arch]
if (!entry) {
throw new Error(`missing tarball inputs for ${agent.name}/${arch}`)
}
tarballs[arch] = {
key: tarballKey(agent.name, agent.version, arch),
sha256: entry.sha256,
sizeBytes: entry.sizeBytes,
}
}
agents[agent.name] = {
image: agent.image,
version: agent.version,
tarballs,
}
}
return {
schemaVersion: 1,
vmVersion: bundle.vmVersion,
updatedAt: now.toISOString(),
vmDisk,
agents,
}
}

View File

@@ -0,0 +1,96 @@
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
function required(name: string): string {
const value = process.env[name]?.trim()
if (!value) throw new Error(`missing env var: ${name}`)
return value
}
export function createR2Client(): S3Client {
return new S3Client({
region: 'auto',
endpoint: `https://${required('R2_ACCOUNT_ID')}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: required('R2_ACCESS_KEY_ID'),
secretAccessKey: required('R2_SECRET_ACCESS_KEY'),
},
})
}
export function getBucket(): string {
return required('R2_BUCKET')
}
export function getCdnBase(): string {
return process.env.R2_PUBLIC_BASE_URL?.trim() ?? 'https://cdn.browseros.com'
}
export async function putFile(
client: S3Client,
bucket: string,
key: string,
filePath: string,
contentType: string,
): Promise<void> {
const { size } = await stat(filePath)
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: createReadStream(filePath),
ContentLength: size,
ContentType: contentType,
}),
)
}
export async function putBody(
client: S3Client,
bucket: string,
key: string,
body: string,
contentType: string,
): Promise<void> {
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentLength: Buffer.byteLength(body),
ContentType: contentType,
}),
)
}
export async function getBody(
client: S3Client,
bucket: string,
key: string,
): Promise<string | null> {
try {
const response = await client.send(
new GetObjectCommand({ Bucket: bucket, Key: key }),
)
const body = response.Body as
| { transformToByteArray(): Promise<Uint8Array> }
| undefined
if (!body) throw new Error(`missing response body for R2 key: ${key}`)
const bytes = await body.transformToByteArray()
return new TextDecoder().decode(bytes)
} catch (error) {
const cause = error as {
name?: string
$metadata?: { httpStatusCode?: number }
}
if (cause.name === 'NoSuchKey' || cause.$metadata?.httpStatusCode === 404) {
return null
}
throw error
}
}

View File

@@ -0,0 +1,22 @@
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
export async function sha256File(path: string): Promise<string> {
const hash = createHash('sha256')
for await (const chunk of createReadStream(path)) {
hash.update(chunk)
}
return hash.digest('hex')
}
export async function verifySha256(
path: string,
expected: string,
): Promise<void> {
const actual = await sha256File(path)
if (actual !== expected) {
throw new Error(
`sha256 mismatch for ${path}: expected ${expected}, got ${actual}`,
)
}
}

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bun
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { createR2Client, getBody, getBucket } from './common/r2'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
key: { type: 'string' },
out: { type: 'string' },
},
})
if (!values.key || !values.out) {
console.error('usage: download -- --key <r2-key> --out <path>')
process.exit(1)
}
const body = await getBody(createR2Client(), getBucket(), values.key)
if (body === null) {
throw new Error(
`R2 key not found: ${values.key}. Publish a full manifest before publishing slices.`,
)
}
await mkdir(path.dirname(values.out), { recursive: true })
await writeFile(values.out, body)
console.log(`downloaded ${values.key} to ${values.out}`)

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env bun
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { ARCHES, type Arch } from './common/arch'
import { fetchWithTimeout } from './common/fetch'
import {
type AgentEntry,
type Artifact,
type ArtifactInputs,
type Bundle,
type BundleAgent,
buildManifest,
qcow2Key,
tarballKey,
type VmManifest,
} from './common/manifest'
import { sha256File } from './common/sha256'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
'dist-dir': { type: 'string', default: './dist' },
out: { type: 'string' },
slice: { type: 'string', default: 'full' },
'merge-from': { type: 'string' },
},
})
const distDir = values['dist-dir']
const slice = values.slice
const pkgRoot = path.resolve(import.meta.dir, '..')
const bundle = JSON.parse(
await readFile(path.join(pkgRoot, 'bundle.json'), 'utf8'),
) as Bundle
const baseline = values['merge-from']
? await loadBaseline(values['merge-from'])
: null
if (slice !== 'full' && !baseline) {
throw new Error(`--slice ${slice} requires --merge-from`)
}
const manifest = await buildSlicedManifest({ bundle, distDir, slice, baseline })
const outPath = values.out ?? path.join(distDir, 'manifest.json')
await mkdir(path.dirname(outPath), { recursive: true })
await writeFile(outPath, `${JSON.stringify(manifest, null, 2)}\n`)
console.log(`wrote ${outPath} (slice=${slice})`)
async function buildSlicedManifest(opts: {
bundle: Bundle
distDir: string
slice: string
baseline: VmManifest | null
}): Promise<VmManifest> {
if (opts.slice === 'full') {
return buildManifest(
opts.bundle,
await readAllInputs(opts.bundle, opts.distDir),
)
}
const baseline = opts.baseline
if (!baseline) throw new Error(`--slice ${opts.slice} requires --merge-from`)
const updatedAt = new Date().toISOString()
if (opts.slice === 'vm') {
return {
...baseline,
schemaVersion: 1,
vmVersion: opts.bundle.vmVersion,
updatedAt,
vmDisk: await readVmDisk(opts.bundle.vmVersion, opts.distDir),
}
}
if (opts.slice.startsWith('agents:')) {
const name = opts.slice.slice('agents:'.length)
const agent = opts.bundle.agents.find((entry) => entry.name === name)
if (!agent) throw new Error(`unknown agent: ${name}`)
return {
...baseline,
updatedAt,
agents: {
...baseline.agents,
[name]: await readAgentEntry(agent, opts.distDir),
},
}
}
throw new Error(`unknown slice: ${opts.slice}`)
}
async function readAllInputs(
bundle: Bundle,
distDir: string,
): Promise<ArtifactInputs> {
const agents: ArtifactInputs['agents'] = {}
for (const agent of bundle.agents) {
agents[agent.name] = {} as ArtifactInputs['agents'][string]
for (const arch of ARCHES) {
const artifactPath = path.join(
distDir,
'images',
path.basename(tarballKey(agent.name, agent.version, arch)),
)
agents[agent.name][arch] = await readArtifactInput(artifactPath)
}
}
return {
vmDisk: await readArtifactInputs((arch) =>
path.join(distDir, path.basename(qcow2Key(bundle.vmVersion, arch))),
),
agents,
}
}
async function readVmDisk(
vmVersion: string,
distDir: string,
): Promise<Record<Arch, Artifact>> {
const vmDisk = {} as Record<Arch, Artifact>
for (const arch of ARCHES) {
const key = qcow2Key(vmVersion, arch)
const artifactPath = path.join(distDir, path.basename(key))
vmDisk[arch] = { key, ...(await readArtifactInput(artifactPath)) }
}
return vmDisk
}
async function readAgentEntry(
agent: BundleAgent,
distDir: string,
): Promise<AgentEntry> {
const tarballs = {} as AgentEntry['tarballs']
for (const arch of ARCHES) {
const key = tarballKey(agent.name, agent.version, arch)
const artifactPath = path.join(distDir, 'images', path.basename(key))
tarballs[arch] = { key, ...(await readArtifactInput(artifactPath)) }
}
return { image: agent.image, version: agent.version, tarballs }
}
async function readArtifactInputs(
pathForArch: (arch: Arch) => string,
): Promise<Record<Arch, { sha256: string; sizeBytes: number }>> {
const out = {} as Record<Arch, { sha256: string; sizeBytes: number }>
for (const arch of ARCHES) {
out[arch] = await readArtifactInput(pathForArch(arch))
}
return out
}
async function readArtifactInput(
filePath: string,
): Promise<{ sha256: string; sizeBytes: number }> {
return {
sha256: await sha256File(filePath),
sizeBytes: (await stat(filePath)).size,
}
}
async function loadBaseline(src: string): Promise<VmManifest> {
if (src.startsWith('http://') || src.startsWith('https://')) {
const response = await fetchWithTimeout(src)
if (!response.ok) {
throw new Error(`baseline fetch failed: ${src} (${response.status})`)
}
return (await response.json()) as VmManifest
}
return JSON.parse(await readFile(src, 'utf8')) as VmManifest
}

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env bun
import { createReadStream, createWriteStream } from 'node:fs'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { pipeline } from 'node:stream/promises'
import { parseArgs } from 'node:util'
import { createGunzip } from 'node:zlib'
import { parseArch, podmanArch } from './common/arch'
import type { Bundle } from './common/manifest'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
agent: { type: 'string' },
arch: { type: 'string' },
tarball: { type: 'string' },
},
})
if (!values.agent || !values.arch || !values.tarball) {
console.error(
'usage: smoke:tarball -- --agent <name> --arch <arm64|x64> --tarball <path.tar.gz>',
)
process.exit(1)
}
const arch = parseArch(values.arch)
const pkgRoot = path.resolve(import.meta.dir, '..')
const bundle = JSON.parse(
await readFile(path.join(pkgRoot, 'bundle.json'), 'utf8'),
) as Bundle
const agent = bundle.agents.find(({ name }) => name === values.agent)
if (!agent) throw new Error(`unknown agent: ${values.agent}`)
const ref = `${agent.image}:${agent.version}`
const tarball = await maybeDecompress(values.tarball)
try {
await spawnChecked(['podman', 'rmi', '-f', ref]).catch(() => {})
await spawnChecked(['podman', 'load', '--input', tarball.path])
const inspected = await inspectImage(ref)
if (inspected.Os !== 'linux') {
throw new Error(`expected linux image, got ${inspected.Os ?? '<missing>'}`)
}
if (inspected.Architecture !== podmanArch(arch)) {
throw new Error(
`expected ${podmanArch(arch)} image, got ${inspected.Architecture ?? '<missing>'}`,
)
}
} finally {
await spawnChecked(['podman', 'rmi', '-f', ref]).catch(() => {})
if (tarball.cleanupDir) {
await rm(tarball.cleanupDir, { recursive: true, force: true })
}
}
console.log('tarball smoke test passed')
async function maybeDecompress(
tarballPath: string,
): Promise<{ path: string; cleanupDir?: string }> {
if (!tarballPath.endsWith('.gz')) return { path: tarballPath }
const cleanupDir = await mkdtemp(path.join(tmpdir(), 'browseros-tar-smoke-'))
const tarPath = path.join(cleanupDir, 'image.tar')
await pipeline(
createReadStream(tarballPath),
createGunzip(),
createWriteStream(tarPath),
)
return { path: tarPath, cleanupDir }
}
async function inspectImage(ref: string): Promise<{
Architecture?: string
Os?: string
}> {
const stdout = await spawnCapture([
'podman',
'inspect',
'--type',
'image',
'--format',
'{{json .}}',
ref,
])
return JSON.parse(stdout) as { Architecture?: string; Os?: string }
}
async function spawnCapture(argv: string[]): Promise<string> {
const proc = Bun.spawn(argv, { stdout: 'pipe', stderr: 'pipe' })
const [stdout, stderr, code] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
])
if (code !== 0) {
throw new Error(
`${argv[0]} exited ${code}\n${stderr.trim() || stdout.trim()}`,
)
}
return stdout.trim()
}
async function spawnChecked(argv: string[]): Promise<void> {
await spawnCapture(argv)
}

View File

@@ -1,37 +1,51 @@
#!/usr/bin/env bun
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { $ } from 'bun'
import { type Arch, parseArch } from './common/arch'
const INSTANCE_NAME = 'browseros-vm-smoke'
const SOCKET_POLL_INTERVAL_MS = 2000
const SOCKET_POLL_TIMEOUT_MS = 120_000
// Bun's fetch accepts a `unix` socket key at runtime, but the web-standard
// `RequestInit` type doesn't include it. Declare the Bun-specific shape
// locally so callers don't need to cast inline.
type BunRequestInit = RequestInit & { unix?: string }
export interface BootProbeOptions {
limactlPath?: string
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
qcow: { type: 'string' },
arch: { type: 'string', default: 'x64' },
limactl: { type: 'string', default: 'limactl' },
},
})
if (!values.qcow) {
console.error(
'usage: smoke:vm -- --qcow <path.qcow2.zst> [--arch arm64|x64] [--limactl limactl]',
)
process.exit(1)
}
export async function bootAndProbe(
const arch = parseArch(values.arch ?? 'x64')
await bootAndProbe(values.qcow, arch, values.limactl ?? 'limactl')
console.log('vm smoke test passed')
async function bootAndProbe(
qcowZstPath: string,
opts: BootProbeOptions = {},
arch: Arch,
limactl: string,
): Promise<void> {
const limactl = (opts.limactlPath ?? 'limactl').trim()
if (!limactl) {
throw new Error('bootAndProbe: limactlPath cannot be an empty string')
}
const workDir = await mkdtemp(path.join(tmpdir(), 'vm-smoke-'))
const workDir = await mkdtemp(path.join(tmpdir(), 'browseros-vm-smoke-'))
const qcowPath = path.join(workDir, 'disk.qcow2')
const configPath = path.join(workDir, 'lima.yaml')
const sockPath = path.join(workDir, 'podman.sock')
try {
await $`zstd -d -f -o ${qcowPath} ${qcowZstPath}`.quiet()
await writeFile(configPath, composeLimaConfig(qcowPath, sockPath))
await writeFile(configPath, composeLimaConfig(qcowPath, arch, sockPath))
await $`${limactl} start --name=${INSTANCE_NAME} --tty=false ${configPath}`
await waitForSocket(sockPath)
await probePodmanSocket(sockPath)
@@ -42,12 +56,15 @@ export async function bootAndProbe(
}
}
export function composeLimaConfig(qcowPath: string, sockPath: string): string {
return `# Generated by @browseros/vm-container smoke test
vmType: qemu
function composeLimaConfig(
qcowPath: string,
arch: Arch,
sockPath: string,
): string {
return `vmType: qemu
images:
- location: ${qcowPath}
arch: x86_64
arch: ${limaArch(arch)}
containerd:
system: false
user: false
@@ -60,11 +77,14 @@ portForwards:
`
}
function limaArch(arch: Arch): 'aarch64' | 'x86_64' {
return arch === 'arm64' ? 'aarch64' : 'x86_64'
}
async function waitForSocket(sockPath: string): Promise<void> {
const deadline = Date.now() + SOCKET_POLL_TIMEOUT_MS
while (Date.now() < deadline) {
const file = Bun.file(sockPath)
if (await file.exists()) return
if (await Bun.file(sockPath).exists()) return
await Bun.sleep(SOCKET_POLL_INTERVAL_MS)
}
throw new Error(

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bun
import { parseArgs } from 'node:util'
import { createR2Client, getBucket, putBody, putFile } from './common/r2'
import { sha256File } from './common/sha256'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
file: { type: 'string' },
key: { type: 'string' },
'content-type': { type: 'string' },
'sidecar-sha': { type: 'boolean' },
},
})
if (!values.file || !values.key) {
throw new Error('--file and --key required')
}
const contentType = values['content-type'] ?? 'application/octet-stream'
const client = createR2Client()
const bucket = getBucket()
try {
await putFile(client, bucket, values.key, values.file, contentType)
console.log(`uploaded ${values.file} to ${bucket}/${values.key}`)
if (values['sidecar-sha']) {
const sha = await sha256File(values.file)
const filename = values.file.split('/').pop() ?? values.file
await putBody(
client,
bucket,
`${values.key}.sha256`,
`${sha} ${filename}\n`,
'text/plain; charset=utf-8',
)
console.log(`uploaded sha256 to ${bucket}/${values.key}.sha256`)
}
} finally {
client.destroy()
}

View File

@@ -0,0 +1,18 @@
import { describe, expect, it } from 'bun:test'
import { parseArch, podmanArch } from '../scripts/common/arch'
describe('arch helpers', () => {
it('normalizes BrowserOS arches for podman', () => {
expect(podmanArch('arm64')).toBe('arm64')
expect(podmanArch('x64')).toBe('amd64')
})
it('parses supported release arches', () => {
expect(parseArch('arm64')).toBe('arm64')
expect(parseArch('x64')).toBe('x64')
})
it('rejects unsupported release arches', () => {
expect(() => parseArch('amd64')).toThrow('unknown arch: amd64')
})
})

View File

@@ -0,0 +1,282 @@
import { afterEach, describe, expect, it } from 'bun:test'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import {
type PlanItem,
planSync,
readLocalManifest,
selectSyncArches,
} from '../scripts/cache-sync'
import type { VmManifest } from '../scripts/common/manifest'
import { sha256File } from '../scripts/common/sha256'
const openclaw = {
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
}
function manifest(
vmVersion: string,
diskSha: string,
tarSha: string,
): VmManifest {
return {
schemaVersion: 1,
vmVersion,
updatedAt: '2026-04-22T00:00:00.000Z',
vmDisk: {
arm64: {
key: `vm/browseros-vm-${vmVersion}-arm64.qcow2.zst`,
sha256: `${diskSha}-arm64`,
sizeBytes: 101,
},
x64: {
key: `vm/browseros-vm-${vmVersion}-x64.qcow2.zst`,
sha256: `${diskSha}-x64`,
sizeBytes: 102,
},
},
agents: {
openclaw: {
...openclaw,
tarballs: {
arm64: {
key: 'vm/images/openclaw-2026.4.12-arm64.tar.gz',
sha256: `${tarSha}-arm64`,
sizeBytes: 201,
},
x64: {
key: 'vm/images/openclaw-2026.4.12-x64.tar.gz',
sha256: `${tarSha}-x64`,
sizeBytes: 202,
},
},
},
},
}
}
function keys(plan: PlanItem[]): string[] {
return plan.map((item) => item.key)
}
describe('planSync', () => {
it('downloads every selected-arch artifact for a fresh cache', () => {
const remote = manifest('2026.04.22', 'd1', 't1')
expect(
keys(planSync({ local: null, remote, cacheRoot: '/c', arches: ['x64'] })),
).toEqual([
'vm/browseros-vm-2026.04.22-x64.qcow2.zst',
'vm/images/openclaw-2026.4.12-x64.tar.gz',
])
})
it('does nothing when the local manifest matches the remote manifest', () => {
const remote = manifest('2026.04.22', 'd1', 't1')
expect(
planSync({ local: remote, remote, cacheRoot: '/c', arches: ['x64'] }),
).toEqual([])
})
it('downloads only artifacts whose sha256 changed', () => {
const local = manifest('2026.04.20', 'd-old', 't1')
const remote = manifest('2026.04.22', 'd-new', 't1')
expect(
keys(planSync({ local, remote, cacheRoot: '/c', arches: ['x64'] })),
).toEqual(['vm/browseros-vm-2026.04.22-x64.qcow2.zst'])
})
it('supports syncing all release arches', () => {
const remote = manifest('2026.04.22', 'd1', 't1')
expect(
planSync({
local: null,
remote,
cacheRoot: '/c',
arches: ['arm64', 'x64'],
}),
).toHaveLength(4)
})
it('selects host arch by default and both arches when requested', () => {
expect(selectSyncArches(false, 'x64')).toEqual(['x64'])
expect(selectSyncArches(true, 'x64')).toEqual(['arm64', 'x64'])
})
})
describe('readLocalManifest', () => {
let dir: string | null = null
afterEach(async () => {
if (!dir) return
await rm(dir, { recursive: true, force: true })
dir = null
})
it('returns null only when the local manifest is absent', async () => {
dir = await mkdtemp(path.join(tmpdir(), 'browseros-cache-manifest-'))
await expect(
readLocalManifest(path.join(dir, 'missing.json')),
).resolves.toBeNull()
})
it('surfaces corrupt local manifest files', async () => {
dir = await mkdtemp(path.join(tmpdir(), 'browseros-cache-manifest-'))
const manifestPath = path.join(dir, 'manifest.json')
await writeFile(manifestPath, '{not json')
await expect(readLocalManifest(manifestPath)).rejects.toThrow()
})
})
describe('emit-manifest', () => {
let dir: string | null = null
afterEach(async () => {
if (!dir) return
await rm(dir, { recursive: true, force: true })
dir = null
})
it('merges a vm slice while preserving agents from the baseline', async () => {
dir = await mkdtemp(path.join(tmpdir(), 'browseros-emit-vm-'))
const distDir = path.join(dir, 'dist')
await writeVmFiles(distDir)
const baseline = manifest('2026.04.20', 'old-disk', 'old-tar')
const baselinePath = path.join(dir, 'baseline.json')
const outPath = path.join(dir, 'manifest.json')
await writeJson(baselinePath, baseline)
await runEmitManifest([
'--slice',
'vm',
'--dist-dir',
distDir,
'--merge-from',
baselinePath,
'--out',
outPath,
])
const merged = JSON.parse(await readFile(outPath, 'utf8')) as VmManifest
expect(merged.vmVersion).toBe('2026.04.22')
expect(merged.agents).toEqual(baseline.agents)
expect(merged.vmDisk.x64.sha256).toBe(
await sha256File(
path.join(distDir, 'browseros-vm-2026.04.22-x64.qcow2.zst'),
),
)
})
it('merges an agent slice while preserving vmDisk from the baseline', async () => {
dir = await mkdtemp(path.join(tmpdir(), 'browseros-emit-agent-'))
const distDir = path.join(dir, 'dist')
await writeAgentFiles(distDir)
const baseline = manifest('2026.04.20', 'old-disk', 'old-tar')
const baselinePath = path.join(dir, 'baseline.json')
const outPath = path.join(dir, 'manifest.json')
await writeJson(baselinePath, baseline)
await runEmitManifest([
'--slice',
'agents:openclaw',
'--dist-dir',
distDir,
'--merge-from',
baselinePath,
'--out',
outPath,
])
const merged = JSON.parse(await readFile(outPath, 'utf8')) as VmManifest
expect(merged.vmVersion).toBe('2026.04.20')
expect(merged.vmDisk).toEqual(baseline.vmDisk)
expect(merged.agents.openclaw.tarballs.arm64.sha256).toBe(
await sha256File(
path.join(distDir, 'images/openclaw-2026.4.12-arm64.tar.gz'),
),
)
})
it('fails slice emission without a merge baseline', async () => {
dir = await mkdtemp(path.join(tmpdir(), 'browseros-emit-fail-'))
const distDir = path.join(dir, 'dist')
await writeVmFiles(distDir)
const result = await runEmitManifest(
[
'--slice',
'vm',
'--dist-dir',
distDir,
'--out',
path.join(dir, 'out.json'),
],
false,
)
expect(result.code).toBe(1)
expect(result.stderr).toContain('--slice vm requires --merge-from')
})
})
async function writeVmFiles(distDir: string): Promise<void> {
await mkdir(distDir, { recursive: true })
await writeFile(
path.join(distDir, 'browseros-vm-2026.04.22-arm64.qcow2.zst'),
'arm disk',
)
await writeFile(
path.join(distDir, 'browseros-vm-2026.04.22-x64.qcow2.zst'),
'x64 disk',
)
}
async function writeAgentFiles(distDir: string): Promise<void> {
await mkdir(path.join(distDir, 'images'), { recursive: true })
await writeFile(
path.join(distDir, 'images/openclaw-2026.4.12-arm64.tar.gz'),
'arm tarball',
)
await writeFile(
path.join(distDir, 'images/openclaw-2026.4.12-x64.tar.gz'),
'x64 tarball',
)
}
async function writeJson(filePath: string, value: unknown): Promise<void> {
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`)
}
async function runEmitManifest(
args: string[],
expectSuccess = true,
): Promise<{ code: number; stdout: string; stderr: string }> {
const proc = Bun.spawn(
['bun', 'run', 'scripts/emit-manifest.ts', '--', ...args],
{
cwd: path.join(import.meta.dir, '..'),
stdout: 'pipe',
stderr: 'pipe',
},
)
const [stdout, stderr, code] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
])
if (expectSuccess && code !== 0) {
throw new Error(`emit-manifest failed: ${stderr || stdout}`)
}
return { code, stdout, stderr }
}

View File

@@ -0,0 +1,123 @@
import { afterEach, describe, expect, it } from 'bun:test'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
type ArtifactInputs,
type Bundle,
buildManifest,
qcow2Key,
tarballKey,
} from '../scripts/common/manifest'
import { verifySha256 } from '../scripts/common/sha256'
const bundle: Bundle = {
vmVersion: '2026.04.22',
agents: [
{
name: 'openclaw',
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
},
],
}
const inputs: ArtifactInputs = {
vmDisk: {
arm64: { sha256: 'disk-arm', sizeBytes: 11 },
x64: { sha256: 'disk-x64', sizeBytes: 12 },
},
agents: {
openclaw: {
arm64: { sha256: 'tar-arm', sizeBytes: 21 },
x64: { sha256: 'tar-x64', sizeBytes: 22 },
},
},
}
describe('manifest helpers', () => {
it('builds release artifact keys', () => {
expect(qcow2Key('2026.04.22', 'arm64')).toBe(
'vm/browseros-vm-2026.04.22-arm64.qcow2.zst',
)
expect(tarballKey('openclaw', '2026.4.12', 'x64')).toBe(
'vm/images/openclaw-2026.4.12-x64.tar.gz',
)
})
it('builds a manifest from bundle metadata and artifact inputs', () => {
const manifest = buildManifest(
bundle,
inputs,
new Date('2026-04-22T00:00:00.000Z'),
)
expect(manifest).toMatchObject({
schemaVersion: 1,
vmVersion: '2026.04.22',
updatedAt: '2026-04-22T00:00:00.000Z',
vmDisk: {
arm64: {
key: 'vm/browseros-vm-2026.04.22-arm64.qcow2.zst',
sha256: 'disk-arm',
sizeBytes: 11,
},
},
agents: {
openclaw: {
image: 'ghcr.io/openclaw/openclaw',
version: '2026.4.12',
tarballs: {
x64: {
key: 'vm/images/openclaw-2026.4.12-x64.tar.gz',
sha256: 'tar-x64',
sizeBytes: 22,
},
},
},
},
})
})
it('fails when required artifact inputs are missing', () => {
expect(() =>
buildManifest(bundle, {
vmDisk: { arm64: inputs.vmDisk.arm64 } as ArtifactInputs['vmDisk'],
agents: inputs.agents,
}),
).toThrow('missing vmDisk inputs for arch x64')
expect(() =>
buildManifest(bundle, {
vmDisk: inputs.vmDisk,
agents: { openclaw: { arm64: inputs.agents.openclaw.arm64 } },
} as unknown as ArtifactInputs),
).toThrow('missing tarball inputs for openclaw/x64')
})
})
describe('sha256 helpers', () => {
let dir: string | null = null
afterEach(async () => {
if (!dir) return
await rm(dir, { recursive: true, force: true })
dir = null
})
it('verifies matching file content and rejects mismatches', async () => {
dir = await mkdtemp(join(tmpdir(), 'browseros-build-tools-'))
const filePath = join(dir, 'artifact.txt')
await writeFile(filePath, 'browseros\n')
await expect(
verifySha256(
filePath,
'8e4e07174da39a48ab7aa9a1bebd3adcddff43172c0b19fcbe921cc47c599f62',
),
).resolves.toBeUndefined()
await expect(verifySha256(filePath, 'bad')).rejects.toThrow(
'sha256 mismatch',
)
})
})

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"resolveJsonModule": true
},
"include": ["scripts/**/*", "tests/**/*", "package.json", "bundle.json"]
}

View File

@@ -9,6 +9,8 @@
export const PATHS = {
DEFAULT_EXECUTION_DIR: process.cwd(),
BROWSEROS_DIR_NAME: '.browseros',
DEV_BROWSEROS_DIR_NAME: '.browseros-dev',
CACHE_DIR_NAME: 'cache',
MEMORY_DIR_NAME: 'memory',
SESSIONS_DIR_NAME: 'sessions',
TOOL_OUTPUT_DIR_NAME: 'tool-output',

View File

@@ -1,12 +0,0 @@
# Cloudflare R2 — required for `bun run upload`.
# In CI these come from GHA repo secrets; locally put them in `.env` (gitignored)
# or symlink to another `.env` that already has them:
# ln -s ../../../browseros/.env .env
# ln -s ../../apps/server/.env.production .env
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=browseros
# Optional override — only used if you serve artifacts from a different CDN.
# CDN_BASE_URL=https://cdn.browseros.com

View File

@@ -1,131 +0,0 @@
# @browseros/vm-container
Produces the Debian + Podman `qcow2` disk image shipped inside BrowserOS. This
is the WS1 deliverable from `specs/bundled-vm-runtime-spec.md`.
The package owns three surfaces:
- **Build** (`bun run build`) — runs `virt-customize` against a pinned Debian
genericcloud image and emits a `.qcow2.zst` plus a `build-result-<arch>.json`
metadata file.
- **Upload** (`bun run upload`) — publishes pre-built artifacts to R2 under
`vm/<version>/` with a per-version `manifest.json` and a top-level
`latest.json` pointer.
- **Smoke** (`bun run smoke`) — boots a built `.qcow2.zst` in Lima + QEMU and
pings `podman.socket`.
Types under `@browseros/vm-container/schema` are the consumer contract. WS4's
runtime code imports `VmManifest`, `parseManifest`, and the R2 key helpers so
producer and consumer can't drift.
## Requirements
- **Linux host** (libguestfs / `virt-customize` is Linux-only).
- `libguestfs-tools`, `qemu-utils`, `zstd` on `PATH` (`sudo apt-get install …`
on Debian/Ubuntu).
- Bun `^1.3.6`. Run from the monorepo root once with `bun install`.
On macOS, the build step does not run locally — use the GitHub Actions
workflow (`build-vm-container.yml`) or a Linux VM.
## Environment
The upload step needs Cloudflare R2 credentials. Locally put them in `.env`
inside this directory (`.env` is gitignored by the root rule). For CI, the
workflow sets them from repo secrets — no `.env` file needed.
| Variable | Required | Default | Notes |
| ----------------------- | -------- | --------------------------- | ------------------------------------------ |
| `R2_ACCOUNT_ID` | upload | — | |
| `R2_ACCESS_KEY_ID` | upload | — | |
| `R2_SECRET_ACCESS_KEY` | upload | — | |
| `R2_BUCKET` | upload | — | e.g. `browseros` |
| `CDN_BASE_URL` | optional | `https://cdn.browseros.com` | Only affects URLs written into `manifest.json`. An explicit `cdnBaseUrl` option passed to `publishDisks()` overrides this env var. |
If you already have R2 credentials in another repo `.env`, symlink it to avoid
duplicating secrets:
```bash
cd packages/browseros-agent/packages/vm-container
ln -s ../../../browseros/.env .env # Chromium build's .env
# or
ln -s ../../apps/server/.env.production .env
```
## Local build (Linux host)
```bash
cd packages/browseros-agent/packages/vm-container
# Build one arch. virt-customize inherits LIBGUESTFS_BACKEND from the shell.
LIBGUESTFS_BACKEND=direct bun run build -- \
--version 2026.04.22-1 \
--arch x64 \
--output-dir ./dist/x64
# Smoke test the result (limactl on PATH required).
bun run smoke -- --qcow ./dist/x64/browseros-vm-2026.04.22-1-x64.qcow2.zst
# Upload both arches to R2 (requires R2_* env vars).
bun run upload -- --version 2026.04.22-1 --artifact-dir ./dist
```
`--update-latest` is the default on upload; pass `--no-update-latest` to keep
`vm/latest.json` pointing at whatever was there before.
## R2 layout produced
```
r2://$R2_BUCKET/vm/<version>/
browseros-vm-<version>-arm64.qcow2.zst
browseros-vm-<version>-arm64.qcow2.zst.sha256
browseros-vm-<version>-x64.qcow2.zst
browseros-vm-<version>-x64.qcow2.zst.sha256
manifest.json
r2://$R2_BUCKET/vm/latest.json
```
`manifest.json` is the consumer contract — see `src/schema/manifest.ts` for
the zod schema. `latest.json` is a human/debug pointer and is **not**
consumed by `build:server`; WS3's follow-up pins a specific version in
`server-prod-resources.json`.
## CI
The `build-vm-container.yml` workflow runs:
1. Matrix build per arch on `ubuntu-24.04` + `ubuntu-24.04-arm64`.
2. x64 Lima boot smoke test.
3. Gated publish job (runs only on `workflow_dispatch` with `publish=true`).
Triggers: `workflow_dispatch` (manual), `pull_request` on
`packages/vm-container/**` (dry-run, no publish), and a weekly cron to catch
upstream Debian drift.
## Repro + pinning
- Base Debian image is pinned by sha512 in `src/build/base-image.ts` because
Debian's cloud-image snapshots publish `SHA512SUMS` (not `SHA256SUMS`). The
build verifies the download against that pinned sha512, then also computes
a local sha256 of the same bytes and records it as `base_image_sha256` in
the published `manifest.json` so WS4 can verify using the stable contract.
Update both the pin and the upstream version together when bumping to a
newer daily.
- Recipe file (`recipe/browseros-vm.recipe`) is git-tracked and its sha256
lands in `manifest.build.recipe_sha256`.
- Installed package versions are captured post-install via `dpkg-query` and
land in `manifest.packages`.
- `manifest.build.git_sha` / `built_by` record the invocation context.
## What lives here vs. what's elsewhere
- **Here:** disk recipe, build orchestrator, R2 uploader, smoke test, types
consumed by the runtime.
- **Not here:** host-side consumption (`apps/server/.../podman-runtime.ts`),
`server-prod-resources.json` entries, `limactl` binary upload (`packages/
browseros/build/cli/storage.py`), agent container tarballs (WS2 lives in
`packages/browseros-agent/packages/agent-container/` once it lands).
The file baked into `/etc/browseros-vm-manifest.json` is a build-time marker.
The published per-version `manifest.json` is finalized later, after the
artifacts have been hashed and uploaded.

View File

@@ -1,32 +0,0 @@
{
"name": "@browseros/vm-container",
"version": "0.0.0",
"description": "BrowserOS VM disk image producer (Debian + Podman qcow2)",
"type": "module",
"private": true,
"exports": {
"./schema": {
"types": "./src/schema/index.ts",
"default": "./src/schema/index.ts"
},
"./download": {
"types": "./src/download/index.ts",
"default": "./src/download/index.ts"
}
},
"scripts": {
"build": "bun run scripts/build.ts",
"upload": "bun run scripts/upload.ts",
"smoke": "bun run scripts/smoke.ts",
"test": "bun test tests/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.933.0",
"@browseros/shared": "workspace:*",
"zod": "^3.24.2"
},
"devDependencies": {
"aws-sdk-client-mock": "^4.1.0"
}
}

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env bun
import { writeFile } from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import { buildDisk } from '../src/build/orchestrator'
import { parseArch, todayCalver } from '../src/schema/arch'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
version: { type: 'string' },
arch: { type: 'string' },
'output-dir': { type: 'string', default: './dist' },
'base-image-sha256': { type: 'string' },
},
})
if (!values.arch) {
console.error(
'usage: bun run build -- --arch <arm64|x64> [--version <YYYY.MM.DD[-N]>] [--output-dir ./dist] [--base-image-sha256 <sha>]',
)
process.exit(1)
}
const version = values.version ?? todayCalver(Math.floor(Date.now() / 1000))
const result = await buildDisk({
version,
arch: parseArch(values.arch),
outputDir: values['output-dir'] ?? './dist',
baseImageSha256Override: values['base-image-sha256'],
})
const resultPath = path.join(
values['output-dir'] ?? './dist',
`build-result-${result.arch}.json`,
)
await writeFile(resultPath, JSON.stringify(result, null, 2))
console.log(JSON.stringify(result, null, 2))

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bun
import { parseArgs } from 'node:util'
import { bootAndProbe } from '../src/smoke/lima-boot'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
qcow: { type: 'string' },
limactl: { type: 'string' },
},
})
if (!values.qcow) {
console.error(
'usage: bun run smoke -- --qcow <path.qcow2.zst> [--limactl /usr/local/bin/limactl]',
)
process.exit(1)
}
await bootAndProbe(values.qcow, { limactlPath: values.limactl })
console.log('smoke test passed')

View File

@@ -1,80 +0,0 @@
#!/usr/bin/env bun
import { readdir, readFile } from 'node:fs/promises'
import path from 'node:path'
import { parseArgs } from 'node:util'
import type { BuildResult } from '../src/build/types'
import { type Arch, parseArch } from '../src/schema/arch'
import { publishDisks } from '../src/upload/publish'
const { values } = parseArgs({
args: Bun.argv.slice(2),
options: {
version: { type: 'string' },
'artifact-dir': { type: 'string' },
'update-latest': { type: 'boolean', default: true },
'no-update-latest': { type: 'boolean', default: false },
},
})
if (!values.version || !values['artifact-dir']) {
console.error(
'usage: bun run upload -- --version <YYYY.MM.DD-N> --artifact-dir <dir> [--no-update-latest]',
)
process.exit(1)
}
const results = await loadResults(values['artifact-dir'])
if (Object.keys(results).length === 0) {
throw new Error(
`no build-result-*.json files found under ${values['artifact-dir']}`,
)
}
await publishDisks({
version: values.version,
results,
updateLatest: !values['no-update-latest'],
})
console.log(
`published ${Object.keys(results).length} arch(es) for version ${values.version}`,
)
async function loadResults(
dir: string,
): Promise<Partial<Record<Arch, BuildResult>>> {
const out: Partial<Record<Arch, BuildResult>> = {}
for (const file of await walkForResults(dir)) {
const raw = await readFile(file, 'utf8')
const result = JSON.parse(raw) as BuildResult
out[parseArch(result.arch)] = resolvePaths(result, path.dirname(file))
}
return out
}
async function walkForResults(dir: string): Promise<string[]> {
const out: string[] = []
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name)
if (entry.isDirectory()) {
out.push(...(await walkForResults(full)))
} else if (
entry.name.startsWith('build-result-') &&
entry.name.endsWith('.json')
) {
out.push(full)
}
}
return out
}
function resolvePaths(result: BuildResult, dir: string): BuildResult {
const resolve = (p: string): string =>
path.isAbsolute(p) ? p : path.join(dir, path.basename(p))
return {
...result,
rawQcowPath: resolve(result.rawQcowPath),
compressedPath: resolve(result.compressedPath),
buildLogPath: resolve(result.buildLogPath),
}
}

View File

@@ -1,48 +0,0 @@
import type { Arch } from '../schema/arch'
export interface BaseImage {
distro: 'debian'
release: string
channel: 'genericcloud'
upstreamVersion: string
arch: Arch
url: string
sha512: string
}
// Debian bookworm genericcloud snapshot pin. Bump by hand when a newer
// daily is needed; the scheduled base-image-bump workflow (follow-up PR)
// will automate it. Pins are the SHA-512 values from the snapshot's
// SHA512SUMS file at https://cloud.debian.org/images/cloud/bookworm/.
// Debian publishes SHA512SUMS for these snapshots, not SHA256SUMS.
const BOOKWORM_VERSION = '20260413-2447'
const PINNED_SHA512: Record<Arch, string> = {
arm64:
'15ad6c52e255c84eb0e91001c5907b27199d8a7164d8ac172cfe9c92850dfaf606a6c3161d6af7f0fd5a5fef2aa8dcd9a23c2eb0fedbfcddb38e2bc306cba98f',
x64: 'db11b13c4efcc37828ffadae521d101e85079d349e1418074087bb7d306f11caccdc2b0b539d6fd50d623d40a898f83c6137268a048d7700397dc35b7dcbc927',
}
export const DEBIAN_BASE_IMAGES: Record<Arch, BaseImage> = {
arm64: {
distro: 'debian',
release: 'bookworm',
channel: 'genericcloud',
upstreamVersion: BOOKWORM_VERSION,
arch: 'arm64',
url: `https://cloud.debian.org/images/cloud/bookworm/${BOOKWORM_VERSION}/debian-12-genericcloud-arm64-${BOOKWORM_VERSION}.qcow2`,
sha512: PINNED_SHA512.arm64,
},
x64: {
distro: 'debian',
release: 'bookworm',
channel: 'genericcloud',
upstreamVersion: BOOKWORM_VERSION,
arch: 'x64',
url: `https://cloud.debian.org/images/cloud/bookworm/${BOOKWORM_VERSION}/debian-12-genericcloud-amd64-${BOOKWORM_VERSION}.qcow2`,
sha512: PINNED_SHA512.x64,
},
}
export const debianSha512SumsUrl = (upstreamVersion: string): string =>
`https://cloud.debian.org/images/cloud/bookworm/${upstreamVersion}/SHA512SUMS`

View File

@@ -1,272 +0,0 @@
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import { copyFile, readFile, rm, stat, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { $ } from 'bun'
import type { Arch } from '../schema/arch'
import { assertCalver } from '../schema/arch'
import {
type BaseImage,
DEBIAN_BASE_IMAGES,
debianSha512SumsUrl,
} from './base-image'
import {
composeVirtCustomizeArgv,
parsePackagesOutput,
parseRecipe,
} from './recipe'
import type { BuildOptions, BuildResult } from './types'
const DEFAULT_RECIPE_REL = '../../recipe/browseros-vm.recipe'
const SHA512_HEX = /^[a-f0-9]{128}$/
// Bun's file writer type is mildly hostile to `ReadableStream.pipeTo`, so we
// hand-pump chunks through a lightweight sink type.
type ChunkSink = ReturnType<ReturnType<typeof Bun.file>['writer']>
export async function buildDisk(opts: BuildOptions): Promise<BuildResult> {
assertCalver(opts.version)
const base = DEBIAN_BASE_IMAGES[opts.arch]
const pinnedSha512 = await resolvePinnedSha512(base.upstreamVersion, base)
const prepared = await prepareCustomizedDisk(opts, base, pinnedSha512)
const baseImageSha256 = await sha256File(prepared.basePath)
if (
opts.baseImageSha256Override &&
opts.baseImageSha256Override !== baseImageSha256
) {
throw new Error(
`base image sha256 mismatch: override ${opts.baseImageSha256Override}, computed ${baseImageSha256}`,
)
}
const finalized = await finalizeArtifacts(opts, prepared.workPath)
await rm(prepared.workPath, { force: true })
await rm(prepared.basePath, { force: true })
return {
arch: opts.arch,
version: opts.version,
baseImage: { ...base, sha512: pinnedSha512, sha256: baseImageSha256 },
recipeSha256: prepared.recipeSha256,
buildLogPath: prepared.buildLogPath,
rawQcowPath: finalized.rawQcowPath,
rawQcowSha256: finalized.rawQcowSha256,
rawQcowSize: finalized.rawQcowSize,
compressedPath: finalized.compressedPath,
compressedSha256: finalized.compressedSha256,
compressedSize: finalized.compressedSize,
packages: finalized.packages,
}
}
interface PreparedDisk {
basePath: string
workPath: string
buildLogPath: string
recipeSha256: string
}
async function prepareCustomizedDisk(
opts: BuildOptions,
base: BaseImage,
pinnedSha512: string,
): Promise<PreparedDisk> {
await $`mkdir -p ${opts.outputDir}`.quiet()
const basePath = path.join(opts.outputDir, `base-${opts.arch}.qcow2`)
await downloadTo(base.url, basePath)
await verifySha512(basePath, pinnedSha512)
const workPath = path.join(
opts.outputDir,
`work-${opts.version}-${opts.arch}.qcow2`,
)
await copyFile(basePath, workPath)
const recipePath =
opts.recipePath ?? path.resolve(import.meta.dir, DEFAULT_RECIPE_REL)
const recipeText = await readFile(recipePath, 'utf8')
const recipeSha256 = sha256String(recipeText)
const buildMarkerPath = path.join(
opts.outputDir,
`build-marker-${opts.arch}.json`,
)
// The baked JSON is a build-time marker. The published per-version manifest
// adds final provider hashes and URLs after artifact hashing and upload.
await writeFile(
buildMarkerPath,
JSON.stringify(
{
name: 'browseros-vm',
version: opts.version,
arch: opts.arch,
phase: 'build',
},
null,
2,
),
)
const argv = composeVirtCustomizeArgv({
diskPath: workPath,
recipe: parseRecipe(recipeText),
substitutions: { version: opts.version, manifest_tmp: buildMarkerPath },
recipeDir: path.dirname(recipePath),
})
const buildLogPath = path.join(opts.outputDir, `build-${opts.arch}.log`)
await spawnToLog(['virt-customize', ...argv], buildLogPath)
await $`virt-sparsify --in-place ${workPath}`.quiet()
return { basePath, workPath, buildLogPath, recipeSha256 }
}
interface FinalizedArtifacts {
rawQcowPath: string
rawQcowSha256: string
rawQcowSize: number
compressedPath: string
compressedSha256: string
compressedSize: number
packages: Record<string, string>
}
async function finalizeArtifacts(
opts: BuildOptions,
workPath: string,
): Promise<FinalizedArtifacts> {
const rawQcowPath = path.join(
opts.outputDir,
`browseros-vm-${opts.version}-${opts.arch}.qcow2`,
)
await $`qemu-img convert -O qcow2 -c ${workPath} ${rawQcowPath}`.quiet()
const rawQcowSha256 = await sha256File(rawQcowPath)
const rawQcowSize = (await stat(rawQcowPath)).size
const compressedPath = `${rawQcowPath}.zst`
await $`zstd -19 --long=30 -T0 -f -o ${compressedPath} ${rawQcowPath}`.quiet()
const compressedSha256 = await sha256File(compressedPath)
const compressedSize = (await stat(compressedPath)).size
const packages = await readPackagesFromDisk(
workPath,
opts.outputDir,
opts.arch,
)
return {
rawQcowPath,
rawQcowSha256,
rawQcowSize,
compressedPath,
compressedSha256,
compressedSize,
packages,
}
}
async function downloadTo(url: string, dest: string): Promise<void> {
const response = await fetch(url)
if (!response.ok || !response.body) {
throw new Error(`download failed: ${url} (${response.status})`)
}
const sink = Bun.file(dest).writer()
const reader = response.body.getReader()
try {
for (;;) {
const { done, value } = await reader.read()
if (done) break
sink.write(value)
}
} finally {
await sink.end()
}
}
async function verifySha512(filePath: string, expected: string): Promise<void> {
const actual = await hashFile(filePath, 'sha512')
if (actual !== expected) {
throw new Error(
`sha512 mismatch for ${filePath}: expected ${expected}, got ${actual}`,
)
}
}
async function sha256File(filePath: string): Promise<string> {
return hashFile(filePath, 'sha256')
}
async function hashFile(
filePath: string,
algo: 'sha256' | 'sha512',
): Promise<string> {
const hash = createHash(algo)
for await (const chunk of createReadStream(filePath)) hash.update(chunk)
return hash.digest('hex')
}
function sha256String(text: string): string {
return createHash('sha256').update(text).digest('hex')
}
async function spawnToLog(argv: string[], logPath: string): Promise<void> {
const log = Bun.file(logPath).writer()
const proc = Bun.spawn(argv, {
stdout: 'pipe',
stderr: 'pipe',
env: {
...process.env,
LIBGUESTFS_BACKEND: process.env.LIBGUESTFS_BACKEND ?? 'direct',
},
})
await Promise.all([
pumpStream(proc.stdout, log),
pumpStream(proc.stderr, log),
])
const code = await proc.exited
await log.end()
if (code !== 0) {
throw new Error(`${argv[0]} exited ${code}; see ${logPath}`)
}
}
async function pumpStream(
stream: ReadableStream<Uint8Array>,
sink: ChunkSink,
): Promise<void> {
const reader = stream.getReader()
for (;;) {
const { done, value } = await reader.read()
if (done) break
sink.write(value)
}
}
async function readPackagesFromDisk(
diskPath: string,
outputDir: string,
arch: Arch,
): Promise<Record<string, string>> {
const dumpPath = path.join(outputDir, `pkgs-${arch}.txt`)
await $`virt-copy-out -a ${diskPath} /var/lib/browseros-vm-pkg-versions ${outputDir}`.quiet()
await $`mv ${outputDir}/browseros-vm-pkg-versions ${dumpPath}`.quiet()
const text = await readFile(dumpPath, 'utf8')
return parsePackagesOutput(text)
}
async function resolvePinnedSha512(
upstreamVersion: string,
base: BaseImage,
): Promise<string> {
if (SHA512_HEX.test(base.sha512)) return base.sha512
const sumsUrl = debianSha512SumsUrl(upstreamVersion)
const response = await fetch(sumsUrl)
if (!response.ok) throw new Error(`SHA512SUMS fetch failed: ${sumsUrl}`)
const text = await response.text()
const filename = base.url.slice(base.url.lastIndexOf('/') + 1)
for (const line of text.split('\n')) {
const [sha, name] = line.trim().split(/\s+/)
if (name === filename && sha) return sha
}
throw new Error(`SHA512SUMS missing entry for ${filename}`)
}

View File

@@ -1,94 +0,0 @@
import path from 'node:path'
export type RecipeOp =
| { op: 'run-command'; cmd: string }
| { op: 'copy-in'; src: string; dest: string }
| { op: 'write'; dest: string; content: string }
| { op: 'truncate'; target: string }
export function parseRecipe(text: string): RecipeOp[] {
return text
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith('#'))
.map(parseLine)
}
function parseLine(line: string): RecipeOp {
const space = line.indexOf(' ')
if (space === -1) {
throw new Error(`recipe line missing argument: ${line}`)
}
const op = line.slice(0, space)
const arg = line.slice(space + 1).trim()
switch (op) {
case 'run-command':
return { op, cmd: arg }
case 'copy-in': {
const colon = arg.indexOf(':')
if (colon === -1) throw new Error(`copy-in missing ':': ${line}`)
return { op, src: arg.slice(0, colon), dest: arg.slice(colon + 1) }
}
case 'write': {
const colon = arg.indexOf(':')
if (colon === -1) throw new Error(`write missing ':': ${line}`)
return { op, dest: arg.slice(0, colon), content: arg.slice(colon + 1) }
}
case 'truncate':
return { op, target: arg }
default:
throw new Error(`unknown recipe op: ${op}`)
}
}
export interface ComposeOptions {
diskPath: string
recipe: RecipeOp[]
substitutions: Record<string, string>
recipeDir: string
}
export function composeVirtCustomizeArgv(opts: ComposeOptions): string[] {
const substitute = (s: string): string =>
s.replaceAll(/\{(\w+)\}/g, (match, key) => opts.substitutions[key] ?? match)
const argv = ['-a', opts.diskPath]
for (const op of opts.recipe) {
switch (op.op) {
case 'run-command':
argv.push('--run-command', substitute(op.cmd))
break
case 'copy-in': {
const substituted = substitute(op.src)
const resolvedSrc = path.isAbsolute(substituted)
? substituted
: path.join(opts.recipeDir, substituted)
argv.push('--copy-in', `${resolvedSrc}:${op.dest}`)
break
}
case 'write':
argv.push('--write', `${op.dest}:${substitute(op.content)}`)
break
case 'truncate':
argv.push('--truncate', op.target)
break
}
}
argv.push(
'--run-command',
// biome-ignore lint/suspicious/noTemplateCurlyInString: dpkg format placeholder, not JS template
"dpkg-query -W -f='${Package} ${Version}\\n' > /var/lib/browseros-vm-pkg-versions",
)
return argv
}
export function parsePackagesOutput(text: string): Record<string, string> {
const out: Record<string, string> = {}
for (const line of text.split('\n')) {
const trimmed = line.trim()
if (!trimmed) continue
const space = trimmed.indexOf(' ')
if (space === -1) continue
out[trimmed.slice(0, space)] = trimmed.slice(space + 1)
}
return out
}

View File

@@ -1,34 +0,0 @@
import type { Arch } from '../schema/arch'
import type { BaseImage } from './base-image'
// Runtime snapshot of the base image used for this build. `sha512` comes
// from the pin (verified against Debian's signed SHA512SUMS); `sha256` is
// computed locally after download because Debian doesn't publish SHA256
// sidecars. The manifest exposes sha256 to WS4; sha512 is retained for
// upstream-supply-chain traceability.
export interface BuildBaseImage extends BaseImage {
sha256: string
}
export interface BuildResult {
arch: Arch
version: string
baseImage: BuildBaseImage
recipeSha256: string
rawQcowPath: string
rawQcowSha256: string
rawQcowSize: number
compressedPath: string
compressedSha256: string
compressedSize: number
packages: Record<string, string>
buildLogPath: string
}
export interface BuildOptions {
version: string
arch: Arch
outputDir: string
recipePath?: string
baseImageSha256Override?: string
}

View File

@@ -1,27 +0,0 @@
import type { Arch } from '../schema/arch'
import type { VmManifest } from '../schema/manifest'
// WS4 landing pad. Typed signatures only — bodies implemented by WS4 where
// podman-runtime.ts consumes the shipped qcow2. Keeping the types here
// prevents WS4's implementation from drifting from the producer's schema.
export async function downloadManifest(
_version: string | 'latest',
): Promise<VmManifest> {
throw new Error('downloadManifest: implemented in WS4')
}
export async function downloadQcow(
_manifest: VmManifest,
_arch: Arch,
_destPath: string,
): Promise<void> {
throw new Error('downloadQcow: implemented in WS4')
}
export async function verifySha256(
_path: string,
_expected: string,
): Promise<void> {
throw new Error('verifySha256: implemented in WS4')
}

View File

@@ -1,27 +0,0 @@
export const ARCHES = ['arm64', 'x64'] as const
export type Arch = (typeof ARCHES)[number]
export function parseArch(s: string): Arch {
if (s === 'arm64' || s === 'x64') return s
throw new Error(`invalid arch: ${s} (expected 'arm64' | 'x64')`)
}
// YYYY.MM.DD with an optional numeric `-N` suffix (e.g. `-1`).
export const CALVER_REGEX = /^\d{4}\.\d{2}\.\d{2}(-\d+)?$/
export function assertCalver(version: string): void {
if (!CALVER_REGEX.test(version)) {
throw new Error(
`invalid CalVer: ${version} (expected YYYY.MM.DD[-N], e.g. 2026.04.22 or 2026.04.22-1)`,
)
}
}
export function todayCalver(suffix?: number): string {
const now = new Date()
const yyyy = now.getUTCFullYear()
const mm = String(now.getUTCMonth() + 1).padStart(2, '0')
const dd = String(now.getUTCDate()).padStart(2, '0')
const base = `${yyyy}.${mm}.${dd}`
return suffix == null ? base : `${base}-${suffix}`
}

View File

@@ -1,25 +0,0 @@
export type { Arch } from './arch'
export {
ARCHES,
assertCalver,
CALVER_REGEX,
parseArch,
todayCalver,
} from './arch'
export type { LatestPointer, VmManifest, VmProvider } from './manifest'
export {
latestPointerSchema,
MANIFEST_SCHEMA_VERSION,
parseLatestPointer,
parseManifest,
vmManifestSchema,
vmProviderSchema,
} from './manifest'
export {
keyForLatest,
keyForManifest,
keyForQcow,
keyForSha,
qcowFilename,
R2_VM_PREFIX,
} from './r2-keys'

View File

@@ -1,57 +0,0 @@
import { z } from 'zod'
import { ARCHES, CALVER_REGEX } from './arch'
export const MANIFEST_SCHEMA_VERSION = 'v1' as const
const sha256Hex = z.string().regex(/^[a-f0-9]{64}$/)
export const vmProviderSchema = z.object({
arch: z.enum(ARCHES),
filename: z.string().min(1),
format: z.literal('qcow2+zstd'),
compressed_sha256: sha256Hex,
compressed_size_bytes: z.number().int().positive(),
uncompressed_sha256: sha256Hex,
uncompressed_size_bytes: z.number().int().positive(),
base_image_sha256: sha256Hex,
url: z.string().url(),
})
export const vmManifestSchema = z.object({
name: z.literal('browseros-vm'),
version: z.string().regex(CALVER_REGEX),
schema: z.literal(MANIFEST_SCHEMA_VERSION),
build: z.object({
git_sha: z.string().min(1),
git_dirty: z.boolean(),
built_at: z.string().datetime({ offset: true }),
built_by: z.string().min(1),
recipe_sha256: sha256Hex,
}),
base_image: z.object({
distro: z.literal('debian'),
release: z.string().min(1),
channel: z.literal('genericcloud'),
upstream_version: z.string().min(1),
}),
packages: z.record(z.string(), z.string()),
providers: z.array(vmProviderSchema).length(2),
})
export const latestPointerSchema = z.object({
version: z.string().regex(CALVER_REGEX),
updated_at: z.string().datetime({ offset: true }),
url: z.string().url(),
})
export type VmProvider = z.infer<typeof vmProviderSchema>
export type VmManifest = z.infer<typeof vmManifestSchema>
export type LatestPointer = z.infer<typeof latestPointerSchema>
export function parseManifest(raw: unknown): VmManifest {
return vmManifestSchema.parse(raw)
}
export function parseLatestPointer(raw: unknown): LatestPointer {
return latestPointerSchema.parse(raw)
}

View File

@@ -1,17 +0,0 @@
import type { Arch } from './arch'
export const R2_VM_PREFIX = 'vm'
export const qcowFilename = (version: string, arch: Arch): string =>
`browseros-vm-${version}-${arch}.qcow2.zst`
export const keyForQcow = (version: string, arch: Arch): string =>
`${R2_VM_PREFIX}/${version}/${qcowFilename(version, arch)}`
export const keyForSha = (version: string, arch: Arch): string =>
`${keyForQcow(version, arch)}.sha256`
export const keyForManifest = (version: string): string =>
`${R2_VM_PREFIX}/${version}/manifest.json`
export const keyForLatest = (): string => `${R2_VM_PREFIX}/latest.json`

View File

@@ -1,211 +0,0 @@
import { execSync } from 'node:child_process'
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import {
DeleteObjectCommand,
PutObjectCommand,
type S3Client,
} from '@aws-sdk/client-s3'
import type { BuildResult } from '../build/types'
import {
keyForLatest,
keyForManifest,
keyForQcow,
keyForSha,
latestPointerSchema,
MANIFEST_SCHEMA_VERSION,
qcowFilename,
type VmManifest,
type VmProvider,
vmManifestSchema,
} from '../schema'
import type { Arch } from '../schema/arch'
import { createR2Client, getBucket, getCdnBaseUrl } from './r2-client'
export interface PublishOptions {
version: string
results: Partial<Record<Arch, BuildResult>>
updateLatest: boolean
cdnBaseUrl?: string
client?: S3Client
bucket?: string
}
export async function publishDisks(opts: PublishOptions): Promise<void> {
const archs = Object.keys(opts.results) as Arch[]
if (archs.length === 0) throw new Error('publishDisks: no results supplied')
const client = opts.client ?? createR2Client()
const bucket = opts.bucket ?? getBucket()
const cdnBase = opts.cdnBaseUrl ?? getCdnBaseUrl()
const uploaded: string[] = []
try {
const providers: VmProvider[] = []
let reference: BuildResult | undefined
for (const arch of archs) {
const result = opts.results[arch]
if (!result) throw new Error(`missing BuildResult for arch ${arch}`)
reference ??= result
const qcowKey = keyForQcow(opts.version, arch)
const shaKey = keyForSha(opts.version, arch)
await uploadFile(client, bucket, qcowKey, result.compressedPath)
uploaded.push(qcowKey)
await uploadBody(
client,
bucket,
shaKey,
`${result.compressedSha256} ${qcowFilename(opts.version, arch)}\n`,
)
uploaded.push(shaKey)
providers.push({
arch,
filename: qcowFilename(opts.version, arch),
format: 'qcow2+zstd',
compressed_sha256: result.compressedSha256,
compressed_size_bytes: result.compressedSize,
uncompressed_sha256: result.rawQcowSha256,
uncompressed_size_bytes: result.rawQcowSize,
base_image_sha256: result.baseImage.sha256,
url: `${cdnBase}/${qcowKey}`,
})
}
if (!reference)
throw new Error('publishDisks: no results yielded a reference build')
const manifest = buildManifest(opts.version, reference, providers)
const manifestKey = keyForManifest(opts.version)
await uploadBody(
client,
bucket,
manifestKey,
JSON.stringify(manifest, null, 2),
)
uploaded.push(manifestKey)
if (opts.updateLatest) {
const pointer = latestPointerSchema.parse({
version: opts.version,
updated_at: new Date().toISOString(),
url: `${cdnBase}/${manifestKey}`,
})
const latestKey = keyForLatest()
await uploadBody(
client,
bucket,
latestKey,
JSON.stringify(pointer, null, 2),
)
uploaded.push(latestKey)
}
} catch (err) {
await rollback(client, bucket, uploaded)
throw err
}
}
function buildManifest(
version: string,
reference: BuildResult,
providers: VmProvider[],
): VmManifest {
const manifest = {
name: 'browseros-vm' as const,
version,
schema: MANIFEST_SCHEMA_VERSION,
build: {
git_sha: gitSha(),
git_dirty: gitDirty(),
built_at: new Date().toISOString(),
built_by: builtBy(),
recipe_sha256: reference.recipeSha256,
},
base_image: {
distro: 'debian' as const,
release: reference.baseImage.release,
channel: 'genericcloud' as const,
upstream_version: reference.baseImage.upstreamVersion,
},
packages: reference.packages,
providers,
}
return vmManifestSchema.parse(manifest)
}
async function uploadFile(
client: S3Client,
bucket: string,
key: string,
localPath: string,
): Promise<void> {
const { size } = await stat(localPath)
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: createReadStream(localPath),
ContentLength: size,
}),
)
}
async function uploadBody(
client: S3Client,
bucket: string,
key: string,
body: string,
): Promise<void> {
await client.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentLength: Buffer.byteLength(body),
ContentType: key.endsWith('.json')
? 'application/json; charset=utf-8'
: 'text/plain; charset=utf-8',
}),
)
}
async function rollback(
client: S3Client,
bucket: string,
keys: string[],
): Promise<void> {
const settled = await Promise.allSettled(
keys.map((key) =>
client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key })),
),
)
const failures = settled.flatMap((result, index) =>
result.status === 'rejected' ? [keys[index]] : [],
)
if (failures.length > 0) {
console.warn(`rollback left orphaned keys: ${failures.join(', ')}`)
}
}
function gitSha(): string {
try {
return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim()
} catch {
return 'unknown'
}
}
function gitDirty(): boolean {
try {
return (
execSync('git status --porcelain', { encoding: 'utf8' }).trim().length > 0
)
} catch {
return false
}
}
function builtBy(): string {
const workflow = process.env.GITHUB_WORKFLOW
const ref = process.env.GITHUB_REF
if (workflow && ref) return `${workflow}@${ref}`
return process.env.USER ?? 'unknown'
}

View File

@@ -1,27 +0,0 @@
import { S3Client } from '@aws-sdk/client-s3'
export function createR2Client(): S3Client {
const accountId = requireEnv('R2_ACCOUNT_ID')
return new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: requireEnv('R2_ACCESS_KEY_ID'),
secretAccessKey: requireEnv('R2_SECRET_ACCESS_KEY'),
},
})
}
export function getBucket(): string {
return requireEnv('R2_BUCKET')
}
export function getCdnBaseUrl(): string {
return process.env.CDN_BASE_URL ?? 'https://cdn.browseros.com'
}
function requireEnv(name: string): string {
const value = process.env[name]
if (!value) throw new Error(`missing required env var: ${name}`)
return value
}

View File

@@ -1,39 +0,0 @@
import { describe, expect, test } from 'bun:test'
import { ARCHES, assertCalver, parseArch } from '../src/schema/arch'
describe('parseArch', () => {
test('accepts supported arches', () => {
expect(parseArch('arm64')).toBe('arm64')
expect(parseArch('x64')).toBe('x64')
})
test('rejects unsupported arches', () => {
expect(() => parseArch('amd64')).toThrow(/invalid arch/)
expect(() => parseArch('')).toThrow(/invalid arch/)
})
test('is case-sensitive — uppercase is rejected', () => {
expect(() => parseArch('ARM64')).toThrow(/invalid arch/)
expect(() => parseArch('X64')).toThrow(/invalid arch/)
})
test('ARCHES contains exactly the supported set', () => {
expect([...ARCHES].sort()).toEqual(['arm64', 'x64'])
})
})
describe('assertCalver', () => {
test('accepts YYYY.MM.DD and YYYY.MM.DD-N', () => {
assertCalver('2026.04.22')
assertCalver('2026.04.22-1')
assertCalver('2026.12.01-99')
})
test('rejects semver and stray strings', () => {
expect(() => assertCalver('1.2.3')).toThrow(/invalid CalVer/)
expect(() => assertCalver('2026-04-22')).toThrow(/invalid CalVer/)
expect(() => assertCalver('2026.04.22-dev1')).toThrow(/invalid CalVer/)
expect(() => assertCalver('2026.04.22-rc1')).toThrow(/invalid CalVer/)
expect(() => assertCalver('latest')).toThrow(/invalid CalVer/)
})
})

View File

@@ -1,10 +0,0 @@
import { describe, expect, test } from 'bun:test'
import { DEBIAN_BASE_IMAGES } from '../src/build/base-image'
describe('DEBIAN_BASE_IMAGES', () => {
test('pins valid SHA-512 digests for each arch', () => {
for (const image of Object.values(DEBIAN_BASE_IMAGES)) {
expect(image.sha512).toMatch(/^[a-f0-9]{128}$/)
}
})
})

View File

@@ -1,122 +0,0 @@
import { describe, expect, test } from 'bun:test'
import type { VmManifest } from '../src/schema/manifest'
import {
MANIFEST_SCHEMA_VERSION,
parseLatestPointer,
parseManifest,
} from '../src/schema/manifest'
const sha = (c: string): string => c.repeat(64).slice(0, 64)
const validManifest: VmManifest = {
name: 'browseros-vm',
version: '2026.04.22-1',
schema: MANIFEST_SCHEMA_VERSION,
build: {
git_sha: 'abc123',
git_dirty: false,
built_at: '2026-04-22T00:00:00.000Z',
built_by: 'operator',
recipe_sha256: sha('a'),
},
base_image: {
distro: 'debian',
release: 'bookworm',
channel: 'genericcloud',
upstream_version: '20260413-2447',
},
packages: { podman: '4.3.1+ds1-8+deb12u1' },
providers: [
{
arch: 'arm64',
filename: 'browseros-vm-2026.04.22-1-arm64.qcow2.zst',
format: 'qcow2+zstd',
compressed_sha256: sha('b'),
compressed_size_bytes: 200_000_000,
uncompressed_sha256: sha('c'),
uncompressed_size_bytes: 500_000_000,
base_image_sha256: sha('d'),
url: 'https://cdn.browseros.com/vm/2026.04.22-1/browseros-vm-2026.04.22-1-arm64.qcow2.zst',
},
{
arch: 'x64',
filename: 'browseros-vm-2026.04.22-1-x64.qcow2.zst',
format: 'qcow2+zstd',
compressed_sha256: sha('e'),
compressed_size_bytes: 210_000_000,
uncompressed_sha256: sha('f'),
uncompressed_size_bytes: 520_000_000,
base_image_sha256: sha('d'),
url: 'https://cdn.browseros.com/vm/2026.04.22-1/browseros-vm-2026.04.22-1-x64.qcow2.zst',
},
],
}
describe('parseManifest', () => {
test('accepts a valid manifest', () => {
expect(parseManifest(validManifest)).toEqual(validManifest)
})
test('rejects bad CalVer', () => {
expect(() =>
parseManifest({ ...validManifest, version: '1.2.3' }),
).toThrow()
})
test('rejects unknown schema version', () => {
expect(() => parseManifest({ ...validManifest, schema: 'v2' })).toThrow()
})
test('rejects short sha256', () => {
const bad = {
...validManifest,
build: { ...validManifest.build, recipe_sha256: 'tooshort' },
}
expect(() => parseManifest(bad)).toThrow()
})
test('rejects invalid timestamps', () => {
expect(() =>
parseManifest({
...validManifest,
build: { ...validManifest.build, built_at: 'not-a-date' },
}),
).toThrow()
})
test('rejects less than 2 providers', () => {
const bad = { ...validManifest, providers: [validManifest.providers[0]] }
expect(() => parseManifest(bad)).toThrow()
})
})
describe('parseLatestPointer', () => {
test('accepts valid pointer', () => {
const pointer = {
version: '2026.04.22-1',
updated_at: '2026-04-22T00:00:00.000Z',
url: 'https://cdn.browseros.com/vm/2026.04.22-1/manifest.json',
}
expect(parseLatestPointer(pointer)).toEqual(pointer)
})
test('rejects bad CalVer', () => {
expect(() =>
parseLatestPointer({
version: 'latest',
updated_at: '2026-04-22T00:00:00.000Z',
url: 'https://cdn.browseros.com/vm/latest/manifest.json',
}),
).toThrow()
})
test('rejects invalid timestamps', () => {
expect(() =>
parseLatestPointer({
version: '2026.04.22-1',
updated_at: 'yesterday',
url: 'https://cdn.browseros.com/vm/2026.04.22-1/manifest.json',
}),
).toThrow()
})
})

View File

@@ -1,187 +0,0 @@
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import {
DeleteObjectCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3'
import { mockClient } from 'aws-sdk-client-mock'
import type { BuildResult } from '../src/build/types'
import type { Arch } from '../src/schema/arch'
import { publishDisks } from '../src/upload/publish'
// aws-sdk-client-mock's own @smithy/types can lag @aws-sdk/client-s3, so its
// typed signatures reject our command classes at compile time even though
// they work at runtime. `as never` sidesteps the version skew.
const PutCmd = PutObjectCommand as never
const DeleteCmd = DeleteObjectCommand as never
const s3Mock = mockClient(S3Client as never)
const sha = (c: string): string => c.repeat(64).slice(0, 64)
let workDir: string
beforeEach(async () => {
s3Mock.reset()
s3Mock.on(PutCmd).resolves({})
s3Mock.on(DeleteCmd).resolves({})
workDir = await mkdtemp(path.join(tmpdir(), 'publish-test-'))
})
afterEach(async () => {
await rm(workDir, { recursive: true, force: true })
})
async function makeResult(arch: Arch): Promise<BuildResult> {
const compressed = path.join(
workDir,
`browseros-vm-2026.04.22-1-${arch}.qcow2.zst`,
)
await writeFile(compressed, Buffer.from('fake-compressed-data'))
return {
arch,
version: '2026.04.22-1',
baseImage: {
distro: 'debian',
release: 'bookworm',
channel: 'genericcloud',
upstreamVersion: '20260413-2447',
arch,
url: `https://cloud.debian.org/.../${arch}.qcow2`,
sha512: sha('d').repeat(2),
sha256: sha('d'),
},
recipeSha256: sha('a'),
rawQcowPath: path.join(workDir, `browseros-vm-2026.04.22-1-${arch}.qcow2`),
rawQcowSha256: sha('b'),
rawQcowSize: 500_000_000,
compressedPath: compressed,
compressedSha256: sha('c'),
compressedSize: 200_000_000,
packages: { podman: '4.3.1-1' },
buildLogPath: path.join(workDir, `build-${arch}.log`),
}
}
const client = new S3Client({})
const bucket = 'test-bucket'
const version = '2026.04.22-1'
describe('publishDisks', () => {
test('happy path uploads qcow + sha per arch, then manifest, then latest', async () => {
const results = {
arm64: await makeResult('arm64'),
x64: await makeResult('x64'),
}
await publishDisks({ version, results, updateLatest: true, client, bucket })
const puts = s3Mock.commandCalls(PutCmd)
const keys = puts.map((c) => c.args[0].input.Key as string)
expect(keys).toEqual([
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst`,
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst.sha256`,
`vm/${version}/browseros-vm-${version}-x64.qcow2.zst`,
`vm/${version}/browseros-vm-${version}-x64.qcow2.zst.sha256`,
`vm/${version}/manifest.json`,
'vm/latest.json',
])
const sidecarUpload = puts.find(
(call) =>
call.args[0].input.Key ===
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst.sha256`,
)
expect(sidecarUpload?.args[0].input.ContentType).toBe(
'text/plain; charset=utf-8',
)
const manifestUpload = puts.find(
(call) => call.args[0].input.Key === `vm/${version}/manifest.json`,
)
expect(manifestUpload?.args[0].input.ContentType).toBe(
'application/json; charset=utf-8',
)
expect(s3Mock.commandCalls(DeleteCmd).length).toBe(0)
})
test('updateLatest: false omits latest.json', async () => {
const results = {
arm64: await makeResult('arm64'),
x64: await makeResult('x64'),
}
await publishDisks({
version,
results,
updateLatest: false,
client,
bucket,
})
const keys = s3Mock
.commandCalls(PutCmd)
.map((c) => c.args[0].input.Key as string)
expect(keys).not.toContain('vm/latest.json')
})
test('rollback deletes already-uploaded keys on failure', async () => {
s3Mock.reset()
let callCount = 0
s3Mock.on(PutCmd).callsFake(() => {
callCount += 1
if (callCount > 2) throw new Error('simulated R2 failure')
return {}
})
s3Mock.on(DeleteCmd).resolves({})
const results = {
arm64: await makeResult('arm64'),
x64: await makeResult('x64'),
}
await expect(
publishDisks({ version, results, updateLatest: true, client, bucket }),
).rejects.toThrow(/simulated R2 failure/)
const deletedKeys = s3Mock
.commandCalls(DeleteCmd)
.map((c) => c.args[0].input.Key as string)
.sort()
expect(deletedKeys).toEqual([
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst`,
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst.sha256`,
])
})
test('rollback on mid-manifest failure deletes every qcow+sha already uploaded', async () => {
s3Mock.reset()
s3Mock.on(PutCmd).callsFake((input) => {
if (input.Key?.endsWith('manifest.json')) {
throw new Error('simulated manifest upload failure')
}
return {}
})
s3Mock.on(DeleteCmd).resolves({})
const results = {
arm64: await makeResult('arm64'),
x64: await makeResult('x64'),
}
await expect(
publishDisks({ version, results, updateLatest: true, client, bucket }),
).rejects.toThrow(/simulated manifest upload failure/)
const deletedKeys = s3Mock
.commandCalls(DeleteCmd)
.map((c) => c.args[0].input.Key as string)
.sort()
expect(deletedKeys).toEqual([
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst`,
`vm/${version}/browseros-vm-${version}-arm64.qcow2.zst.sha256`,
`vm/${version}/browseros-vm-${version}-x64.qcow2.zst`,
`vm/${version}/browseros-vm-${version}-x64.qcow2.zst.sha256`,
])
const uploadedKeys = s3Mock
.commandCalls(PutCmd)
.filter((c) => !(c.args[0].input.Key as string).endsWith('manifest.json'))
.map((c) => c.args[0].input.Key as string)
expect(uploadedKeys).not.toContain('vm/latest.json')
})
})

View File

@@ -1,41 +0,0 @@
import { describe, expect, test } from 'bun:test'
import {
keyForLatest,
keyForManifest,
keyForQcow,
keyForSha,
qcowFilename,
R2_VM_PREFIX,
} from '../src/schema/r2-keys'
describe('R2 key helpers', () => {
const version = '2026.04.22-1'
test('qcowFilename shape', () => {
expect(qcowFilename(version, 'arm64')).toBe(
'browseros-vm-2026.04.22-1-arm64.qcow2.zst',
)
expect(qcowFilename(version, 'x64')).toBe(
'browseros-vm-2026.04.22-1-x64.qcow2.zst',
)
})
test('keyForQcow is versioned', () => {
expect(keyForQcow(version, 'arm64')).toBe(
`${R2_VM_PREFIX}/${version}/browseros-vm-${version}-arm64.qcow2.zst`,
)
})
test('keyForSha appends .sha256', () => {
expect(keyForSha(version, 'x64')).toBe(
`${keyForQcow(version, 'x64')}.sha256`,
)
})
test('keyForManifest + keyForLatest', () => {
expect(keyForManifest(version)).toBe(
`${R2_VM_PREFIX}/${version}/manifest.json`,
)
expect(keyForLatest()).toBe(`${R2_VM_PREFIX}/latest.json`)
})
})

View File

@@ -1,121 +0,0 @@
import { describe, expect, test } from 'bun:test'
import {
composeVirtCustomizeArgv,
parsePackagesOutput,
parseRecipe,
} from '../src/build/recipe'
describe('parseRecipe', () => {
test('skips comments and blank lines, parses all four ops', () => {
const text = `
# comment
run-command apt-get update
copy-in auth.json:/etc/containers/
write /etc/browseros-vm-version:{version}
truncate /etc/machine-id
`
expect(parseRecipe(text)).toEqual([
{ op: 'run-command', cmd: 'apt-get update' },
{ op: 'copy-in', src: 'auth.json', dest: '/etc/containers/' },
{ op: 'write', dest: '/etc/browseros-vm-version', content: '{version}' },
{ op: 'truncate', target: '/etc/machine-id' },
])
})
test('rejects unknown ops', () => {
expect(() => parseRecipe('unknown-op something')).toThrow(
/unknown recipe op/,
)
})
test('write with colon in content keeps everything after first colon', () => {
expect(parseRecipe('write /etc/x:a:b:c')).toEqual([
{ op: 'write', dest: '/etc/x', content: 'a:b:c' },
])
})
})
describe('composeVirtCustomizeArgv', () => {
test('substitutes variables and resolves copy-in relative to recipeDir', () => {
const argv = composeVirtCustomizeArgv({
diskPath: '/work/disk.qcow2',
recipe: [
{ op: 'run-command', cmd: 'echo {version}' },
{ op: 'copy-in', src: 'auth.json', dest: '/etc/' },
{ op: 'write', dest: '/etc/version', content: '{version}' },
{ op: 'truncate', target: '/etc/machine-id' },
],
substitutions: { version: '2026.04.22-1' },
recipeDir: '/recipe',
})
expect(argv).toEqual([
'-a',
'/work/disk.qcow2',
'--run-command',
'echo 2026.04.22-1',
'--copy-in',
'/recipe/auth.json:/etc/',
'--write',
'/etc/version:2026.04.22-1',
'--truncate',
'/etc/machine-id',
'--run-command',
// biome-ignore lint/suspicious/noTemplateCurlyInString: dpkg format placeholder, not JS template
"dpkg-query -W -f='${Package} ${Version}\\n' > /var/lib/browseros-vm-pkg-versions",
])
})
test('absolute copy-in paths are passed through', () => {
const argv = composeVirtCustomizeArgv({
diskPath: '/disk.qcow2',
recipe: [
{ op: 'copy-in', src: '/tmp/manifest.json', dest: '/etc/m.json' },
],
substitutions: {},
recipeDir: '/recipe',
})
expect(argv).toContain('--copy-in')
expect(argv).toContain('/tmp/manifest.json:/etc/m.json')
})
test('copy-in resolves absolutes from substituted placeholders', () => {
const argv = composeVirtCustomizeArgv({
diskPath: '/disk.qcow2',
recipe: [{ op: 'copy-in', src: '{manifest_tmp}', dest: '/etc/m.json' }],
substitutions: { manifest_tmp: '/tmp/vm-dist/manifest-stub.json' },
recipeDir: '/recipe',
})
expect(argv).toContain('/tmp/vm-dist/manifest-stub.json:/etc/m.json')
expect(argv.join(' ')).not.toContain('/recipe/tmp/')
})
test('unresolved substitutions pass through unchanged', () => {
const argv = composeVirtCustomizeArgv({
diskPath: '/disk.qcow2',
recipe: [
{ op: 'run-command', cmd: 'echo {missing}' },
{ op: 'write', dest: '/etc/x', content: '{also_missing}' },
],
substitutions: { version: '2026.04.22-1' },
recipeDir: '/recipe',
})
expect(argv).toContain('echo {missing}')
expect(argv).toContain('/etc/x:{also_missing}')
})
})
describe('parsePackagesOutput', () => {
test('parses dpkg-query output', () => {
const text = `podman 4.3.1+ds1-8+deb12u1
crun 1.8.1-1+deb12u1
fuse-overlayfs 1.10-1+b1
`
expect(parsePackagesOutput(text)).toEqual({
podman: '4.3.1+ds1-8+deb12u1',
crun: '1.8.1-1+deb12u1',
'fuse-overlayfs': '1.10-1+b1',
})
})
})

View File

@@ -1,7 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "."
},
"include": ["src/**/*", "scripts/**/*", "tests/**/*"]
}