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 ───────────────────────────────────────────────────────