From f53a8488cfb1fe6829119a689ceb5ce4d747f228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=99=BD?= <31078449+Nowhitestar@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:49:38 +0800 Subject: [PATCH] feat(install): auto-detect agents, route MCP auth to QR mode for remote installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two end-to-end improvements to install.sh / install.ps1, mirrored across both: 1. Agent auto-detection Probe well-known config dirs and binaries for ~18 of vercel-labs/skills' 45 supported agents (claude-code, cursor, codex, gemini-cli, opencode, openclaw, qwen-code, iflow-cli, windsurf, warp, amp, crush, goose, droid, kode, kilo, kimi-cli, kiro-cli). When detection finds anything, the `skills add` step gets `-a id1,id2,...` instead of dumping the user into the multi-select. New flags: --list-agents (preview), --all-agents (skip our detection, let skills CLI scan everything), --only (manual override) is unchanged. 2. Local-vs-remote MCP auth routing The current --auth-login auto-opens a browser. On SSH sessions, Docker containers, and OpenClaw remote channels (where the user is on a phone), that silently launches a browser they can't see — leaving them stuck on "Waiting for authorization...". Detect the remote context (any signal fires): - $HOME/.openclaw exists (OpenClaw runtime — confirmed by project owner) - $SSH_CONNECTION / $SSH_TTY set - Linux without $DISPLAY / $WAYLAND_DISPLAY Then pass --no-browser to the MCP CLI, which prints URL + ANSI QR for the user to scan with a phone (companion server PR adds these flags). New flags: --remote / --local (force either mode), --force-mcp (re-auth even if AGENTKEY_API_KEY is already in MCP configs). Also adds an idempotency short-circuit: if any known MCP config already has an agentkey block with a valid-looking API key, skip the auth step (with --force-mcp to override). Docs: README.md + docs/README_zh.md updated to document the new flags and add a dedicated "Installing over SSH / Docker / OpenClaw" section. Companion: chainbase-labs/AgentKey-Server#2 (adds --no-browser / --qr to @agentkey/mcp). This installer change is forward-compatible — current @agentkey/mcp will warn and ignore unknown flags; once 0.3.5 lands the QR flow goes live. --- README.md | 42 +++++++- docs/README_zh.md | 42 +++++++- scripts/install.ps1 | 185 ++++++++++++++++++++++++++++++---- scripts/install.sh | 236 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 466 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c402027..57490e3 100644 --- a/README.md +++ b/README.md @@ -218,21 +218,32 @@ Still stuck? See the "Where do I get help" item below.
Advanced install options (CI / specific agents / manual two-step) +The installer auto-detects which AI agents you have on this machine (by probing well-known config dirs and binaries from the [vercel-labs/skills supported-agents list](https://github.com/vercel-labs/skills)) and pre-selects them — no multi-select prompt. Override with the flags below. + **Installer flags:** ```bash # Non-interactive (CI / unattended): install to every detected agent, no prompts curl -fsSL https://agentkey.app/install.sh | bash -s -- --yes -# Only install the skill for specific agents +# See which agents the installer would auto-select on this host (and exit) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --list-agents + +# Only install the skill for specific agents (overrides auto-detection) curl -fsSL https://agentkey.app/install.sh | bash -s -- --only claude-code,cursor +# Skip our agent detection; let `skills` CLI install for every agent it finds +curl -fsSL https://agentkey.app/install.sh | bash -s -- --all-agents + # Only the skill, or only the MCP auth curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-mcp curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-skill + +# Re-authenticate even if AgentKey is already configured locally +curl -fsSL https://agentkey.app/install.sh | bash -s -- --force-mcp ``` -PowerShell equivalents: `-Yes`, `-Only`, `-SkipMcp`, `-SkipSkill`. +PowerShell equivalents: `-Yes`, `-ListAgents`, `-Only`, `-AllAgents`, `-SkipMcp`, `-SkipSkill`, `-ForceMcp`. **Manual two-step install** (if you'd rather run the two underlying commands yourself, or the one-line installer can't reach your machine): @@ -244,7 +255,32 @@ npx skills add chainbase-labs/agentkey npx -y @agentkey/mcp --auth-login ``` -Over SSH or any shell where a browser can't open, use `npx -y @agentkey/mcp --setup` — an interactive wizard asks for the key and lets you pick which MCP clients to write to. +
+ +
+Installing over SSH, inside Docker, or via OpenClaw / Claude Code remote channels + +When the installer runs on a machine you can't see (an SSH server, a Docker container, an OpenClaw runtime triggered from your phone), the default `--auth-login` would silently spawn a browser on the *remote* host — invisible to you. + +The installer detects this automatically and switches to a **scan-from-phone** flow: it prints the auth URL plus a terminal QR code, and skips the browser auto-open. Detection signals (any one fires): + +- `~/.openclaw/` exists (OpenClaw runtime) +- `$SSH_CONNECTION` / `$SSH_TTY` set +- Linux without `$DISPLAY` / `$WAYLAND_DISPLAY` + +Force the mode either way: + +```bash +# Force remote mode (URL + QR, no browser) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --remote + +# Force local mode (auto-open browser, ignore heuristics) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --local +``` + +PowerShell: `-Remote` / `-Local`. + +If you'd rather skip the URL/QR flow entirely and type a key manually, `npx -y @agentkey/mcp --setup` opens an interactive wizard that asks for the key and lets you pick which MCP clients to write to.
diff --git a/docs/README_zh.md b/docs/README_zh.md index faa1e68..9286cac 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -218,21 +218,32 @@ npx skills remove chainbase-labs/agentkey
进阶安装(CI / 指定 Agent / 手动两步) +安装器会自动探测本机已安装的 AI Agent(依据 [vercel-labs/skills 支持列表](https://github.com/vercel-labs/skills) 比对配置目录和命令行工具),自动选中它们 —— 不再弹多选框。需要覆盖时用下面的旗标: + **安装器参数:** ```bash # 非交互模式(CI / 无人值守):安装到所有检测到的 Agent,不询问 curl -fsSL https://agentkey.app/install.sh | bash -s -- --yes -# 只安装到指定的 Agent +# 看一下安装器在本机会自动选中哪些 Agent(看完即退出) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --list-agents + +# 只安装到指定的 Agent(覆盖自动检测结果) curl -fsSL https://agentkey.app/install.sh | bash -s -- --only claude-code,cursor +# 跳过我们的检测,让 skills CLI 自己识别全部 Agent +curl -fsSL https://agentkey.app/install.sh | bash -s -- --all-agents + # 只装 Skill 或只做 MCP 授权 curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-mcp curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-skill + +# 即使本机已经配置过 AgentKey 也强制重新走一次授权 +curl -fsSL https://agentkey.app/install.sh | bash -s -- --force-mcp ``` -PowerShell 对应参数:`-Yes`、`-Only`、`-SkipMcp`、`-SkipSkill`。 +PowerShell 对应参数:`-Yes`、`-ListAgents`、`-Only`、`-AllAgents`、`-SkipMcp`、`-SkipSkill`、`-ForceMcp`。 **手动两步安装**(想自己跑两条底层命令,或一键脚本在你的环境里跑不起来): @@ -244,7 +255,32 @@ npx skills add chainbase-labs/agentkey npx -y @agentkey/mcp --auth-login ``` -在 SSH 远程或无法弹浏览器的终端里,用 `npx -y @agentkey/mcp --setup` —— 交互式向导,问你要 Key 并让你勾选要写入的 MCP 客户端。 +
+ +
+在 SSH / Docker / OpenClaw 远程通道里安装 + +如果安装命令是在你看不到屏幕的机器上跑(远程 SSH 服务器、Docker 容器、由手机触发的 OpenClaw 运行时),默认 `--auth-login` 会在远端"成功"打开一个你看不见的浏览器,然后你只能盯着 "Waiting for authorization..." 卡死。 + +安装器会自动识别这种场景,切换到"扫码授权"流程:终端里直接打印授权 URL 加二维码,不再尝试本地开浏览器。**触发条件**(任一命中即判定为远程): + +- `~/.openclaw/` 目录存在(OpenClaw 运行时) +- `$SSH_CONNECTION` / `$SSH_TTY` 已设置 +- Linux 且无 `$DISPLAY` / `$WAYLAND_DISPLAY` + +需要强制其中一种模式: + +```bash +# 强制远程模式(URL + 二维码,不开浏览器) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --remote + +# 强制本地模式(自动开浏览器,无视启发式判断) +curl -fsSL https://agentkey.app/install.sh | bash -s -- --local +``` + +PowerShell:`-Remote` / `-Local`。 + +如果完全不想走 URL/二维码流程、想自己手动粘 Key,可以用 `npx -y @agentkey/mcp --setup` —— 交互式向导,问你要 Key 并让你勾选要写入的 MCP 客户端。
diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 7307fb9..c63b8e0 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -9,9 +9,11 @@ & ([scriptblock]::Create((irm https://agentkey.app/install.ps1))) -Only "claude-code,cursor" Behavior mirrors install.sh: checks Node >= 18 (installs via winget/scoop/choco), - runs `npx skills add` (auto-detects agents), then `npx @agentkey/mcp --auth-login` - to open a browser for device auth. MCP config is written automatically for - Claude Code / Claude Desktop / Cursor. + auto-detects which AI agents are installed and runs `npx skills add` for them, + then `npx @agentkey/mcp --auth-login` for device auth. The auth step opens a + local browser by default; under SSH / Docker / OpenClaw it switches to a + QR + URL flow that the user scans on a phone (`--no-browser` server-side flag). + MCP config is written automatically for Claude Code / Claude Desktop / Cursor. #> [CmdletBinding()] @@ -19,6 +21,11 @@ param( [switch]$Yes, [switch]$Interactive, [string]$Only, + [switch]$AllAgents, + [switch]$ListAgents, + [switch]$Remote, + [switch]$Local, + [switch]$ForceMcp, [switch]$SkipSkill, [switch]$SkipMcp, [switch]$Help @@ -29,6 +36,31 @@ $SkillRepo = 'chainbase-labs/agentkey' $McpPackage = '@agentkey/mcp' $NodeMinMajor = 18 +# ── Agent markers (mirror of install.sh) ────────────────────────────────── +# Subset of vercel-labs/skills' 45 supported agent IDs that have reliable +# Windows-side markers. Sync source: +# https://github.com/vercel-labs/skills (Supported Agents table). +$AgentMarkers = @( + @{ Id = 'claude-code'; Markers = @("path:$env:USERPROFILE\.claude.json", 'cmd:claude', "path:$env:APPDATA\Claude") } + @{ Id = 'cursor'; Markers = @("path:$env:USERPROFILE\.cursor", 'cmd:cursor', "path:$env:LOCALAPPDATA\Programs\cursor") } + @{ Id = 'codex'; Markers = @("path:$env:USERPROFILE\.codex", 'cmd:codex') } + @{ Id = 'gemini-cli'; Markers = @("path:$env:USERPROFILE\.gemini", 'cmd:gemini') } + @{ Id = 'opencode'; Markers = @("path:$env:USERPROFILE\.opencode", 'cmd:opencode') } + @{ Id = 'openclaw'; Markers = @("path:$env:USERPROFILE\.openclaw") } + @{ Id = 'qwen-code'; Markers = @("path:$env:USERPROFILE\.qwen", 'cmd:qwen') } + @{ Id = 'iflow-cli'; Markers = @("path:$env:USERPROFILE\.iflow", 'cmd:iflow') } + @{ Id = 'windsurf'; Markers = @("path:$env:USERPROFILE\.windsurf", 'cmd:windsurf') } + @{ Id = 'warp'; Markers = @("path:$env:USERPROFILE\.warp") } + @{ Id = 'amp'; Markers = @('cmd:amp') } + @{ Id = 'crush'; Markers = @('cmd:crush') } + @{ Id = 'goose'; Markers = @('cmd:goose') } + @{ Id = 'droid'; Markers = @('cmd:droid') } + @{ Id = 'kode'; Markers = @('cmd:kode') } + @{ Id = 'kilo'; Markers = @('cmd:kilo') } + @{ Id = 'kimi-cli'; Markers = @("path:$env:USERPROFILE\.kimi", 'cmd:kimi') } + @{ Id = 'kiro-cli'; Markers = @("path:$env:USERPROFILE\.kiro", 'cmd:kiro') } +) + # ── UI helpers ──────────────────────────────────────────────────────────── function Write-Banner { Write-Host '' @@ -52,6 +84,58 @@ function Write-Muted($text) { Write-Host " $text" -ForegroundColor DarkGray } function Die ($text) { Write-Err $text; exit 1 } +# ── Helpers: agent + remote detection ───────────────────────────────────── +function Test-AgentMarker { + param([string]$Marker) + if ($Marker.StartsWith('cmd:')) { + return [bool](Get-Command $Marker.Substring(4) -ErrorAction SilentlyContinue) + } + if ($Marker.StartsWith('path:')) { + return Test-Path -LiteralPath $Marker.Substring(5) + } + return $false +} + +function Get-DetectedAgents { + $hits = New-Object System.Collections.Generic.List[string] + foreach ($entry in $AgentMarkers) { + foreach ($m in $entry.Markers) { + if (Test-AgentMarker $m) { $hits.Add($entry.Id) | Out-Null; break } + } + } + return @($hits | Sort-Object -Unique) +} + +# Detect "remote install" — context where opening a browser on this host +# is futile (SSH, Docker, OpenClaw remote channels). Mirrors the bash +# script's logic. +function Test-RemoteInstall { + if ($script:Local) { return $false } + if ($script:Remote) { return $true } + + if (Test-Path -LiteralPath "$env:USERPROFILE\.openclaw") { return $true } + if ($env:SSH_CONNECTION -or $env:SSH_TTY) { return $true } + return $false +} + +# Cheap "is AgentKey already configured?" check across known MCP config files. +function Test-AlreadyAuthed { + $configs = @( + "$env:USERPROFILE\.claude.json", + "$env:USERPROFILE\.cursor\mcp.json", + "$env:APPDATA\Claude\claude_desktop_config.json" + ) + foreach ($cfg in $configs) { + if (-not (Test-Path -LiteralPath $cfg)) { continue } + $content = Get-Content -Raw -LiteralPath $cfg -ErrorAction SilentlyContinue + if (-not $content) { continue } + if ($content -match '"agentkey"' -and $content -match '"AGENTKEY_API_KEY"\s*:\s*"ak_[A-Za-z0-9_-]+"') { + return $true + } + } + return $false +} + # ── Help ────────────────────────────────────────────────────────────────── if ($Help) { @' @@ -65,13 +149,38 @@ Parameters: -Yes Non-interactive: install skill to every detected agent, no prompts -Interactive Force interactive mode (fails if console input is redirected) -Only Only install skill for these agents (e.g. "claude-code,cursor") + -AllAgents Skip auto-detection; let 'skills' CLI install for every detected agent + -ListAgents Print the agents we'd auto-select on this machine and exit + -Remote Force remote-install mode: print URL + QR for the auth step, + do NOT auto-open a local browser. Use this when running over + SSH, in WinRM, in a container, or via OpenClaw / Claude Code + remote channels. + -Local Force local mode (auto-open browser) and bypass remote heuristics + -ForceMcp Re-run MCP auth even if AgentKey is already configured -SkipSkill Skip the skill install step (only run MCP auth) -SkipMcp Skip the MCP auth step (only install the skill) -Help Show this help + +Behavior: + The installer auto-detects which AI agents are on this machine and + pre-selects them for skill installation. Remote-install mode is auto- + detected from %USERPROFILE%\.openclaw and SSH env vars; override with + -Remote / -Local. '@ exit 0 } +if ($Remote -and $Local) { + Die '-Remote and -Local are mutually exclusive.' +} + +if ($ListAgents) { + $detected = Get-DetectedAgents + if ($detected.Count -gt 0) { $detected -join "`n" | Write-Output } + else { Write-Host 'no agents detected on this host' -ForegroundColor Yellow } + exit 0 +} + Write-Banner # ── 1. Preflight ────────────────────────────────────────────────────────── @@ -151,15 +260,38 @@ if (-not (Get-Command npx -ErrorAction SilentlyContinue)) { # ── 2. Install the AgentKey skill ───────────────────────────────────────── if (-not $SkipSkill) { Write-Step '2. Install the AgentKey skill' - Write-Info "The 'skills' CLI will auto-detect every supported agent on this machine." - $skillsArgs = @('-y', 'skills', 'add', $SkillRepo, '-g') + # Resolve target agent list: + # 1. -Only wins (manual override) + # 2. else -AllAgents ⇒ no -a (let skills CLI auto-detect everything) + # 3. else our auto-detection ⇒ -a + # 4. else (nothing detected) ⇒ no -a (fall back to skills CLI default) + $targets = @() if ($Only) { - $agentList = $Only -split ',' | Where-Object { $_ -ne '' } + $targets = $Only -split ',' | Where-Object { $_ -ne '' } + Write-Info "Targeting agents from -Only: $($targets -join ', ')" + } elseif ($AllAgents) { + Write-Info "Installing for every agent the 'skills' CLI detects (-AllAgents)" + } else { + $targets = Get-DetectedAgents + if ($targets.Count -gt 0) { + Write-Ok "Detected agents on this host: $($targets -join ', ')" + Write-Muted '(override with -Only , or use -AllAgents)' + } else { + Write-Info "No agents auto-detected — letting 'skills' CLI scan." + } + } + + $skillsArgs = @('-y', 'skills', 'add', $SkillRepo, '-g') + if ($targets.Count -gt 0) { $skillsArgs += '-a' - $skillsArgs += $agentList + $skillsArgs += $targets + } + # Always pass -y in noninteractive mode AND when we already resolved + # an explicit target list — there's nothing left to ask the user. + if ($Mode -eq 'noninteractive' -or $targets.Count -gt 0) { + $skillsArgs += '-y' } - if ($Mode -eq 'noninteractive') { $skillsArgs += '-y' } & npx @skillsArgs if ($LASTEXITCODE -ne 0) { Die "Failed to install skill via 'skills' CLI" } @@ -170,22 +302,41 @@ if (-not $SkipSkill) { } # ── 3. MCP authentication ──────────────────────────────────────────────── -if (-not $SkipMcp) { - Write-Step '3. Register the MCP server (browser login)' - Write-Info 'Opening your browser for AgentKey device authentication ...' - Write-Muted 'When auth finishes, the MCP server is written into Claude Code / Claude Desktop / Cursor configs.' +if ($SkipMcp) { + Write-Step '3. Register the MCP server' + Write-Muted 'Skipped (-SkipMcp)' +} elseif ((Test-AlreadyAuthed) -and -not $ForceMcp) { + Write-Step '3. Register the MCP server' + Write-Ok 'AgentKey is already configured in an MCP client config — skipping auth.' + Write-Muted 'Re-run with -ForceMcp to authenticate again.' +} else { + $isRemote = Test-RemoteInstall + $authArgs = @('--auth-login') + + if ($isRemote) { + Write-Step '3. Register the MCP server (remote auth: scan QR with phone)' + Write-Info 'Detected remote install context — printing QR + URL instead of opening a browser here.' + if (Test-Path -LiteralPath "$env:USERPROFILE\.openclaw") { + Write-Muted ' reason: %USERPROFILE%\.openclaw exists (OpenClaw runtime)' + } elseif ($env:SSH_CONNECTION -or $env:SSH_TTY) { + Write-Muted ' reason: SSH session detected' + } + Write-Muted 'Override with -Local if you want a browser opened on this machine instead.' + $authArgs += '--no-browser' + } else { + Write-Step '3. Register the MCP server (browser login)' + Write-Info 'Opening your browser for AgentKey device authentication ...' + Write-Muted 'When auth finishes, the MCP server is written into Claude Code / Claude Desktop / Cursor configs.' + } Write-Host '' - & npx -y $McpPackage --auth-login + & npx -y $McpPackage @authArgs if ($LASTEXITCODE -ne 0) { Write-Err 'MCP auth failed.' - Write-Muted "Retry manually: npx -y $McpPackage --auth-login" + Write-Muted "Retry manually: npx -y $McpPackage $($authArgs -join ' ')" exit 1 } Write-Ok 'MCP server registered' -} else { - Write-Step '3. Register the MCP server' - Write-Muted 'Skipped (-SkipMcp)' } # ── 4. Summary ─────────────────────────────────────────────────────────── diff --git a/scripts/install.sh b/scripts/install.sh index 3a88e24..8eea267 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,6 +5,7 @@ # curl -fsSL https://agentkey.app/install.sh | bash -s -- --yes # curl -fsSL https://agentkey.app/install.sh | bash -s -- --interactive # curl -fsSL https://agentkey.app/install.sh | bash -s -- --only claude-code,cursor +# curl -fsSL https://agentkey.app/install.sh | bash -s -- --remote # curl -fsSL https://agentkey.app/install.sh | bash -s -- --skip-mcp # # The whole procedural body is wrapped in `main()` so that under `curl | bash` @@ -20,6 +21,37 @@ SKILL_REPO="chainbase-labs/agentkey" MCP_PACKAGE="@agentkey/mcp" NODE_MIN_MAJOR=18 +# ── Agent markers ───────────────────────────────────────────────────────── +# Subset of vercel-labs/skills' 45 supported agent IDs that have reliable +# on-disk markers (config dirs / binaries on PATH). Agents we can't probe +# cleanly (mostly VS Code extensions like cline/continue/roo) just don't get +# pre-detected — the user can pass --all-agents or --only to include them. +# Sync source: https://github.com/vercel-labs/skills (Supported Agents table). +# +# Format: |[,...] +# marker types: cmd:foo — `command -v foo` +# path:/abs/or/~path — file or dir exists (~ expands to $HOME) +AGENT_MARKERS=( + "claude-code|path:~/.claude.json,cmd:claude,path:~/Library/Application Support/Claude,path:~/.config/Claude" + "cursor|path:~/.cursor,cmd:cursor" + "codex|path:~/.codex,cmd:codex" + "gemini-cli|path:~/.gemini,cmd:gemini" + "opencode|path:~/.opencode,cmd:opencode" + "openclaw|path:~/.openclaw" + "qwen-code|path:~/.qwen,cmd:qwen" + "iflow-cli|path:~/.iflow,cmd:iflow" + "windsurf|path:~/.windsurf,cmd:windsurf" + "warp|path:~/.warp,path:~/Library/Application Support/dev.warp.Warp-Stable" + "amp|cmd:amp" + "crush|cmd:crush" + "goose|cmd:goose" + "droid|cmd:droid" + "kode|cmd:kode" + "kilo|cmd:kilo" + "kimi-cli|path:~/.kimi,cmd:kimi" + "kiro-cli|path:~/.kiro,cmd:kiro" +) + # ── Colors (only if stdout is a TTY) ───────────────────────────────────── # Use $'...' so variables hold real ESC bytes — otherwise heredoc output prints # the literal string "\033[1m" instead of applying the SGR code. @@ -70,15 +102,112 @@ Options: --yes, -y Non-interactive: install skill to every detected agent, no prompts --interactive Force interactive mode (fails if no TTY/terminal is reachable) --only Only install skill for these agents (comma-separated, e.g. claude-code,cursor) + --all-agents Skip auto-detection; let 'skills' CLI install for every detected agent + --list-agents Print the agents we'd auto-select on this machine and exit + --remote Force remote-install mode: print URL + QR for the auth step, + do NOT auto-open a local browser. Use this when running over + SSH, in Docker, or via OpenClaw / Claude Code remote channels. + --local Force local mode (auto-open browser) and bypass remote heuristics + --force-mcp Re-run MCP auth even if AgentKey is already configured --skip-skill Skip the skill install step (only run MCP auth) --skip-mcp Skip the MCP auth step (only install the skill) -h, --help Show this help -Default: interactive when a terminal is reachable (even under 'curl | bash'), - otherwise falls back to --yes. +Behavior: + Interactive mode is the default when a terminal is reachable; otherwise it + falls back to --yes. The installer auto-detects which AI agents are on this + machine and pre-selects them for skill installation. Remote-install mode is + auto-detected from \$HOME/.openclaw, SSH env vars, and missing \$DISPLAY; + override with --remote / --local. EOF } +# ── Helpers: agent + remote detection ───────────────────────────────────── + +# Expand a leading "~" to \$HOME (no glob expansion, no eval). +_expand_path() { + local p="$1" + case "$p" in + "~"|"~/"*) printf '%s\n' "$HOME${p#"~"}" ;; + *) printf '%s\n' "$p" ;; + esac +} + +# Probe a single marker: cmd:NAME (binary on PATH) or path:PATH (file/dir). +_probe_marker() { + local m="$1" + case "$m" in + cmd:*) command -v "${m#cmd:}" >/dev/null 2>&1 ;; + path:*) [ -e "$(_expand_path "${m#path:}")" ] ;; + *) return 1 ;; + esac +} + +# Print detected agent IDs as a comma-separated list (empty if none). +detect_agents() { + local entry id markers marker hits=() + for entry in "${AGENT_MARKERS[@]}"; do + id="${entry%%|*}" + markers="${entry#*|}" + # Any marker hit ⇒ agent detected. + IFS=',' read -ra marker_list <<<"$markers" + for marker in "${marker_list[@]}"; do + if _probe_marker "$marker"; then + hits+=("$id") + break + fi + done + done + if [ ${#hits[@]} -gt 0 ]; then + printf '%s\n' "${hits[@]}" | sort -u | paste -sd, - + fi +} + +# Detect "remote install" — a context where auto-opening a browser on this +# host is futile because the user isn't sitting in front of it. Mutates +# nothing; returns 0 (true) for remote, 1 (false) for local. +detect_remote() { + [ "$FORCE_LOCAL" = true ] && return 1 + [ "$FORCE_REMOTE" = true ] && return 0 + + # OpenClaw runtime — the project owner confirms ~/.openclaw exists in + # any host where OpenClaw is installed/active. Most reliable single + # signal because it doesn't depend on env-var inheritance through the + # docker → channel → shell chain. + [ -d "$HOME/.openclaw" ] && return 0 + + # Generic SSH session. + [ -n "${SSH_CONNECTION:-}" ] && return 0 + [ -n "${SSH_TTY:-}" ] && return 0 + + # Headless Linux (no GUI session). + if [ "$(uname -s)" = "Linux" ] \ + && [ -z "${DISPLAY:-}" ] \ + && [ -z "${WAYLAND_DISPLAY:-}" ]; then + return 0 + fi + + return 1 +} + +# Cheap "is AgentKey already configured?" check across known MCP config files. +# Greps for an agentkey block + a non-empty AGENTKEY_API_KEY of the expected +# shape. Returns 0 if at least one config has both. +already_authed() { + local cfg + for cfg in "$HOME/.claude.json" \ + "$HOME/.cursor/mcp.json" \ + "$HOME/Library/Application Support/Claude/claude_desktop_config.json" \ + "$HOME/.config/Claude/claude_desktop_config.json"; do + [ -f "$cfg" ] || continue + if grep -q '"agentkey"' "$cfg" 2>/dev/null \ + && grep -qE '"AGENTKEY_API_KEY"[^"]*"ak_[A-Za-z0-9_-]+"' "$cfg" 2>/dev/null; then + return 0 + fi + done + return 1 +} + install_node() { local platform="$1" ui_info "Installing Node.js v$NODE_MIN_MAJOR+ ..." @@ -118,6 +247,15 @@ main() { local SKIP_MCP=false local SKIP_SKILL=false local PRINT_HELP=false + local LIST_AGENTS=false + local ALL_AGENTS=false + local FORCE_MCP=false + # FORCE_REMOTE / FORCE_LOCAL are read by detect_remote(). Declared as + # plain (non-`local`) so the helpers see them — they're dynamic-scope + # accessible either way in bash, but explicit assignment here keeps + # `set -u` happy. + FORCE_REMOTE=false + FORCE_LOCAL=false while [ $# -gt 0 ]; do case "$1" in @@ -125,6 +263,11 @@ main() { --interactive) MODE=interactive; shift ;; --only) ONLY_AGENTS="${2:-}"; shift 2 ;; --only=*) ONLY_AGENTS="${1#*=}"; shift ;; + --all-agents) ALL_AGENTS=true; shift ;; + --list-agents) LIST_AGENTS=true; shift ;; + --remote) FORCE_REMOTE=true; shift ;; + --local) FORCE_LOCAL=true; shift ;; + --force-mcp) FORCE_MCP=true; shift ;; --skip-skill) SKIP_SKILL=true; shift ;; --skip-mcp) SKIP_MCP=true; shift ;; -h|--help) PRINT_HELP=true; shift ;; @@ -133,6 +276,20 @@ main() { done if $PRINT_HELP; then print_help; exit 0; fi + if $FORCE_REMOTE && $FORCE_LOCAL; then + die "--remote and --local are mutually exclusive" + fi + + if $LIST_AGENTS; then + local detected + detected="$(detect_agents)" + if [ -n "$detected" ]; then + printf '%s\n' "$detected" | tr ',' '\n' + else + printf 'no agents detected on this host\n' >&2 + fi + exit 0 + fi ui_banner @@ -207,15 +364,39 @@ main() { # ── 2. Install the AgentKey skill ───────────────────────────────────── if ! $SKIP_SKILL; then ui_step "2. Install the AgentKey skill" - ui_info "The 'skills' CLI will auto-detect every supported agent on this machine." - local SKILLS_ARGS=(-y skills add "$SKILL_REPO" -g) + # Resolve target agent list: + # 1. --only wins (manual override) + # 2. else --all-agents ⇒ no -a (let skills CLI auto-detect everything) + # 3. else our auto-detection ⇒ -a + # 4. else (nothing detected) ⇒ no -a (fall back to skills CLI default) + local TARGETS="" if [ -n "$ONLY_AGENTS" ]; then - # shellcheck disable=SC2206 - local AGENT_LIST=(${ONLY_AGENTS//,/ }) + TARGETS="$ONLY_AGENTS" + ui_info "Targeting agents from --only: $TARGETS" + elif $ALL_AGENTS; then + ui_info "Installing for every agent the 'skills' CLI detects (--all-agents)" + else + TARGETS="$(detect_agents)" + if [ -n "$TARGETS" ]; then + ui_ok "Detected agents on this host: $TARGETS" + ui_muted "(override with --only , or use --all-agents)" + else + ui_info "No agents auto-detected — letting 'skills' CLI scan." + fi + fi + + local SKILLS_ARGS=(-y skills add "$SKILL_REPO" -g) + if [ -n "$TARGETS" ]; then + # `skills` CLI accepts -a as either repeated or comma-separated. + # We pass each ID individually for maximum compatibility. + local AGENT_LIST=() + IFS=',' read -ra AGENT_LIST <<<"$TARGETS" SKILLS_ARGS+=(-a "${AGENT_LIST[@]}") fi - if [ "$MODE" = noninteractive ]; then + # Always pass -y in noninteractive mode AND when we already resolved + # an explicit target list — there's nothing left to ask the user. + if [ "$MODE" = noninteractive ] || [ -n "$TARGETS" ]; then SKILLS_ARGS+=(-y) fi @@ -237,21 +418,44 @@ main() { fi # ── 3. MCP authentication ──────────────────────────────────────────── - if ! $SKIP_MCP; then - ui_step "3. Register the MCP server (browser login)" - ui_info "Opening your browser for AgentKey device authentication ..." - ui_muted "When auth finishes, the MCP server is written into Claude Code / Claude Desktop / Cursor configs." + if $SKIP_MCP; then + ui_step "3. Register the MCP server" + ui_muted "Skipped (--skip-mcp)" + elif already_authed && ! $FORCE_MCP; then + ui_step "3. Register the MCP server" + ui_ok "AgentKey is already configured in an MCP client config — skipping auth." + ui_muted "Re-run with --force-mcp to authenticate again." + else + # Decide local-vs-remote and route the MCP CLI flags accordingly. + local IS_REMOTE=false + if detect_remote; then IS_REMOTE=true; fi + + local AUTH_ARGS=(--auth-login) + if $IS_REMOTE; then + ui_step "3. Register the MCP server (remote auth: scan QR with phone)" + ui_info "Detected remote install context — printing QR + URL instead of opening a browser here." + if [ -d "$HOME/.openclaw" ]; then + ui_muted " reason: \$HOME/.openclaw exists (OpenClaw runtime)" + elif [ -n "${SSH_CONNECTION:-}" ] || [ -n "${SSH_TTY:-}" ]; then + ui_muted " reason: SSH session detected" + elif [ "$(uname -s)" = "Linux" ]; then + ui_muted " reason: Linux without \$DISPLAY / \$WAYLAND_DISPLAY" + fi + ui_muted "Override with --local if you want a browser opened on this machine instead." + AUTH_ARGS+=(--no-browser) + else + ui_step "3. Register the MCP server (browser login)" + ui_info "Opening your browser for AgentKey device authentication ..." + ui_muted "When auth finishes, the MCP server is written into Claude Code / Claude Desktop / Cursor configs." + fi echo - if ! npx -y "$MCP_PACKAGE" --auth-login; then + if ! npx -y "$MCP_PACKAGE" "${AUTH_ARGS[@]}"; then ui_error "MCP auth failed." - ui_muted "Retry manually: npx -y $MCP_PACKAGE --auth-login" + ui_muted "Retry manually: npx -y $MCP_PACKAGE ${AUTH_ARGS[*]}" exit 1 fi ui_ok "MCP server registered" - else - ui_step "3. Register the MCP server" - ui_muted "Skipped (--skip-mcp)" fi # ── 4. Summary ───────────────────────────────────────────────────────