diff --git a/README.md b/README.md index fef8c99..5fa0a43 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ This package can detect the following AI agents and development environments: - **Cursor** (Anysphere) - **Claude Code** (Anthropic) - **Claude Cowork** (Anthropic) +- **Kimi Code** (Moonshot AI) +- **Grok Build** (xAI) - **Devin** (Cognition Labs) - **Gemini CLI** (Google) - **Codex** (OpenAI) diff --git a/detect_agent/__init__.py b/detect_agent/__init__.py index 55755ba..baf4580 100644 --- a/detect_agent/__init__.py +++ b/detect_agent/__init__.py @@ -15,15 +15,17 @@ CURSOR: Literal["cursor"] = "cursor" CURSOR_CLI: Literal["cursor-cli"] = "cursor-cli" -CLAUDE: Literal["claude"] = "claude" +CLAUDE: Literal["claude_code"] = "claude_code" COWORK: Literal["cowork"] = "cowork" DEVIN: Literal["devin"] = "devin" REPLIT: Literal["replit"] = "replit" -GEMINI: Literal["gemini"] = "gemini" -CODEX: Literal["codex"] = "codex" +KIMI: Literal["kimi"] = "kimi" +GROK: Literal["grok"] = "grok" +GEMINI: Literal["gemini_cli"] = "gemini_cli" +CODEX: Literal["codex_cli"] = "codex_cli" ANTIGRAVITY: Literal["antigravity"] = "antigravity" AUGMENT_CLI: Literal["augment-cli"] = "augment-cli" -OPENCODE: Literal["opencode"] = "opencode" +OPENCODE: Literal["open_code"] = "open_code" GITHUB_COPILOT: Literal["github-copilot"] = "github-copilot" CLINE: Literal["cline"] = "cline" GOOSE: Literal["goose"] = "goose" @@ -35,15 +37,17 @@ KnownAgentNames = Literal[ "cursor", "cursor-cli", - "claude", + "claude_code", "cowork", "devin", "replit", - "gemini", - "codex", + "kimi", + "grok", + "gemini_cli", + "codex_cli", "antigravity", "augment-cli", - "opencode", + "open_code", "github-copilot", "cline", "goose", diff --git a/detect_agent/agents.json b/detect_agent/agents.json index 78f24f6..f5f8f11 100644 --- a/detect_agent/agents.json +++ b/detect_agent/agents.json @@ -27,9 +27,30 @@ ] } }, + { + "key": "KIMI", + "name": "kimi", + "description": "Kimi Code plugin hooks. KIMI_CODE_HOME may be configured outside an active Kimi session, so detection uses the plugin-scoped KIMI_PLUGIN_ROOT marker.", + "match": { + "type": "anyOf", + "conditions": [{ "type": "env_set", "name": "KIMI_PLUGIN_ROOT" }] + } + }, + { + "key": "GROK", + "name": "grok", + "description": "Grok Build plugin hooks. Evaluated before Claude Code because Grok supports Claude Code plugins and may expose compatibility markers.", + "match": { + "type": "anyOf", + "conditions": [ + { "type": "env_set", "name": "GROK_PLUGIN_ROOT" }, + { "type": "env_set", "name": "GROK_PLUGIN_DATA" } + ] + } + }, { "key": "GEMINI", - "name": "gemini", + "name": "gemini_cli", "match": { "type": "anyOf", "conditions": [{ "type": "env_set", "name": "GEMINI_CLI" }] @@ -45,7 +66,7 @@ }, { "key": "CODEX", - "name": "codex", + "name": "codex_cli", "match": { "type": "anyOf", "conditions": [ @@ -77,7 +98,7 @@ }, { "key": "OPENCODE", - "name": "opencode", + "name": "open_code", "match": { "type": "anyOf", "conditions": [ @@ -123,7 +144,7 @@ { "key": "COWORK", "name": "cowork", - "description": "Claude Cowork. Evaluated before `claude` so the more specific COWORK marker wins. Requires CLAUDE_CODE_IS_COWORK plus a Claude Code marker.", + "description": "Claude Cowork. Evaluated before `claude_code` so the more specific COWORK marker wins. Requires CLAUDE_CODE_IS_COWORK plus a Claude Code marker.", "match": { "type": "allOf", "conditions": [ @@ -140,7 +161,7 @@ }, { "key": "CLAUDE", - "name": "claude", + "name": "claude_code", "match": { "type": "anyOf", "conditions": [ diff --git a/tests/test_detect_agent.py b/tests/test_detect_agent.py index 1795207..01f4dbf 100644 --- a/tests/test_detect_agent.py +++ b/tests/test_detect_agent.py @@ -21,6 +21,9 @@ "CURSOR_TRACE_ID", "CURSOR_AGENT", "CURSOR_EXTENSION_HOST_ROLE", + "KIMI_PLUGIN_ROOT", + "GROK_PLUGIN_ROOT", + "GROK_PLUGIN_DATA", "GEMINI_CLI", "CLINE_ACTIVE", "CODEX_SANDBOX", diff --git a/tests/testcases.json b/tests/testcases.json index 49943ed..2d59e29 100644 --- a/tests/testcases.json +++ b/tests/testcases.json @@ -54,26 +54,54 @@ }, { - "name": "detects gemini from GEMINI_CLI", + "name": "detects kimi from KIMI_PLUGIN_ROOT", + "env": { "KIMI_PLUGIN_ROOT": "/tmp/kimi-plugin" }, + "expectedIsAgent": true, + "expectedAgentKey": "KIMI" + }, + { + "name": "does not detect kimi from KIMI_CODE_HOME alone", + "env": { "KIMI_CODE_HOME": "/tmp/kimi" }, + "expectedIsAgent": false + }, + + { + "name": "detects grok from GROK_PLUGIN_ROOT", + "env": { "GROK_PLUGIN_ROOT": "/tmp/grok-plugin" }, + "expectedIsAgent": true, + "expectedAgentKey": "GROK" + }, + { + "name": "detects grok from GROK_PLUGIN_DATA before Claude compatibility markers", + "env": { + "GROK_PLUGIN_DATA": "/tmp/grok-data", + "CLAUDE_CODE": "1" + }, + "expectedIsAgent": true, + "expectedAgentKey": "GROK" + }, + + { + "name": "detects gemini_cli from GEMINI_CLI", "env": { "GEMINI_CLI": "1" }, "expectedIsAgent": true, "expectedAgentKey": "GEMINI" }, { - "name": "detects codex from CODEX_SANDBOX", + "name": "detects codex_cli from CODEX_SANDBOX", "env": { "CODEX_SANDBOX": "seatbelt" }, "expectedIsAgent": true, "expectedAgentKey": "CODEX" }, { - "name": "detects codex from CODEX_CI", + "name": "detects codex_cli from CODEX_CI", "env": { "CODEX_CI": "1" }, "expectedIsAgent": true, "expectedAgentKey": "CODEX" }, { - "name": "detects codex from CODEX_THREAD_ID", + "name": "detects codex_cli from CODEX_THREAD_ID", "env": { "CODEX_THREAD_ID": "thread-123" }, "expectedIsAgent": true, "expectedAgentKey": "CODEX" @@ -94,13 +122,13 @@ }, { - "name": "detects opencode from OPENCODE_CLIENT", + "name": "detects open_code from OPENCODE_CLIENT", "env": { "OPENCODE_CLIENT": "opencode" }, "expectedIsAgent": true, "expectedAgentKey": "OPENCODE" }, { - "name": "detects opencode from OPENCODE", + "name": "detects open_code from OPENCODE", "env": { "OPENCODE": "1" }, "expectedIsAgent": true, "expectedAgentKey": "OPENCODE" @@ -148,13 +176,13 @@ }, { - "name": "detects claude from CLAUDE_CODE", + "name": "detects claude_code from CLAUDE_CODE", "env": { "CLAUDE_CODE": "1" }, "expectedIsAgent": true, "expectedAgentKey": "CLAUDE" }, { - "name": "detects claude from CLAUDECODE", + "name": "detects claude_code from CLAUDECODE", "env": { "CLAUDECODE": "1" }, "expectedIsAgent": true, "expectedAgentKey": "CLAUDE"