Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ jobs:
grep -q 'x86_64-pc-windows-msvc' .github/workflows/release.yml
grep -q 'environment: macos-release' .github/workflows/release.yml
grep -q './scripts/sign_and_notarize_macos.sh' .github/workflows/release.yml
grep -q 'environment: cos-release' .github/workflows/release.yml
grep -q './scripts/upload-release-to-cos.sh cos-upload browser-cli' .github/workflows/release.yml
grep -q 'needs: \[build-macos, build-windows\]' .github/workflows/release.yml
! grep -q 'skills/lexmount-browser/bin/browser-cli.exe' .github/workflows/release.yml
- run: bash -n scripts/sign_and_notarize_macos.sh
! grep -q 'skills/lexmount-browser/bin/' .github/workflows/release.yml
- run: bash -n scripts/sign_and_notarize_macos.sh scripts/upload-release-to-cos.sh
- run: sh -n scripts/package-skill.sh skills/lexmount-browser/scripts/bootstrap.sh skills/lexmount-browser/scripts/doctor.sh
- name: Verify Skill platform selection and PATH isolation
run: |
Expand All @@ -43,22 +45,18 @@ jobs:
test "$output" = '{"ok":false,"error":"unsupported_platform","message":"This Skill supports macOS arm64 through scripts/doctor.sh and Windows x64 through scripts/doctor.ps1."}'
! grep -q 'command -v browser-cli' skills/lexmount-browser/scripts/doctor.sh
! grep -q 'Get-Command browser-cli' skills/lexmount-browser/scripts/doctor.ps1
- name: Verify Python-free packaging contract
- name: Verify runtime packaging contract
run: |
! grep -R -E 'python3[[:space:]]|python[[:space:]]+-m|uv[[:space:]]' scripts skills .github/workflows
- name: Stage Skill package fixtures
- name: Stage excluded Skill package fixtures
run: |
mkdir -p skills/lexmount-browser/bin
printf 'macOS arm64 package fixture' > skills/lexmount-browser/bin/browser-cli
# This residual file must not enter the Skill ZIP until Windows signing is available.
printf 'Windows x64 package fixture' > skills/lexmount-browser/bin/browser-cli.exe
- run: ./scripts/package-skill.sh
- run: unzip -t dist/lexmount-browser.zip
- name: Verify SkillHub ZIP root
run: |
unzip -Z1 dist/lexmount-browser.zip | grep -qx 'SKILL.md'
! unzip -Z1 dist/lexmount-browser.zip | grep -q '^lexmount-browser/'
unzip -Z1 dist/lexmount-browser.zip | grep -qx 'bin/browser-cli'
! unzip -Z1 dist/lexmount-browser.zip | grep -q 'browser-cli.exe'
test "$(unzip -Z1 dist/lexmount-browser.zip | grep '^bin/' | wc -l)" -eq 1
zipinfo -l dist/lexmount-browser.zip | grep -q -- '^-rwxr-xr-x.*bin/browser-cli$'
! unzip -Z1 dist/lexmount-browser.zip | grep -q '^bin/'
22 changes: 18 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
publish:
needs: [build-macos, build-windows]
runs-on: ubuntu-latest
environment: cos-release
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v4
Expand All @@ -70,14 +71,27 @@ jobs:
- name: Package SkillHub upload ZIP
run: |
version="${GITHUB_REF_NAME#v}"
install -D -m 0755 \
"browser-cli-v${version}-aarch64-apple-darwin" \
skills/lexmount-browser/bin/browser-cli
file skills/lexmount-browser/bin/browser-cli | grep -q 'Mach-O 64-bit.*arm64'
./scripts/package-skill.sh
cp dist/lexmount-browser.zip "lexmount-browser-v${version}-skillhub.zip"
sha256sum "lexmount-browser-v${version}-skillhub.zip" > "lexmount-browser-v${version}-skillhub.zip.sha256"
- run: cat *.sha256 | sort -k2 > SHA256SUMS
- name: Upload platform binaries to Tencent Cloud COS
env:
TENCENT_CLOUD_SECRET_ID: ${{ secrets.TENCENT_CLOUD_SECRET_ID }}
TENCENT_CLOUD_SECRET_KEY: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }}
COS_BUCKET: ${{ vars.COS_BUCKET }}
COS_REGION: ${{ vars.COS_REGION }}
COS_PUBLIC_BASE_URL: ${{ vars.COS_PUBLIC_BASE_URL }}
COS_OBJECT_PREFIX: ${{ vars.COS_OBJECT_PREFIX }}
run: |
version="${GITHUB_REF_NAME#v}"
mkdir cos-upload
cp \
"browser-cli-v${version}-aarch64-apple-darwin" \
"browser-cli-v${version}-x86_64-pc-windows-msvc.exe" \
cos-upload/
(cd cos-upload && sha256sum browser-cli-v* | sort -k2 > SHA256SUMS)
./scripts/upload-release-to-cos.sh cos-upload browser-cli "${version}"
- run: rm -f -- *.sha256
- uses: softprops/action-gh-release@v2
with:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lexmount-browser"
version = "1.1.9"
version = "1.1.10"
edition = "2024"
license = "MIT"
description = "Native Rust SDK and CLI for Lexmount cloud browsers"
Expand Down
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,15 @@ surface.
The publishable Skill is in `skills/lexmount-browser`. Build a deterministic ZIP:

