Compare commits

...
7 Commits
Author SHA1 Message Date
David Mokos d2f8b90f53 adds Grok Bot environment detection (#21)
Grok Bot sets CURSOR_AGENT alongside SAND_BOX_BOOT_ID and SAND_BOX_STORE_ID, so it is matched before Cursor. Session ids still come from CURSOR_CONVERSATION_ID.
2026-10-24 03:10:00 -07:00
David Mokos e308d262b1 adds Factory Droid detection (#20) 2026-09-24 04:58:40 -07:00
David Mokos 09f4c420ad adds Grok environment detection (#19) 2026-08-24 23:30:14 +02:00
David Mokos 9205525ad4 adds Muse Code environment detection (#18) 2026-08-17 18:02:47 +02:00
David Mokos cf68eb6e0c updates repository metadata for npm provenance (#16) 2026-08-04 16:15:59 +02:00
David Mokos b7c8ccd0aa documents opencode detection and bumps version to 0.1.5 (#15) 2026-08-04 16:11:59 +02:00
Din 1cad0bda77 [lib] detect OpenCode from environment variables (#14)
OpenCode was only detectable through the process-tree strategy, which is
opt-in via experimentalProcessTree. With the default strategies it was
never detected.

The OpenCode CLI unconditionally sets OPENCODE=1 and OPENCODE_PID in its
yargs middleware, so both are inherited by any process it spawns.
2026-08-04 07:01:07 -07:00
6 changed files with 161 additions and 9 deletions

No files matched your search

+13 -3
View File
@@ -2,11 +2,11 @@
Detect whether a JavaScript CLI is running inside a coding agent.
Please [open an issue](https://github.com/davidmokos/detect-agent/issues/new)
Please [open an issue](https://github.com/expo/agent-cli-detector/issues/new)
if your coding agent isn't properly detected :)
[![npm](https://img.shields.io/npm/v/agent-cli-detector.svg)](https://www.npmjs.com/package/agent-cli-detector)
[![Tests](https://github.com/davidmokos/detect-agent/actions/workflows/test.yml/badge.svg)](https://github.com/davidmokos/detect-agent/actions)
[![Tests](https://github.com/expo/agent-cli-detector/actions/workflows/test.yml/badge.svg)](https://github.com/expo/agent-cli-detector/actions)
## Installation
@@ -41,18 +41,27 @@ Officially supported coding agents:
| [Codex](https://developers.openai.com/codex/) | `codex` | ✅ |
| [Cursor](https://cursor.com/) | `cursor` | ✅ |
| [Devin](https://devin.ai/) | `devin` | 🚫 |
| [Factory Droid](https://docs.factory.ai/droid-cli/overview) | `droid` | 🚫 |
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `gemini` | 🚫 |
| [GitHub Copilot CLI](https://github.com/github/copilot-cli) | `copilot` | ✅ |
| [Grok](https://x.ai/build) | `grok` | ✅ |
| [Grok Bot](https://x.ai/bot) | `grok-bot` | ✅ |
| [Kilo Code](https://kilocode.ai/) | `kilocode` | ✅ |
| [Kiro](https://kiro.dev/) | `kiro` | ✅ |
| [Muse Code][muse-code] | `muse` | 🚫 |
| [OpenCode](https://opencode.ai/) | `opencode` | 🚫 |
| [Pi](https://github.com/badlogic/pi-mono) | `pi` | 🚫 |
| [Replit](https://replit.com/) | `replit` | ✅ |
| [Rork](https://rork.com/) | `rork` | 🚫 |
[muse-code]: https://developer.meta.com/ai/products/muse-code/
Detection is data-driven: the exact environment variables and process
patterns for each agent live in [`src/agents.ts`](src/agents.ts).
OpenCode 1.0.19 and later is detected from environment variables by default.
OpenCode 0.x requires experimental process-tree detection.
## API
### `detectAgent([options])`
@@ -95,7 +104,8 @@ A string containing the agent's session identifier, normalized from
agent-specific environment variables such as `CODEX_THREAD_ID`,
`CURSOR_CONVERSATION_ID`, `CLAUDE_CODE_SESSION_ID`,
`ANTIGRAVITY_TRAJECTORY_ID`, `KIRO_SESSION_ID`, `KILO_RUN_ID`, and
`COPILOT_AGENT_SESSION_ID`, and `REPLIT_SESSION`. Omitted when the agent doesn't expose one
`COPILOT_AGENT_SESSION_ID`, `REPLIT_SESSION`, and `GROK_SESSION_ID`.
Omitted when the agent doesn't expose one
(see the Session ID column in the support table above).
### `isRunningFromAgent([options])`
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "agent-cli-detector",
"version": "0.1.4",
"version": "0.1.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "agent-cli-detector",
"version": "0.1.4",
"version": "0.1.8",
"license": "MIT",
"bin": {
"agent-cli-detector": "dist/cli.js"
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "agent-cli-detector",
"version": "0.1.4",
"version": "0.1.8",
"description": "Detect whether a JavaScript CLI is running inside a coding agent.",
"type": "module",
"sideEffects": false,
@@ -52,10 +52,10 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/davidmokos/detect-agent.git"
"url": "git+https://github.com/expo/agent-cli-detector.git"
},
"bugs": {
"url": "https://github.com/davidmokos/detect-agent/issues"
"url": "https://github.com/expo/agent-cli-detector/issues"
},
"homepage": "https://github.com/davidmokos/detect-agent#readme"
"homepage": "https://github.com/expo/agent-cli-detector#readme"
}
+32
View File
@@ -12,6 +12,15 @@ export const defaultAgents = [
process: [{ pattern: /^codex$/i }],
sessionEnv: ["CODEX_THREAD_ID"]
},
{
id: "grok-bot",
name: "Grok Bot",
env: [
{ name: "SAND_BOX_BOOT_ID" },
{ name: "SAND_BOX_STORE_ID" }
],
sessionEnv: ["CURSOR_CONVERSATION_ID"]
},
{
id: "cursor",
name: "Cursor",
@@ -35,6 +44,10 @@ export const defaultAgents = [
{
id: "opencode",
name: "OpenCode",
env: [
{ name: "OPENCODE", value: "1" },
{ name: "OPENCODE_PID" }
],
process: [{ pattern: /^opencode$/i }]
},
{
@@ -42,12 +55,26 @@ export const defaultAgents = [
name: "Devin",
process: [{ pattern: /devin/i }]
},
{
id: "droid",
name: "Factory Droid",
env: [{ name: "FACTORY_ENV", value: "production" }]
},
{
id: "gemini",
name: "Gemini CLI",
env: [{ name: "GEMINI_CLI", value: "1" }],
process: [{ pattern: /^gemini$/i }]
},
{
id: "grok",
name: "Grok",
env: [
{ name: "GROK_AGENT", value: "1" },
{ name: "GROK_SESSION_ID" }
],
sessionEnv: ["GROK_SESSION_ID"]
},
{
id: "antigravity",
name: "Antigravity",
@@ -90,6 +117,11 @@ export const defaultAgents = [
process: [{ pattern: /kiro/i }],
sessionEnv: ["KIRO_SESSION_ID"]
},
{
id: "muse",
name: "Muse Code",
env: [{ name: "MUSE_RELEASE_INFO" }]
},
{
id: "cline",
name: "Cline",
+4
View File
@@ -6,10 +6,14 @@ export type AgentId =
| "copilot"
| "cursor"
| "devin"
| "droid"
| "gemini"
| "grok"
| "grok-bot"
| "kiro"
| "kilocode"
| "claude-code"
| "muse"
| "opencode"
| "pi"
| "replit"
+106
View File
@@ -64,6 +64,83 @@ test("detects replit and normalizes its session id", () => {
});
});
test("detects grok and normalizes its session id", () => {
const result = detectAgent({
env: {
GROK_AGENT: "1",
GROK_SESSION_ID: "01a035a8-166c-7771-8e5e-f67e0dd98c83"
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "grok",
name: "Grok",
sessionId: "01a035a8-166c-7771-8e5e-f67e0dd98c83"
});
});
test("detects Grok Bot from SAND_BOX_BOOT_ID and normalizes the conversation id", () => {
const result = detectAgent({
env: {
CURSOR_AGENT: "1",
CURSOR_CONVERSATION_ID: "conversation-123",
SAND_BOX_BOOT_ID: "boot-456"
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "grok-bot",
name: "Grok Bot",
sessionId: "conversation-123"
});
});
test("detects cursor from CURSOR_AGENT without Grok Bot signals", () => {
const result = detectAgent({
env: {
CURSOR_AGENT: "1",
CURSOR_CONVERSATION_ID: "conversation-123"
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "cursor",
name: "Cursor",
sessionId: "conversation-123"
});
});
test("detects Grok Bot from SAND_BOX_STORE_ID alone", () => {
const result = detectAgent({
env: {
SAND_BOX_STORE_ID: "store-789"
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "grok-bot",
name: "Grok Bot"
});
});
test("detects Factory Droid from FACTORY_ENV", () => {
const result = detectAgent({
env: {
FACTORY_ENV: "production"
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "droid",
name: "Factory Droid"
});
});
test("detects rork from RORK_API_URL", () => {
const result = detectAgent({
env: {
@@ -78,6 +155,23 @@ test("detects rork from RORK_API_URL", () => {
});
});
test("detects Muse Code from MUSE_RELEASE_INFO", () => {
const result = detectAgent({
env: {
MUSE_RELEASE_INFO: JSON.stringify({
channel: "muse-stable",
version: "0.1.0-R708.1"
})
}
});
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "muse",
name: "Muse Code"
});
});
test("detects bolt from any known environment variable", () => {
for (const envName of ["BOLT_ENV", "BOLT_ORIGIN", "BOLT_SERVER_URL"]) {
const result = detectAgent({
@@ -94,6 +188,18 @@ test("detects bolt from any known environment variable", () => {
}
});
test("detects opencode from any known environment variable", () => {
for (const env of [{ OPENCODE: "1" }, { OPENCODE_PID: "12345" }]) {
const result = detectAgent({ env });
assert.equal(result.detected, true);
assert.deepEqual(result.agent, {
id: "opencode",
name: "OpenCode"
});
}
});
test("default strategies only use environment variables", () => {
assert.deepEqual(
createDefaultStrategies().map((strategy) => strategy.name),