feat(skills): add browser control installer skill
This commit is contained in:
parent
447b57e46a
commit
ac038867d2
45
.claude/skills/install-browser-control/SKILL.md
Normal file
45
.claude/skills/install-browser-control/SKILL.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
name: install-browser-control
|
||||
description: 在新 Mac 上为 Codex 和 Claude 全局安装或整理真实 Chrome/Chromium 调试能力时使用;适用于需要 connect-chrome、browse、setup-browser-cookies 三个入口、可见浏览器接管、DOM 操作、network/console 检查与 cookie 导入支持的场景。
|
||||
---
|
||||
|
||||
# Install Browser Control
|
||||
|
||||
当用户要求在新机器上把浏览器接管调试能力装完整时,直接执行本 skill。
|
||||
|
||||
## 目标
|
||||
|
||||
- 自动识别 `CODEX_HOME`,默认回退到 `~/.codex`
|
||||
- 在 Codex / Claude 两侧全局目录补齐或标准化:
|
||||
- `connect-chrome`
|
||||
- `browse`
|
||||
- `setup-browser-cookies`
|
||||
- 优先复用现有等价能力;缺失时从公开官方来源安装
|
||||
- 生成本地说明文件:`$CODEX_HOME/browser-control-setup.md`
|
||||
- 做真实验证:可见 Chromium 窗口、`https://example.com`、DOM 读取、network/console、cookie 导入入口调用
|
||||
|
||||
## 执行
|
||||
|
||||
先运行安装脚本:
|
||||
|
||||
```bash
|
||||
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
bash "$SCRIPT_DIR/install-browser-control.sh"
|
||||
```
|
||||
|
||||
## 交付
|
||||
|
||||
安装脚本结束后,读取并引用:
|
||||
|
||||
```bash
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
sed -n '1,240p' "$CODEX_HOME/browser-control-setup.md"
|
||||
```
|
||||
|
||||
汇报时只保留高信号结果:
|
||||
|
||||
- 是否安装完成
|
||||
- 3 个入口的最终路径
|
||||
- 本地说明文件路径
|
||||
- 实际验证结果
|
||||
- 如果未完成,唯一阻塞点
|
||||
433
.claude/skills/install-browser-control/install-browser-control.sh
Executable file
433
.claude/skills/install-browser-control/install-browser-control.sh
Executable file
@ -0,0 +1,433 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}"
|
||||
GSTACK_REPO_URL="${GSTACK_REPO_URL:-https://github.com/garrytan/gstack.git}"
|
||||
VERIFY_URL="${VERIFY_URL:-https://example.com}"
|
||||
SETUP_DOC="$CODEX_HOME/browser-control-setup.md"
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
log() {
|
||||
printf '[install-browser-control] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[install-browser-control] ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_macos() {
|
||||
local os_name
|
||||
os_name="$(uname -s)"
|
||||
if [[ "$os_name" != "Darwin" ]]; then
|
||||
fail "当前脚本仅支持 macOS,检测到: $os_name"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_dir() {
|
||||
mkdir -p "$1"
|
||||
}
|
||||
|
||||
detect_codex_install() {
|
||||
if command -v codex >/dev/null 2>&1; then
|
||||
command -v codex
|
||||
else
|
||||
printf 'not-found'
|
||||
fi
|
||||
}
|
||||
|
||||
detect_claude_install() {
|
||||
if command -v claude >/dev/null 2>&1; then
|
||||
command -v claude
|
||||
else
|
||||
printf 'not-found'
|
||||
fi
|
||||
}
|
||||
|
||||
have_equivalent_gstack() {
|
||||
local target="$1"
|
||||
[[ -f "$target/connect-chrome/SKILL.md" ]] \
|
||||
&& [[ -f "$target/setup-browser-cookies/SKILL.md" ]] \
|
||||
&& [[ -f "$target/browse/src/cli.ts" || -x "$target/browse/dist/browse" ]]
|
||||
}
|
||||
|
||||
clone_seed() {
|
||||
local seed_dir="$TMP_DIR/gstack-seed"
|
||||
if [[ -d "$seed_dir" ]]; then
|
||||
return
|
||||
fi
|
||||
log "cloning gstack from $GSTACK_REPO_URL"
|
||||
git clone --depth 1 "$GSTACK_REPO_URL" "$seed_dir" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
backup_path() {
|
||||
local path="$1"
|
||||
if [[ -e "$path" ]]; then
|
||||
mv "$path" "$path.local.bak.$(date +%Y%m%d%H%M%S)"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_gstack_home() {
|
||||
local home_root="$1"
|
||||
local target="$home_root/skills/gstack"
|
||||
|
||||
ensure_dir "$home_root/skills"
|
||||
|
||||
if have_equivalent_gstack "$target"; then
|
||||
printf '%s' "$target"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -e "$target" ]]; then
|
||||
backup_path "$target"
|
||||
fi
|
||||
|
||||
clone_seed
|
||||
cp -R "$TMP_DIR/gstack-seed" "$target"
|
||||
printf '%s' "$target"
|
||||
}
|
||||
|
||||
ensure_bun() {
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
log "installing bun with Homebrew"
|
||||
brew install bun >/dev/null
|
||||
else
|
||||
log "installing bun from bun.sh"
|
||||
curl -fsSL https://bun.sh/install | bash >/dev/null
|
||||
export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"
|
||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||
fi
|
||||
|
||||
command -v bun >/dev/null 2>&1 || fail "bun 安装失败"
|
||||
}
|
||||
|
||||
ensure_browse_runtime() {
|
||||
local gstack_root="$1"
|
||||
local browse_bin="$gstack_root/browse/dist/browse"
|
||||
|
||||
if [[ ! -x "$browse_bin" ]]; then
|
||||
ensure_bun
|
||||
log "building browse binary in $gstack_root"
|
||||
(
|
||||
cd "$gstack_root"
|
||||
bun install >/dev/null
|
||||
bun run build >/dev/null
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -x "$browse_bin" ]] || fail "browse binary 不可用: $browse_bin"
|
||||
|
||||
ensure_bun
|
||||
log "ensuring Playwright Chromium runtime"
|
||||
(
|
||||
cd "$gstack_root"
|
||||
bunx playwright install chromium >/dev/null 2>&1 || true
|
||||
)
|
||||
|
||||
printf '%s' "$browse_bin"
|
||||
}
|
||||
|
||||
ensure_skill_entry() {
|
||||
local home_root="$1"
|
||||
local skill_name="$2"
|
||||
local dest="$home_root/skills/$skill_name"
|
||||
|
||||
if [[ -L "$dest" ]]; then
|
||||
printf '%s' "$dest"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -f "$dest/SKILL.md" ]] && grep -q "^name: $skill_name\$" "$dest/SKILL.md"; then
|
||||
printf '%s' "$dest"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -e "$dest" ]]; then
|
||||
backup_path "$dest"
|
||||
fi
|
||||
|
||||
ln -sfn "gstack/$skill_name" "$dest"
|
||||
printf '%s' "$dest"
|
||||
}
|
||||
|
||||
detect_browser_apps() {
|
||||
local apps=()
|
||||
[[ -d "/Applications/Google Chrome.app" ]] && apps+=("Google Chrome")
|
||||
[[ -d "/Applications/Chromium.app" ]] && apps+=("Chromium")
|
||||
[[ -d "/Applications/Microsoft Edge.app" ]] && apps+=("Microsoft Edge")
|
||||
[[ -d "/Applications/Brave Browser.app" ]] && apps+=("Brave Browser")
|
||||
[[ -d "/Applications/Arc.app" ]] && apps+=("Arc")
|
||||
|
||||
if [[ ${#apps[@]} -eq 0 ]]; then
|
||||
printf 'none-detected'
|
||||
else
|
||||
local joined=""
|
||||
local app
|
||||
for app in "${apps[@]}"; do
|
||||
if [[ -n "$joined" ]]; then
|
||||
joined+=", "
|
||||
fi
|
||||
joined+="$app"
|
||||
done
|
||||
printf '%s' "$joined"
|
||||
fi
|
||||
}
|
||||
|
||||
visible_browser_processes() {
|
||||
/usr/bin/osascript -e 'tell application "System Events" to get the name of every process whose visible is true' 2>/dev/null \
|
||||
| tr ',' '\n' \
|
||||
| sed 's/^ *//' \
|
||||
| grep -E 'Chrome|Chromium' \
|
||||
| paste -sd ', ' - \
|
||||
|| true
|
||||
}
|
||||
|
||||
verify_browser_control() {
|
||||
local browse_bin="$1"
|
||||
local verify_tmp="$TMP_DIR/verify"
|
||||
local connect_out
|
||||
local newtab_out
|
||||
local title_out
|
||||
local heading_out
|
||||
local network_out
|
||||
local console_out
|
||||
local status_out
|
||||
local cookie_out=""
|
||||
local cookie_rc=0
|
||||
local visible_out=""
|
||||
local cookie_status=""
|
||||
|
||||
mkdir -p "$verify_tmp"
|
||||
"$browse_bin" stop >/dev/null 2>&1 || true
|
||||
|
||||
"$browse_bin" connect >"$verify_tmp/connect.log" 2>&1
|
||||
sleep 2
|
||||
"$browse_bin" newtab "$VERIFY_URL" >"$verify_tmp/newtab.log" 2>&1
|
||||
"$browse_bin" js 'document.title' >"$verify_tmp/title.log" 2>&1
|
||||
"$browse_bin" text h1 >"$verify_tmp/heading.log" 2>&1
|
||||
"$browse_bin" reload >/dev/null 2>&1 || true
|
||||
"$browse_bin" wait --load >/dev/null 2>&1 || true
|
||||
"$browse_bin" network >"$verify_tmp/network.log" 2>&1
|
||||
"$browse_bin" console >"$verify_tmp/console.log" 2>&1
|
||||
"$browse_bin" status >"$verify_tmp/status.log" 2>&1
|
||||
visible_out="$(visible_browser_processes)"
|
||||
|
||||
"$browse_bin" cookie-import-browser chrome --domain example.com >"$verify_tmp/cookie.log" 2>&1 || cookie_rc=$?
|
||||
|
||||
connect_out="$(cat "$verify_tmp/connect.log")"
|
||||
newtab_out="$(cat "$verify_tmp/newtab.log")"
|
||||
title_out="$(cat "$verify_tmp/title.log")"
|
||||
heading_out="$(cat "$verify_tmp/heading.log")"
|
||||
network_out="$(cat "$verify_tmp/network.log")"
|
||||
console_out="$(cat "$verify_tmp/console.log")"
|
||||
status_out="$(cat "$verify_tmp/status.log")"
|
||||
cookie_out="$(cat "$verify_tmp/cookie.log" 2>/dev/null || true)"
|
||||
|
||||
if [[ $cookie_rc -eq 0 ]]; then
|
||||
cookie_status="callable"
|
||||
elif [[ "$cookie_out" == *"Keychain"* ]]; then
|
||||
cookie_status="callable-requires-keychain-access"
|
||||
elif [[ "$cookie_out" == *"No Chromium browsers found"* ]]; then
|
||||
cookie_status="callable-no-cookie-source-browser"
|
||||
else
|
||||
cookie_status="error"
|
||||
fi
|
||||
|
||||
BROWSE_CONNECT_OUT="$connect_out"
|
||||
BROWSE_NEWTAB_OUT="$newtab_out"
|
||||
BROWSE_TITLE_OUT="$title_out"
|
||||
BROWSE_HEADING_OUT="$heading_out"
|
||||
BROWSE_NETWORK_OUT="$network_out"
|
||||
BROWSE_CONSOLE_OUT="$console_out"
|
||||
BROWSE_STATUS_OUT="$status_out"
|
||||
BROWSE_VISIBLE_OUT="$visible_out"
|
||||
COOKIE_VERIFY_OUT="$cookie_out"
|
||||
COOKIE_VERIFY_STATUS="$cookie_status"
|
||||
}
|
||||
|
||||
write_setup_doc() {
|
||||
local codex_connect="$1"
|
||||
local codex_browse="$2"
|
||||
local codex_cookie="$3"
|
||||
local claude_connect="$4"
|
||||
local claude_browse="$5"
|
||||
local claude_cookie="$6"
|
||||
local codex_gstack="$7"
|
||||
local claude_gstack="$8"
|
||||
local browse_bin="$9"
|
||||
local codex_install="${10}"
|
||||
local claude_install="${11}"
|
||||
local browser_apps="${12}"
|
||||
|
||||
ensure_dir "$CODEX_HOME"
|
||||
|
||||
cat >"$SETUP_DOC" <<EOF
|
||||
# Browser Control Setup
|
||||
|
||||
## 实际安装来源
|
||||
|
||||
- 官方上游:$GSTACK_REPO_URL
|
||||
- Codex 当前 gstack 根目录:$codex_gstack
|
||||
- Claude 当前 gstack 根目录:$claude_gstack
|
||||
- Codex 安装入口:$codex_install
|
||||
- Claude 安装入口:$claude_install
|
||||
- 本机已检测到的系统浏览器:$browser_apps
|
||||
- 运行时浏览器:Playwright Chromium(由 $browse_bin 驱动)
|
||||
|
||||
## 最终技能路径
|
||||
|
||||
### Codex
|
||||
|
||||
- connect-chrome: $codex_connect
|
||||
- browse: $codex_browse
|
||||
- setup-browser-cookies: $codex_cookie
|
||||
|
||||
### Claude
|
||||
|
||||
- connect-chrome: $claude_connect
|
||||
- browse: $claude_browse
|
||||
- setup-browser-cookies: $claude_cookie
|
||||
|
||||
## 如何调用
|
||||
|
||||
### Codex
|
||||
|
||||
- \`connect-chrome\`
|
||||
- \`browse\`
|
||||
- \`setup-browser-cookies\`
|
||||
- \`install-browser-control\`
|
||||
|
||||
### Claude
|
||||
|
||||
- \`/connect-chrome\`
|
||||
- \`/browse\`
|
||||
- \`/setup-browser-cookies\`
|
||||
- \`/install-browser-control\`
|
||||
|
||||
## 如何验证
|
||||
|
||||
1. 运行 \`connect-chrome\`,确认出现 headed Chromium 窗口。
|
||||
2. 运行 \`browse newtab https://example.com\`。
|
||||
3. 运行 \`browse js 'document.title'\` 与 \`browse text h1\`,确认可读取 DOM。
|
||||
4. 运行 \`browse network\` 与 \`browse console\`,确认能读取请求和控制台信息。
|
||||
5. 运行 \`browse cookie-import-browser chrome --domain example.com\`,确认 cookie 导入入口可被调用;首次读取 Chrome Cookie 时,macOS 可能弹出 Keychain 授权框。
|
||||
|
||||
## 本机最近一次真实验证
|
||||
|
||||
### connect
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_CONNECT_OUT
|
||||
\`\`\`
|
||||
|
||||
### newtab + DOM
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_NEWTAB_OUT
|
||||
$BROWSE_TITLE_OUT
|
||||
$BROWSE_HEADING_OUT
|
||||
\`\`\`
|
||||
|
||||
### network
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_NETWORK_OUT
|
||||
\`\`\`
|
||||
|
||||
### console
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_CONSOLE_OUT
|
||||
\`\`\`
|
||||
|
||||
### status
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_STATUS_OUT
|
||||
\`\`\`
|
||||
|
||||
### visible browser processes
|
||||
|
||||
\`\`\`
|
||||
${BROWSE_VISIBLE_OUT:-not-detected}
|
||||
\`\`\`
|
||||
|
||||
### cookie import verification
|
||||
|
||||
- status: $COOKIE_VERIFY_STATUS
|
||||
|
||||
\`\`\`
|
||||
$COOKIE_VERIFY_OUT
|
||||
\`\`\`
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
local codex_gstack
|
||||
local claude_gstack
|
||||
local browse_bin
|
||||
local claude_browse_bin
|
||||
local codex_connect
|
||||
local codex_browse
|
||||
local codex_cookie
|
||||
local claude_connect
|
||||
local claude_browse
|
||||
local claude_cookie
|
||||
local browser_apps
|
||||
local codex_install
|
||||
local claude_install
|
||||
|
||||
ensure_macos
|
||||
|
||||
codex_install="$(detect_codex_install)"
|
||||
claude_install="$(detect_claude_install)"
|
||||
|
||||
codex_gstack="$(ensure_gstack_home "$CODEX_HOME")"
|
||||
claude_gstack="$(ensure_gstack_home "$CLAUDE_HOME")"
|
||||
browse_bin="$(ensure_browse_runtime "$codex_gstack")"
|
||||
claude_browse_bin="$(ensure_browse_runtime "$claude_gstack")"
|
||||
|
||||
[[ -x "$claude_browse_bin" ]] || fail "Claude browse binary 不可用: $claude_browse_bin"
|
||||
|
||||
codex_connect="$(ensure_skill_entry "$CODEX_HOME" "connect-chrome")"
|
||||
codex_browse="$(ensure_skill_entry "$CODEX_HOME" "browse")"
|
||||
codex_cookie="$(ensure_skill_entry "$CODEX_HOME" "setup-browser-cookies")"
|
||||
|
||||
claude_connect="$(ensure_skill_entry "$CLAUDE_HOME" "connect-chrome")"
|
||||
claude_browse="$(ensure_skill_entry "$CLAUDE_HOME" "browse")"
|
||||
claude_cookie="$(ensure_skill_entry "$CLAUDE_HOME" "setup-browser-cookies")"
|
||||
|
||||
browser_apps="$(detect_browser_apps)"
|
||||
|
||||
verify_browser_control "$browse_bin"
|
||||
|
||||
write_setup_doc \
|
||||
"$codex_connect" "$codex_browse" "$codex_cookie" \
|
||||
"$claude_connect" "$claude_browse" "$claude_cookie" \
|
||||
"$codex_gstack" "$claude_gstack" "$browse_bin" \
|
||||
"$codex_install" "$claude_install" "$browser_apps"
|
||||
|
||||
printf 'DONE\n'
|
||||
printf 'CODEX_HOME=%s\n' "$CODEX_HOME"
|
||||
printf 'CLAUDE_HOME=%s\n' "$CLAUDE_HOME"
|
||||
printf 'SETUP_DOC=%s\n' "$SETUP_DOC"
|
||||
printf 'CODEX_CONNECT=%s\n' "$codex_connect"
|
||||
printf 'CODEX_BROWSE=%s\n' "$codex_browse"
|
||||
printf 'CODEX_COOKIE=%s\n' "$codex_cookie"
|
||||
printf 'CLAUDE_CONNECT=%s\n' "$claude_connect"
|
||||
printf 'CLAUDE_BROWSE=%s\n' "$claude_browse"
|
||||
printf 'CLAUDE_COOKIE=%s\n' "$claude_cookie"
|
||||
printf 'COOKIE_VERIFY_STATUS=%s\n' "$COOKIE_VERIFY_STATUS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@ -42,6 +42,7 @@
|
||||
- `issue`: 查看当前仓库或任意 Gitea 仓库的 issue 列表和单条详情,支持自动识别 git origin、用户指定仓库和格式化输出。 (file: `./.codex/skills/issue/SKILL.md`)
|
||||
- `issue-drive`: 归集证据并把问题拆成 1 到多张 Gitea issue,支持从当前仓库 origin 自动识别仓库或用户显式指定。 (file: `./.codex/skills/issue-drive/SKILL.md`)
|
||||
- `changelog`: 一键发版:生成更新日志 → commit → 打 tag,全流程自动化。 (file: `./.codex/skills/changelog/SKILL.md`)
|
||||
- `install-browser-control`: 在新 Mac 上为 Codex 和 Claude 全局安装或整理真实 Chrome/Chromium 调试能力,统一补齐 connect-chrome、browse、setup-browser-cookies 入口并做实机验证。 (file: `./.codex/skills/install-browser-control/SKILL.md`)
|
||||
|
||||
### How to use skills
|
||||
|
||||
|
||||
45
.codex/skills/install-browser-control/SKILL.md
Normal file
45
.codex/skills/install-browser-control/SKILL.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
name: install-browser-control
|
||||
description: 在新 Mac 上为 Codex 和 Claude 全局安装或整理真实 Chrome/Chromium 调试能力时使用;适用于需要 connect-chrome、browse、setup-browser-cookies 三个入口、可见浏览器接管、DOM 操作、network/console 检查与 cookie 导入支持的场景。
|
||||
---
|
||||
|
||||
# Install Browser Control
|
||||
|
||||
当用户要求在新机器上把浏览器接管调试能力装完整时,直接执行本 skill。
|
||||
|
||||
## 目标
|
||||
|
||||
- 自动识别 `CODEX_HOME`,默认回退到 `~/.codex`
|
||||
- 在 Codex / Claude 两侧全局目录补齐或标准化:
|
||||
- `connect-chrome`
|
||||
- `browse`
|
||||
- `setup-browser-cookies`
|
||||
- 优先复用现有等价能力;缺失时从公开官方来源安装
|
||||
- 生成本地说明文件:`$CODEX_HOME/browser-control-setup.md`
|
||||
- 做真实验证:可见 Chromium 窗口、`https://example.com`、DOM 读取、network/console、cookie 导入入口调用
|
||||
|
||||
## 执行
|
||||
|
||||
先运行安装脚本:
|
||||
|
||||
```bash
|
||||
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
bash "$SCRIPT_DIR/install-browser-control.sh"
|
||||
```
|
||||
|
||||
## 交付
|
||||
|
||||
安装脚本结束后,读取并引用:
|
||||
|
||||
```bash
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
sed -n '1,240p' "$CODEX_HOME/browser-control-setup.md"
|
||||
```
|
||||
|
||||
汇报时只保留高信号结果:
|
||||
|
||||
- 是否安装完成
|
||||
- 3 个入口的最终路径
|
||||
- 本地说明文件路径
|
||||
- 实际验证结果
|
||||
- 如果未完成,唯一阻塞点
|
||||
433
.codex/skills/install-browser-control/install-browser-control.sh
Executable file
433
.codex/skills/install-browser-control/install-browser-control.sh
Executable file
@ -0,0 +1,433 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||
CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}"
|
||||
GSTACK_REPO_URL="${GSTACK_REPO_URL:-https://github.com/garrytan/gstack.git}"
|
||||
VERIFY_URL="${VERIFY_URL:-https://example.com}"
|
||||
SETUP_DOC="$CODEX_HOME/browser-control-setup.md"
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
log() {
|
||||
printf '[install-browser-control] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[install-browser-control] ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_macos() {
|
||||
local os_name
|
||||
os_name="$(uname -s)"
|
||||
if [[ "$os_name" != "Darwin" ]]; then
|
||||
fail "当前脚本仅支持 macOS,检测到: $os_name"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_dir() {
|
||||
mkdir -p "$1"
|
||||
}
|
||||
|
||||
detect_codex_install() {
|
||||
if command -v codex >/dev/null 2>&1; then
|
||||
command -v codex
|
||||
else
|
||||
printf 'not-found'
|
||||
fi
|
||||
}
|
||||
|
||||
detect_claude_install() {
|
||||
if command -v claude >/dev/null 2>&1; then
|
||||
command -v claude
|
||||
else
|
||||
printf 'not-found'
|
||||
fi
|
||||
}
|
||||
|
||||
have_equivalent_gstack() {
|
||||
local target="$1"
|
||||
[[ -f "$target/connect-chrome/SKILL.md" ]] \
|
||||
&& [[ -f "$target/setup-browser-cookies/SKILL.md" ]] \
|
||||
&& [[ -f "$target/browse/src/cli.ts" || -x "$target/browse/dist/browse" ]]
|
||||
}
|
||||
|
||||
clone_seed() {
|
||||
local seed_dir="$TMP_DIR/gstack-seed"
|
||||
if [[ -d "$seed_dir" ]]; then
|
||||
return
|
||||
fi
|
||||
log "cloning gstack from $GSTACK_REPO_URL"
|
||||
git clone --depth 1 "$GSTACK_REPO_URL" "$seed_dir" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
backup_path() {
|
||||
local path="$1"
|
||||
if [[ -e "$path" ]]; then
|
||||
mv "$path" "$path.local.bak.$(date +%Y%m%d%H%M%S)"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_gstack_home() {
|
||||
local home_root="$1"
|
||||
local target="$home_root/skills/gstack"
|
||||
|
||||
ensure_dir "$home_root/skills"
|
||||
|
||||
if have_equivalent_gstack "$target"; then
|
||||
printf '%s' "$target"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -e "$target" ]]; then
|
||||
backup_path "$target"
|
||||
fi
|
||||
|
||||
clone_seed
|
||||
cp -R "$TMP_DIR/gstack-seed" "$target"
|
||||
printf '%s' "$target"
|
||||
}
|
||||
|
||||
ensure_bun() {
|
||||
if command -v bun >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
log "installing bun with Homebrew"
|
||||
brew install bun >/dev/null
|
||||
else
|
||||
log "installing bun from bun.sh"
|
||||
curl -fsSL https://bun.sh/install | bash >/dev/null
|
||||
export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"
|
||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||
fi
|
||||
|
||||
command -v bun >/dev/null 2>&1 || fail "bun 安装失败"
|
||||
}
|
||||
|
||||
ensure_browse_runtime() {
|
||||
local gstack_root="$1"
|
||||
local browse_bin="$gstack_root/browse/dist/browse"
|
||||
|
||||
if [[ ! -x "$browse_bin" ]]; then
|
||||
ensure_bun
|
||||
log "building browse binary in $gstack_root"
|
||||
(
|
||||
cd "$gstack_root"
|
||||
bun install >/dev/null
|
||||
bun run build >/dev/null
|
||||
)
|
||||
fi
|
||||
|
||||
[[ -x "$browse_bin" ]] || fail "browse binary 不可用: $browse_bin"
|
||||
|
||||
ensure_bun
|
||||
log "ensuring Playwright Chromium runtime"
|
||||
(
|
||||
cd "$gstack_root"
|
||||
bunx playwright install chromium >/dev/null 2>&1 || true
|
||||
)
|
||||
|
||||
printf '%s' "$browse_bin"
|
||||
}
|
||||
|
||||
ensure_skill_entry() {
|
||||
local home_root="$1"
|
||||
local skill_name="$2"
|
||||
local dest="$home_root/skills/$skill_name"
|
||||
|
||||
if [[ -L "$dest" ]]; then
|
||||
printf '%s' "$dest"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -f "$dest/SKILL.md" ]] && grep -q "^name: $skill_name\$" "$dest/SKILL.md"; then
|
||||
printf '%s' "$dest"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -e "$dest" ]]; then
|
||||
backup_path "$dest"
|
||||
fi
|
||||
|
||||
ln -sfn "gstack/$skill_name" "$dest"
|
||||
printf '%s' "$dest"
|
||||
}
|
||||
|
||||
detect_browser_apps() {
|
||||
local apps=()
|
||||
[[ -d "/Applications/Google Chrome.app" ]] && apps+=("Google Chrome")
|
||||
[[ -d "/Applications/Chromium.app" ]] && apps+=("Chromium")
|
||||
[[ -d "/Applications/Microsoft Edge.app" ]] && apps+=("Microsoft Edge")
|
||||
[[ -d "/Applications/Brave Browser.app" ]] && apps+=("Brave Browser")
|
||||
[[ -d "/Applications/Arc.app" ]] && apps+=("Arc")
|
||||
|
||||
if [[ ${#apps[@]} -eq 0 ]]; then
|
||||
printf 'none-detected'
|
||||
else
|
||||
local joined=""
|
||||
local app
|
||||
for app in "${apps[@]}"; do
|
||||
if [[ -n "$joined" ]]; then
|
||||
joined+=", "
|
||||
fi
|
||||
joined+="$app"
|
||||
done
|
||||
printf '%s' "$joined"
|
||||
fi
|
||||
}
|
||||
|
||||
visible_browser_processes() {
|
||||
/usr/bin/osascript -e 'tell application "System Events" to get the name of every process whose visible is true' 2>/dev/null \
|
||||
| tr ',' '\n' \
|
||||
| sed 's/^ *//' \
|
||||
| grep -E 'Chrome|Chromium' \
|
||||
| paste -sd ', ' - \
|
||||
|| true
|
||||
}
|
||||
|
||||
verify_browser_control() {
|
||||
local browse_bin="$1"
|
||||
local verify_tmp="$TMP_DIR/verify"
|
||||
local connect_out
|
||||
local newtab_out
|
||||
local title_out
|
||||
local heading_out
|
||||
local network_out
|
||||
local console_out
|
||||
local status_out
|
||||
local cookie_out=""
|
||||
local cookie_rc=0
|
||||
local visible_out=""
|
||||
local cookie_status=""
|
||||
|
||||
mkdir -p "$verify_tmp"
|
||||
"$browse_bin" stop >/dev/null 2>&1 || true
|
||||
|
||||
"$browse_bin" connect >"$verify_tmp/connect.log" 2>&1
|
||||
sleep 2
|
||||
"$browse_bin" newtab "$VERIFY_URL" >"$verify_tmp/newtab.log" 2>&1
|
||||
"$browse_bin" js 'document.title' >"$verify_tmp/title.log" 2>&1
|
||||
"$browse_bin" text h1 >"$verify_tmp/heading.log" 2>&1
|
||||
"$browse_bin" reload >/dev/null 2>&1 || true
|
||||
"$browse_bin" wait --load >/dev/null 2>&1 || true
|
||||
"$browse_bin" network >"$verify_tmp/network.log" 2>&1
|
||||
"$browse_bin" console >"$verify_tmp/console.log" 2>&1
|
||||
"$browse_bin" status >"$verify_tmp/status.log" 2>&1
|
||||
visible_out="$(visible_browser_processes)"
|
||||
|
||||
"$browse_bin" cookie-import-browser chrome --domain example.com >"$verify_tmp/cookie.log" 2>&1 || cookie_rc=$?
|
||||
|
||||
connect_out="$(cat "$verify_tmp/connect.log")"
|
||||
newtab_out="$(cat "$verify_tmp/newtab.log")"
|
||||
title_out="$(cat "$verify_tmp/title.log")"
|
||||
heading_out="$(cat "$verify_tmp/heading.log")"
|
||||
network_out="$(cat "$verify_tmp/network.log")"
|
||||
console_out="$(cat "$verify_tmp/console.log")"
|
||||
status_out="$(cat "$verify_tmp/status.log")"
|
||||
cookie_out="$(cat "$verify_tmp/cookie.log" 2>/dev/null || true)"
|
||||
|
||||
if [[ $cookie_rc -eq 0 ]]; then
|
||||
cookie_status="callable"
|
||||
elif [[ "$cookie_out" == *"Keychain"* ]]; then
|
||||
cookie_status="callable-requires-keychain-access"
|
||||
elif [[ "$cookie_out" == *"No Chromium browsers found"* ]]; then
|
||||
cookie_status="callable-no-cookie-source-browser"
|
||||
else
|
||||
cookie_status="error"
|
||||
fi
|
||||
|
||||
BROWSE_CONNECT_OUT="$connect_out"
|
||||
BROWSE_NEWTAB_OUT="$newtab_out"
|
||||
BROWSE_TITLE_OUT="$title_out"
|
||||
BROWSE_HEADING_OUT="$heading_out"
|
||||
BROWSE_NETWORK_OUT="$network_out"
|
||||
BROWSE_CONSOLE_OUT="$console_out"
|
||||
BROWSE_STATUS_OUT="$status_out"
|
||||
BROWSE_VISIBLE_OUT="$visible_out"
|
||||
COOKIE_VERIFY_OUT="$cookie_out"
|
||||
COOKIE_VERIFY_STATUS="$cookie_status"
|
||||
}
|
||||
|
||||
write_setup_doc() {
|
||||
local codex_connect="$1"
|
||||
local codex_browse="$2"
|
||||
local codex_cookie="$3"
|
||||
local claude_connect="$4"
|
||||
local claude_browse="$5"
|
||||
local claude_cookie="$6"
|
||||
local codex_gstack="$7"
|
||||
local claude_gstack="$8"
|
||||
local browse_bin="$9"
|
||||
local codex_install="${10}"
|
||||
local claude_install="${11}"
|
||||
local browser_apps="${12}"
|
||||
|
||||
ensure_dir "$CODEX_HOME"
|
||||
|
||||
cat >"$SETUP_DOC" <<EOF
|
||||
# Browser Control Setup
|
||||
|
||||
## 实际安装来源
|
||||
|
||||
- 官方上游:$GSTACK_REPO_URL
|
||||
- Codex 当前 gstack 根目录:$codex_gstack
|
||||
- Claude 当前 gstack 根目录:$claude_gstack
|
||||
- Codex 安装入口:$codex_install
|
||||
- Claude 安装入口:$claude_install
|
||||
- 本机已检测到的系统浏览器:$browser_apps
|
||||
- 运行时浏览器:Playwright Chromium(由 $browse_bin 驱动)
|
||||
|
||||
## 最终技能路径
|
||||
|
||||
### Codex
|
||||
|
||||
- connect-chrome: $codex_connect
|
||||
- browse: $codex_browse
|
||||
- setup-browser-cookies: $codex_cookie
|
||||
|
||||
### Claude
|
||||
|
||||
- connect-chrome: $claude_connect
|
||||
- browse: $claude_browse
|
||||
- setup-browser-cookies: $claude_cookie
|
||||
|
||||
## 如何调用
|
||||
|
||||
### Codex
|
||||
|
||||
- \`connect-chrome\`
|
||||
- \`browse\`
|
||||
- \`setup-browser-cookies\`
|
||||
- \`install-browser-control\`
|
||||
|
||||
### Claude
|
||||
|
||||
- \`/connect-chrome\`
|
||||
- \`/browse\`
|
||||
- \`/setup-browser-cookies\`
|
||||
- \`/install-browser-control\`
|
||||
|
||||
## 如何验证
|
||||
|
||||
1. 运行 \`connect-chrome\`,确认出现 headed Chromium 窗口。
|
||||
2. 运行 \`browse newtab https://example.com\`。
|
||||
3. 运行 \`browse js 'document.title'\` 与 \`browse text h1\`,确认可读取 DOM。
|
||||
4. 运行 \`browse network\` 与 \`browse console\`,确认能读取请求和控制台信息。
|
||||
5. 运行 \`browse cookie-import-browser chrome --domain example.com\`,确认 cookie 导入入口可被调用;首次读取 Chrome Cookie 时,macOS 可能弹出 Keychain 授权框。
|
||||
|
||||
## 本机最近一次真实验证
|
||||
|
||||
### connect
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_CONNECT_OUT
|
||||
\`\`\`
|
||||
|
||||
### newtab + DOM
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_NEWTAB_OUT
|
||||
$BROWSE_TITLE_OUT
|
||||
$BROWSE_HEADING_OUT
|
||||
\`\`\`
|
||||
|
||||
### network
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_NETWORK_OUT
|
||||
\`\`\`
|
||||
|
||||
### console
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_CONSOLE_OUT
|
||||
\`\`\`
|
||||
|
||||
### status
|
||||
|
||||
\`\`\`
|
||||
$BROWSE_STATUS_OUT
|
||||
\`\`\`
|
||||
|
||||
### visible browser processes
|
||||
|
||||
\`\`\`
|
||||
${BROWSE_VISIBLE_OUT:-not-detected}
|
||||
\`\`\`
|
||||
|
||||
### cookie import verification
|
||||
|
||||
- status: $COOKIE_VERIFY_STATUS
|
||||
|
||||
\`\`\`
|
||||
$COOKIE_VERIFY_OUT
|
||||
\`\`\`
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
local codex_gstack
|
||||
local claude_gstack
|
||||
local browse_bin
|
||||
local claude_browse_bin
|
||||
local codex_connect
|
||||
local codex_browse
|
||||
local codex_cookie
|
||||
local claude_connect
|
||||
local claude_browse
|
||||
local claude_cookie
|
||||
local browser_apps
|
||||
local codex_install
|
||||
local claude_install
|
||||
|
||||
ensure_macos
|
||||
|
||||
codex_install="$(detect_codex_install)"
|
||||
claude_install="$(detect_claude_install)"
|
||||
|
||||
codex_gstack="$(ensure_gstack_home "$CODEX_HOME")"
|
||||
claude_gstack="$(ensure_gstack_home "$CLAUDE_HOME")"
|
||||
browse_bin="$(ensure_browse_runtime "$codex_gstack")"
|
||||
claude_browse_bin="$(ensure_browse_runtime "$claude_gstack")"
|
||||
|
||||
[[ -x "$claude_browse_bin" ]] || fail "Claude browse binary 不可用: $claude_browse_bin"
|
||||
|
||||
codex_connect="$(ensure_skill_entry "$CODEX_HOME" "connect-chrome")"
|
||||
codex_browse="$(ensure_skill_entry "$CODEX_HOME" "browse")"
|
||||
codex_cookie="$(ensure_skill_entry "$CODEX_HOME" "setup-browser-cookies")"
|
||||
|
||||
claude_connect="$(ensure_skill_entry "$CLAUDE_HOME" "connect-chrome")"
|
||||
claude_browse="$(ensure_skill_entry "$CLAUDE_HOME" "browse")"
|
||||
claude_cookie="$(ensure_skill_entry "$CLAUDE_HOME" "setup-browser-cookies")"
|
||||
|
||||
browser_apps="$(detect_browser_apps)"
|
||||
|
||||
verify_browser_control "$browse_bin"
|
||||
|
||||
write_setup_doc \
|
||||
"$codex_connect" "$codex_browse" "$codex_cookie" \
|
||||
"$claude_connect" "$claude_browse" "$claude_cookie" \
|
||||
"$codex_gstack" "$claude_gstack" "$browse_bin" \
|
||||
"$codex_install" "$claude_install" "$browser_apps"
|
||||
|
||||
printf 'DONE\n'
|
||||
printf 'CODEX_HOME=%s\n' "$CODEX_HOME"
|
||||
printf 'CLAUDE_HOME=%s\n' "$CLAUDE_HOME"
|
||||
printf 'SETUP_DOC=%s\n' "$SETUP_DOC"
|
||||
printf 'CODEX_CONNECT=%s\n' "$codex_connect"
|
||||
printf 'CODEX_BROWSE=%s\n' "$codex_browse"
|
||||
printf 'CODEX_COOKIE=%s\n' "$codex_cookie"
|
||||
printf 'CLAUDE_CONNECT=%s\n' "$claude_connect"
|
||||
printf 'CLAUDE_BROWSE=%s\n' "$claude_browse"
|
||||
printf 'CLAUDE_COOKIE=%s\n' "$claude_cookie"
|
||||
printf 'COOKIE_VERIFY_STATUS=%s\n' "$COOKIE_VERIFY_STATUS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@ -148,6 +148,7 @@
|
||||
- `issue`: 查看当前仓库或任意 Gitea 仓库的 issue 列表和单条详情,支持自动识别 git origin、用户指定仓库和格式化输出。 (file: `./.codex/skills/issue/SKILL.md`)
|
||||
- `issue-drive`: 归集证据并把问题拆成 1 到多张 Gitea issue,支持从当前仓库 origin 自动识别仓库或用户显式指定。 (file: `./.codex/skills/issue-drive/SKILL.md`)
|
||||
- `changelog`: 一键发版:生成更新日志 → commit → 打 tag,全流程自动化。 (file: `./.codex/skills/changelog/SKILL.md`)
|
||||
- `install-browser-control`: 在新 Mac 上为 Codex 和 Claude 全局安装或整理真实 Chrome/Chromium 调试能力,统一补齐 connect-chrome、browse、setup-browser-cookies 入口并做实机验证。 (file: `./.codex/skills/install-browser-control/SKILL.md`)
|
||||
|
||||
### How to use skills
|
||||
|
||||
|
||||
@ -148,6 +148,7 @@
|
||||
- `issue`: 查看当前仓库或任意 Gitea 仓库的 issue 列表和单条详情,支持自动识别 git origin、用户指定仓库和格式化输出。 (file: `./.codex/skills/issue/SKILL.md`)
|
||||
- `issue-drive`: 归集证据并把问题拆成 1 到多张 Gitea issue,支持从当前仓库 origin 自动识别仓库或用户显式指定。 (file: `./.codex/skills/issue-drive/SKILL.md`)
|
||||
- `changelog`: 一键发版:生成更新日志 → commit → 打 tag,全流程自动化。 (file: `./.codex/skills/changelog/SKILL.md`)
|
||||
- `install-browser-control`: 在新 Mac 上为 Codex 和 Claude 全局安装或整理真实 Chrome/Chromium 调试能力,统一补齐 connect-chrome、browse、setup-browser-cookies 入口并做实机验证。 (file: `./.codex/skills/install-browser-control/SKILL.md`)
|
||||
|
||||
### How to use skills
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user