```bash
mkdir -p skills/lexmount-browser/bin
cp /path/to/macos-arm64/browser-cli skills/lexmount-browser/bin/browser-cli
./scripts/package-skill.sh
```

The ZIP contains `SKILL.md` at its archive root plus the signed macOS arm64
binary at `bin/browser-cli`. It deliberately excludes the unsigned Windows x64
executable so SkillHub does not reject or strip the package. Tagged releases
publish it as `lexmount-browser-v<VERSION>-skillhub.zip` alongside the standalone
platform binaries and `SHA256SUMS`.

The Skill uses the bundled binary on macOS arm64. On Windows x64,
`bootstrap.ps1` downloads the pinned GitHub Release executable and verifies its
SHA-256 digest before use. The macOS bootstrap remains a missing-binary fallback.
Set `LEXMOUNT_BROWSER_CLI_VERSION` only when testing a different published
release.
The ZIP contains `SKILL.md`, references, and platform bootstrap scripts at its
archive root. Native executables are published separately and are not placed in
the Skill ZIP. On first use, the matching bootstrap script downloads the pinned
release from Tencent Cloud COS and verifies its SHA-256 digest. Set
`LEXMOUNT_BROWSER_CLI_VERSION` or `LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL` only
when testing a different published release or mirror.

Published binaries are intentionally limited to two targets: macOS arm64 and
Windows x64. The macOS binary is signed with a Developer ID Application
Expand All @@ -53,3 +46,9 @@ from the `macos-release` GitHub environment. It requires
`MACOS_DEVELOPER_ID_APPLICATION_P12_BASE64`,
`MACOS_DEVELOPER_ID_P12_PASSWORD`, `APPLE_NOTARY_APPLE_ID`,
`APPLE_NOTARY_TEAM_ID`, and `APPLE_NOTARY_APP_PASSWORD`.

The publish job uploads both platform binaries and their checksum manifest to
Tencent Cloud COS through the `cos-release` GitHub environment. It requires
`TENCENT_CLOUD_SECRET_ID` and `TENCENT_CLOUD_SECRET_KEY` secrets plus
`COS_BUCKET`, `COS_REGION`, `COS_PUBLIC_BASE_URL`, and `COS_OBJECT_PREFIX`
variables.
10 changes: 1 addition & 9 deletions scripts/package-skill.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,9 @@ trap 'rm -rf "$staging_dir"' EXIT INT TERM
mkdir -p "$dist_dir"
rm -f "$dist_dir/lexmount-browser.zip"

