mirror of
https://github.com/chainbase-labs/Agentkey.git
synced 2026-09-20 14:20:23 +08:00
feat(install): auto-detect agents, route MCP auth to QR mode for remote installs (#18)
## Summary 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](https://github.com/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 what we'd auto-select (and exit) - `--all-agents` — skip our detection, let `skills` CLI scan everything - `--only` — manual override (unchanged) ### 2. Local-vs-remote MCP auth routing The current `--auth-login` always 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…". Detection (any signal fires ⇒ remote): | Signal | Notes | |---|---| | `$HOME/.openclaw` exists | OpenClaw runtime — most reliable single signal | | `$SSH_CONNECTION` / `$SSH_TTY` | Generic SSH session | | Linux without `$DISPLAY` / `$WAYLAND_DISPLAY` | Headless | When remote, pass `--no-browser` to the MCP CLI, which prints URL + ANSI QR for the user to scan with a phone. New flags: - `--remote` / `--local` — force either mode - `--force-mcp` — re-auth even if AgentKey is already configured 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 entirely (`--force-mcp` to override). ### 3. Docs `README.md` + `docs/README_zh.md` updated to document the new flags and add a dedicated **"Installing over SSH / Docker / OpenClaw"** section. ## Companion PRs (server side) The MCP-server changes that this installer routes to ship in a chain of three PRs against `chainbase-labs/AgentKey-Server`: | PR | Status | What it adds | |---|---|---| | [#2](https://github.com/chainbase-labs/AgentKey-Server/pull/2) | merged | `--no-browser` / `--qr` / `--no-qr` flags + `qrcode-terminal` dep (v0.3.5 source bump) | | [#3](https://github.com/chainbase-labs/AgentKey-Server/pull/3) | merged | Bump to 0.3.6 — npm `0.3.5` had been published from a pre-merge commit and shipped stale `dist/` | | [#4](https://github.com/chainbase-labs/AgentKey-Server/pull/4) | open | Fix QR not rendering in `--no-browser` (CJS interop bug — `qrcode-terminal`'s `generate` lives on `.default` under NodeNext); bump to 0.3.7 | **This installer needs `@agentkey/mcp@0.3.7`** to be on npm for the QR flow to actually work. Sequence: 1. Merge #4 ✅ 2. Maintainer runs `cd mcp-server && npm publish` (no npm-publish CI workflow) ✅ 3. Merge this PR — `npx -y @agentkey/mcp` will then pull 0.3.7+ and the `--remote` path renders the QR This installer change is **forward-compatible**: it can land anytime — older `@agentkey/mcp` versions silently ignore the unknown `--no-browser` flag, so worst case a remote user gets the old browser-opens-on-the-wrong-host UX until the new mcp publishes. ## Test plan Verified end-to-end on macOS against a local build of `@agentkey/mcp@0.3.7` (server PR #4): - [x] Bash syntax check passes (`bash -n install.sh`) - [x] `install.sh --help` renders all 5 new flag rows - [x] `install.sh --list-agents` correctly prints detected agents on test host (10 agents incl. `openclaw`) - [x] `install.sh --remote --local` exits with code 1 (mutex) - [x] `install.sh --skip-skill` short-circuits with "AgentKey is already configured…" when configs have agentkey - [x] `install.sh --remote --skip-skill --force-mcp` end-to-end: prints `Detected remote install context — printing QR + URL instead of opening a browser here`, then `reason: $HOME/.openclaw exists (OpenClaw runtime)`, launches mcp CLI, renders 16-row ANSI QR, polls — no `open` process spawned - [x] All 4 mcp 0.3.7 flag combinations behave correctly (`--no-browser` / `--no-browser --no-qr` / `--qr` / no-flags back-compat) - [ ] `install.ps1` not lint-checked locally (no pwsh on test host); logic is a strict mirror of bash — needs Windows smoke before merge - [ ] Inside a real OpenClaw container — confirm `~/.openclaw` triggers as expected ## Why now Users running the installer via OpenClaw / Claude Code remote channels (on a phone) currently see a black-hole UX: the browser opens on the wrong machine and the install process appears to hang. The detection here removes the manual `--remote` flag for the common case. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -218,21 +218,32 @@ Still stuck? See the "Where do I get help" item below.
|
||||
<details>
|
||||
<summary><b>Advanced install options (CI / specific agents / manual two-step)</b></summary>
|
||||
|
||||
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.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Installing over SSH, inside Docker, or via OpenClaw / Claude Code remote channels</b></summary>
|
||||
|
||||
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.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
+39
-3
@@ -218,21 +218,32 @@ npx skills remove chainbase-labs/agentkey
|
||||
<details>
|
||||
<summary><b>进阶安装(CI / 指定 Agent / 手动两步)</b></summary>
|
||||
|
||||
安装器会自动探测本机已安装的 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 客户端。
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>在 SSH / Docker / OpenClaw 远程通道里安装</b></summary>
|
||||
|
||||
如果安装命令是在你看不到屏幕的机器上跑(远程 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 客户端。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
+168
-17
@@ -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 <a,b,c> 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."
|
||||
|
||||
# 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 <detected list>
|
||||
# 4. else (nothing detected) ⇒ no -a (fall back to skills CLI default)
|
||||
$targets = @()
|
||||
if ($Only) {
|
||||
$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 <ids>, or use -AllAgents)'
|
||||
} else {
|
||||
Write-Info "No agents auto-detected — letting 'skills' CLI scan."
|
||||
}
|
||||
}
|
||||
|
||||
$skillsArgs = @('-y', 'skills', 'add', $SkillRepo, '-g')
|
||||
if ($Only) {
|
||||
$agentList = $Only -split ',' | Where-Object { $_ -ne '' }
|
||||
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 ───────────────────────────────────────────────────────────
|
||||
|
||||
+220
-16
@@ -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: <agent-id>|<marker>[,<marker>...]
|
||||
# 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 <a,b,c> 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."
|
||||
|
||||
# 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 <detected list>
|
||||
# 4. else (nothing detected) ⇒ no -a (fall back to skills CLI default)
|
||||
local TARGETS=""
|
||||
if [ -n "$ONLY_AGENTS" ]; then
|
||||
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 <ids>, 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 "$ONLY_AGENTS" ]; then
|
||||
# shellcheck disable=SC2206
|
||||
local AGENT_LIST=(${ONLY_AGENTS//,/ })
|
||||
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 ───────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user