1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//! Per-agent facts, kept as data.
use crate::agent::AuthKind;
/// Everything discovery and the adapters need to know about one agent.
pub(crate) struct AgentProfile {
pub id: &'static str,
pub name: &'static str,
/// Binary the user installs (`claude`, `codex`, `hermes`).
pub cli: &'static str,
/// Env var that overrides executable resolution (tests, custom installs).
pub executable_env: &'static str,
/// Config directory under the user's home (`.claude`), and the env var
/// that relocates it.
pub config_dir: &'static str,
pub config_home_env: Option<&'static str>,
/// Which adapter drives it and how to put the CLI in protocol mode.
pub connection: Connection,
/// Presence of any marker means "logged in", read offline. Empty when
/// the agent has no known marker (auth reported as `None`).
pub auth_markers: &'static [AuthMarker],
/// `Some(kind)` when a successful session open proves the agent is logged
/// in with this kind (probed: the agent refuses to open logged out).
/// `None` when an open proves nothing (opencode serves free models
/// logged out) or the adapter reads auth from its own wire.
pub open_auth_kind: Option<AuthKind>,
/// Fingerprints of the agent's own logged-out error, matched against a
/// failed open's message and stderr (probed per agent; empty = the agent
/// uses the ACP auth-required code, which needs no hint).
pub auth_error_hints: &'static [&'static str],
/// Args after the executable that start the agent's own login flow;
/// empty when the agent only logs in interactively.
pub login_args: &'static [&'static str],
/// Shown in `MissingAgent` when the agent is not found.
pub install_hint: &'static str,
/// Extra install locations searched last. `~`-relative unless absolute.
pub extra_paths: &'static [&'static str],
}
pub(crate) enum Connection {
Acp { args: &'static [&'static str] },
Native(NativeKind),
}
pub(crate) enum NativeKind {
// Current native supported agents.
Claude,
Codex,
/// Antigravity's `agy` CLI: its own stream-json event dialect
/// (`--input-format=stream-json`, validated 2026-08-23). Adapter pending.
Antigravity,
}
/// Offline auth markers for fast discovery.
pub(crate) enum AuthMarker {
/// File under the agent's config home.
ConfigFile(&'static str, AuthKind),
/// macOS keychain generic-password service.
Keychain(&'static str, AuthKind),
/// API key environment variable; doubles as the `EnvVar` login method.
ApiKeyEnv(&'static str),
}
/// The supported agents. ACP agents beyond these land with their verified
/// launch quirks (P2); guessed wire flags do not ship.
pub(crate) static PROFILES: &[AgentProfile] = &[
AgentProfile {
id: "claude",
name: "Claude Code",
cli: "claude",
executable_env: "ANYAGENT_CLAUDE_BIN",
config_dir: ".claude",
config_home_env: Some("CLAUDE_CONFIG_DIR"),
connection: Connection::Native(NativeKind::Claude),
auth_markers: &[
AuthMarker::ConfigFile(".credentials.json", AuthKind::Subscription),
AuthMarker::Keychain("Claude Code-credentials", AuthKind::Subscription),
AuthMarker::ApiKeyEnv("ANTHROPIC_API_KEY"),
],
open_auth_kind: None,
auth_error_hints: &[],
login_args: &["auth", "login"],
install_hint: "npm install -g @anthropic-ai/claude-code",
extra_paths: &[".claude/local", ".local/bin"],
},
AgentProfile {
id: "codex",
name: "Codex",
cli: "codex",
executable_env: "ANYAGENT_CODEX_BIN",
config_dir: ".codex",
config_home_env: Some("CODEX_HOME"),
connection: Connection::Native(NativeKind::Codex),
// No env marker: app-server 0.147.0 ignores `OPENAI_API_KEY` entirely
// (probed 2026-08-27, ticket 10) — auth comes only from auth.json.
auth_markers: &[AuthMarker::ConfigFile("auth.json", AuthKind::Subscription)],
open_auth_kind: None,
auth_error_hints: &[],
login_args: &["login"],
install_hint: "npm install -g @openai/codex",
extra_paths: &[],
},
// Gemini CLI is deprecated upstream (personal OAuth sunset, users moved
// to Antigravity); its profile was replaced 2026-08-23.
AgentProfile {
id: "antigravity",
name: "Antigravity",
cli: "agy",
executable_env: "ANYAGENT_ANTIGRAVITY_BIN",
config_dir: ".gemini",
config_home_env: None,
connection: Connection::Native(NativeKind::Antigravity),
auth_markers: &[
AuthMarker::ConfigFile("jetski-standalone-oauth-token", AuthKind::Subscription),
AuthMarker::ConfigFile("oauth_creds.json", AuthKind::Subscription),
],
open_auth_kind: None,
auth_error_hints: &[],
login_args: &[],
install_hint: "install Antigravity from https://antigravity.google, then run `agy install`",
extra_paths: &[".local/bin"],
},
AgentProfile {
id: "grok",
name: "Grok",
cli: "grok",
executable_env: "ANYAGENT_GROK_BIN",
config_dir: ".grok",
config_home_env: None,
// Flag placement verified against grok 1.0.4 (comet field notes):
// `--no-auto-update` is TOP-LEVEL and kills a silent multi-second
// launch-time update check; `--no-leader` (on the subcommand) starts
// a fresh agent instead of attaching to a shared leader process via
// ~/.grok/leader.sock — a wedged/stale leader reads as total silence.
connection: Connection::Acp {
args: &["--no-auto-update", "agent", "--no-leader", "stdio"],
},
// OAuth login writes ~/.grok/auth.json (probed 2026-08-28); the env
// key is the API-key alternative.
auth_markers: &[
AuthMarker::ConfigFile("auth.json", AuthKind::Subscription),
AuthMarker::ApiKeyEnv("XAI_API_KEY"),
],
open_auth_kind: Some(AuthKind::Subscription),
// Grok's -32000 carries no runnable method (probed 1.0.x: "no auth
// method id provided"); login is its own TUI, so the fallback
// command is the bare executable.
auth_error_hints: &["Authentication required"],
login_args: &[],
install_hint: "npm install -g @xai-official/grok \
(or `curl -fsSL https://x.ai/cli/install.sh | bash`)",
extra_paths: &[".local/bin", ".grok/bin", ".npm-global/bin"],
},
AgentProfile {
id: "hermes",
name: "Hermes Agent",
cli: "hermes",
executable_env: "ANYAGENT_HERMES_BIN",
config_dir: ".hermes",
config_home_env: None,
connection: Connection::Acp { args: &["acp"] },
auth_markers: &[AuthMarker::ConfigFile("auth.json", AuthKind::ApiKey)],
open_auth_kind: Some(AuthKind::ApiKey),
auth_error_hints: &["No LLM provider configured"],
login_args: &["login"],
install_hint: "see https://github.com/NousResearch/hermes-agent",
extra_paths: &[".local/bin"],
},
AgentProfile {
id: "opencode",
name: "opencode",
cli: "opencode",
executable_env: "ANYAGENT_OPENCODE_BIN",
// Data home; `opencode auth login` writes auth.json here.
config_dir: ".local/share/opencode",
config_home_env: None,
connection: Connection::Acp { args: &["acp"] },
auth_markers: &[AuthMarker::ConfigFile("auth.json", AuthKind::Subscription)],
open_auth_kind: None,
auth_error_hints: &[],
login_args: &["auth", "login"],
install_hint: "brew install sst/tap/opencode",
extra_paths: &[],
},
AgentProfile {
id: "kiro",
name: "Kiro CLI",
cli: "kiro-cli",
executable_env: "ANYAGENT_KIRO_BIN",
config_dir: ".kiro",
config_home_env: None,
connection: Connection::Acp { args: &["acp"] },
// The credential is a row in a platform data-dir sqlite (auth_kv in
// data.sqlite3), which also exists logged out — no honest offline
// marker, so discovery reports Unknown and `probe` answers for real.
auth_markers: &[],
open_auth_kind: Some(AuthKind::Subscription),
auth_error_hints: &["not logged in"],
login_args: &["login"],
install_hint: "install Kiro CLI from https://kiro.dev",
extra_paths: &[".local/bin"],
},
AgentProfile {
id: "qwen",
name: "Qwen Code",
cli: "qwen",
executable_env: "ANYAGENT_QWEN_BIN",
config_dir: ".qwen",
config_home_env: None,
connection: Connection::Acp {
args: &["--experimental-acp"],
},
auth_markers: &[
AuthMarker::ConfigFile("oauth_creds.json", AuthKind::Subscription),
// Advertised by qwen's own auth method (probed 0.22.0).
AuthMarker::ApiKeyEnv("OPENAI_API_KEY"),
],
open_auth_kind: Some(AuthKind::Subscription),
auth_error_hints: &[],
login_args: &[],
install_hint: "npm install -g @qwen-code/qwen-code",
extra_paths: &[],
},
];
/// The environment variable that relocates an agent's config home, if it has
/// one. `None` for an agent outside the catalog (an ad-hoc ACP install) or one
/// with no such variable.
pub(crate) fn config_home_env(id: &str) -> Option<&'static str> {
PROFILES
.iter()
.find(|p| p.id == id)
.and_then(|p| p.config_home_env)
}