required_binary="$skill_dir/bin/browser-cli"
if [ ! -f "$required_binary" ]; then
echo "Missing required Skill binary: $required_binary" >&2
exit 1
fi

(
cd "$skill_dir"
find . -type f ! -name '.DS_Store' \
\( ! -path './bin/*' -o -path './bin/browser-cli' \) \
find . -type f ! -name '.DS_Store' ! -path './bin/*' \
-print | LC_ALL=C sort |
while IFS= read -r relative_path; do
mkdir -p "$staging_dir/$(dirname -- "$relative_path")"
Expand All @@ -28,7 +21,6 @@ fi
find "$staging_dir" -type d -exec chmod 0755 {} +
find "$staging_dir" -type f -exec chmod 0644 {} +
find "$staging_dir/scripts" -type f \( -name '*.sh' -o -name '*.ps1' \) -exec chmod 0755 {} +
find "$staging_dir/bin" -type f -exec chmod 0755 {} +
find "$staging_dir" -exec touch -t 198001010000 {} +

(
Expand Down
58 changes: 58 additions & 0 deletions scripts/upload-release-to-cos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail

source_dir="${1:?Usage: upload-release-to-cos.sh SOURCE_DIR PRODUCT VERSION}"
product="${2:?Usage: upload-release-to-cos.sh SOURCE_DIR PRODUCT VERSION}"
version="${3:?Usage: upload-release-to-cos.sh SOURCE_DIR PRODUCT VERSION}"

: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
: "${TENCENT_CLOUD_SECRET_ID:?Missing TENCENT_CLOUD_SECRET_ID}"
: "${TENCENT_CLOUD_SECRET_KEY:?Missing TENCENT_CLOUD_SECRET_KEY}"
: "${COS_BUCKET:?Missing COS_BUCKET}"
: "${COS_REGION:?Missing COS_REGION}"
: "${COS_PUBLIC_BASE_URL:?Missing COS_PUBLIC_BASE_URL}"
: "${COS_OBJECT_PREFIX:?Missing COS_OBJECT_PREFIX}"

[[ -d "${source_dir}" ]] || { echo "Source directory not found: ${source_dir}" >&2; exit 2; }
[[ -f "${source_dir}/SHA256SUMS" ]] || { echo "Checksum manifest not found: ${source_dir}/SHA256SUMS" >&2; exit 2; }
[[ "${product}" =~ ^[a-z0-9-]+$ ]] || { echo "Invalid product path: ${product}" >&2; exit 2; }
[[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "Invalid version: ${version}" >&2; exit 2; }

coscli_version="1.0.8"
coscli_name="coscli-v${coscli_version}-linux-amd64"
coscli_path="${RUNNER_TEMP}/${coscli_name}"
coscli_sha256="7165f2ae16c5f7ac495864c963ca574a76e04ec72680d7bc8a8eee3234d8cf91"
verify_dir="${RUNNER_TEMP}/${product}-cos-verify"
object_prefix="${COS_OBJECT_PREFIX#/}"
object_prefix="${object_prefix%/}"
remote_path="${object_prefix}/${product}/v${version}"
public_url="${COS_PUBLIC_BASE_URL%/}/${remote_path}"

curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location --retry 3 \
"https://github.com/tencentyun/coscli/releases/download/v${coscli_version}/${coscli_name}" \
--output "${coscli_path}"
printf '%s %s\n' "${coscli_sha256}" "${coscli_path}" | sha256sum --check --strict
chmod 0700 "${coscli_path}"

"${coscli_path}" cp "${source_dir}/" "cos://${COS_BUCKET}/${remote_path}/" \
--recursive \
--endpoint "cos.${COS_REGION}.myqcloud.com" \
--secret-id "${TENCENT_CLOUD_SECRET_ID}" \
--secret-key "${TENCENT_CLOUD_SECRET_KEY}" \
--init-skip=true \
--disable-log

mkdir -p "${verify_dir}"
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location --retry 5 \
"${public_url}/SHA256SUMS" --output "${verify_dir}/SHA256SUMS"
cmp "${source_dir}/SHA256SUMS" "${verify_dir}/SHA256SUMS"

while read -r expected file_name; do
file_name="${file_name#\*}"
[[ "${file_name}" != */* && -n "${file_name}" ]] || { echo "Invalid manifest filename: ${file_name}" >&2; exit 3; }
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location --retry 5 \
"${public_url}/${file_name}" --output "${verify_dir}/${file_name}"
printf '%s %s\n' "${expected}" "${verify_dir}/${file_name}" | sha256sum --check --strict
done <"${source_dir}/SHA256SUMS"

echo "Uploaded and publicly verified ${product} v${version} at ${public_url}"
8 changes: 5 additions & 3 deletions skills/lexmount-browser/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ description: Use Lexmount cloud browsers to open and interact with JavaScript-he

Select the native Rust binary for the current platform:

- macOS arm64: use the bundled `${CODEBUDDY_SKILL_DIR}/bin/browser-cli`.
- Windows x64: run `${CODEBUDDY_SKILL_DIR}/scripts/bootstrap.ps1` when `${CODEBUDDY_SKILL_DIR}/bin/browser-cli.exe` is missing, then use that downloaded file. The bootstrap script downloads the fixed release version and verifies its SHA-256 digest.
- macOS arm64: run `${CODEBUDDY_SKILL_DIR}/scripts/bootstrap.sh` when `${CODEBUDDY_SKILL_DIR}/bin/browser-cli` is missing, then use that file.
- Windows x64: run `${CODEBUDDY_SKILL_DIR}/scripts/bootstrap.ps1` when `${CODEBUDDY_SKILL_DIR}/bin/browser-cli.exe` is missing, then use that file.

Both bootstrap scripts download the fixed release version from Tencent Cloud COS and verify its SHA-256 digest.

Do not run the binary for the other platform. Both platform binaries emit JSON. The examples below abbreviate the selected path as `browser-cli`; resolve it before running commands.

## Setup

1. On macOS arm64, run `doctor.sh`. Use the bundled binary; run `bootstrap.sh` only as a missing-binary fallback.
1. On macOS arm64, run `bootstrap.sh` if `bin/browser-cli` is missing, then run `doctor.sh`.
2. On Windows x64, run `bootstrap.ps1` if `bin/browser-cli.exe` is missing, then run `doctor.ps1`.
3. If credentials are missing, run `browser-cli auth login`. Let the user approve in their browser. Never ask them to paste an API key into chat.
4. Run `browser-cli doctor` again. Continue only when `ready_for_browser_actions` is true.
Expand Down
5 changes: 3 additions & 2 deletions skills/lexmount-browser/scripts/bootstrap.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
$ErrorActionPreference = "Stop"
$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.1.9" }
$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.1.10" }
$downloadBaseUrl = if ($env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL) { $env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL.TrimEnd('/') } else { "https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli" }
$architecture = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
if ($architecture -ne "AMD64") { throw "Only Windows x64 is supported" }
$asset = "browser-cli-v$version-x86_64-pc-windows-msvc.exe"
$repo = "https://github.com/lexmount/browser-cli-rs/releases/download/v$version"
$repo = "$downloadBaseUrl/v$version"
$tmp = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString())
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
Expand Down
5 changes: 3 additions & 2 deletions skills/lexmount-browser/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/bin/sh
set -eu

version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.1.9}"
repo="https://github.com/lexmount/browser-cli-rs/releases/download/v${version}"
version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.1.10}"
download_base_url="${LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL:-https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli}"
repo="${download_base_url%/}/v${version}"
case "$(uname -s)-$(uname -m)" in
Darwin-arm64) target="aarch64-apple-darwin" ;;
*) echo "Unsupported platform: $(uname -s) $(uname -m). This release supports macOS arm64 and Windows x86_64." >&2; exit 2 ;;
Expand Down