Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ All detection modules follow the same pattern:

### Agent Detection (`src/agents.ts`)

- **Priority**: `AI_AGENT` env var (generic override) → ordered tuple scan
- **Priority**: `AI_AGENT` env var (generic override, normalizing known aliases such as `github_copilot_vscode_agent` to `copilot`) → ordered tuple scan
- Internal types:
- `EnvCheck = string | ((env: Record<string, string | undefined>) => boolean)`
- `InternalAgent = [agentName: AgentName, envChecks: EnvCheck[]]`
- Each agent maps to an array of `EnvCheck` items; the first matching check wins
- When `EnvCheck` is a string, the env var must be truthy; when it's a function, it receives the full env object and returns a boolean
- `envMatcher(envKey, regex, opts?)` helper creates regex-based env var matchers (e.g., `envMatcher("TERM_PROGRAM", /kiro/)`)
- `opts.noTTY: true` makes the matcher only match in a non-interactive context (when `process.stdout.isTTY` is falsy). Use for detections keyed off generic terminal env vars that an IDE sets in its integrated terminal too — e.g. `kiro` (`TERM_PROGRAM=kiro`), where a TTY means a human is at the IDE terminal, not the agent CLI (see #185)
- **GitHub Copilot** (`copilot`): detected via `COPILOT_AGENT` (VS Code Copilot agent terminals) or `COPILOT_CLI` (Copilot CLI / Copilot app). Explicit `AI_AGENT=github_copilot_vscode_agent` is normalized to `copilot`.
- IDEs (cursor, devin, kiro) are checked last so agents running inside them are detected first
- Exports: `detectAgent()`, `agentInfo` (singleton), `agent` (name shorthand), `isAgent` (boolean)

Expand Down Expand Up @@ -118,7 +119,7 @@ The goal: a consumer that imports only e.g. `runtime` or `provider` should not d

```bash
pnpm run build
node -e "console.log(require('./dist/index.cjs').agentInfo)"
node -e "import('./dist/index.mjs').then(m => console.log(m.agentInfo))"
```

If output is like `{ name: "..." }`, it works. If output is `{}`, agent is NOT DETECTED.
Expand All @@ -129,6 +130,8 @@ Debug current env variables:
node -e "console.log(process.env)"
```

Do **not** invent environment variables or mark a detector as verified unless those vars were actually present when the self-test ran. Unit tests may stub env vars to cover detection logic; stubbed tests do **not** replace the live self-test verification comment.

**After running the self-test:**

- If agent **was detected**: add/update a comment above the agent's entry in the `agents` record in `src/agents.ts`:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ console.log({ isAgent, agent, agentInfo });

Set the `AI_AGENT` env var to explicitly specify the agent name. Use `detectAgent()` to re-run detection.

Supported agents: `cursor`, `claude`, `devin`, `replit`, `gemini`, `codex`, `auggie`, `opencode`, `kiro`, `goose`, `pi`, `junie`
Supported agents: `cursor`, `claude`, `copilot`, `devin`, `replit`, `gemini`, `codex`, `auggie`, `opencode`, `kiro`, `goose`, `pi`, `junie`

## Flags

Expand Down
11 changes: 9 additions & 2 deletions src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type AgentName =
| (string & {})
| "cursor"
| "claude"
| "copilot"
| "devin"
| "replit"
| "gemini"
Expand Down Expand Up @@ -44,6 +45,9 @@ const agents: InternalAgent[] = [
// ✅ Verified by junie (can be detected using JUNIE_DATA, JUNIE_SHIM_PATH)
["junie", ["JUNIE_DATA", "JUNIE_SHIM_PATH"]],

// ❓ not tested
["copilot", ["COPILOT_AGENT", "COPILOT_CLI"]],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated AGENTS.md to document the alias normalization behavior and standardized the comment.


// -- IDEs (checked last — agents running inside these should be detected first) --
// ✅ Verified by devin (can be detected using EDITOR, BROWSER, PATH)
["devin", [/* #__PURE__ */ envMatcher("EDITOR", /devin/)]],
Expand Down Expand Up @@ -81,14 +85,17 @@ export type AgentInfo = {
/**
* Detects the current AI coding agent from environment variables.
*
* Supported agents: `cursor`, `claude`, `devin`, `replit`, `gemini`, `codex`, `auggie`, `opencode`, `kiro`, `goose`, `pi`, `junie`
* Supported agents: `cursor`, `claude`, `copilot`, `devin`, `replit`, `gemini`, `codex`, `auggie`, `opencode`, `kiro`, `goose`, `pi`, `junie`
*
* You can also set the `AI_AGENT` environment variable to explicitly specify the agent name.
*/
export function detectAgent(): AgentInfo {
const aiAgent = env.AI_AGENT;
if (aiAgent) {
return { name: aiAgent.toLowerCase() };
const normalized = aiAgent.toLowerCase();
return {
name: normalized === "github_copilot_vscode_agent" ? "copilot" : normalized,
};
}
for (const [name, checks] of agents) {
for (const check of checks) {
Expand Down
15 changes: 15 additions & 0 deletions test/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const agentEnvKeys = [
"GOOSE_PROVIDER",
"JUNIE_DATA",
"JUNIE_SHIM_PATH",
"COPILOT_AGENT",
"COPILOT_CLI",
"CURSOR_AGENT",
"TERM_PROGRAM",
"EDITOR",
Expand Down Expand Up @@ -54,6 +56,17 @@ describe("detectAgent", () => {
expect(detectAgent()).toEqual({ name: "custom-agent" });
});

it("AI_AGENT normalizes github_copilot_vscode_agent to copilot", () => {
vi.stubEnv("AI_AGENT", "github_copilot_vscode_agent");
expect(detectAgent()).toEqual({ name: "copilot" });
});

it("AI_AGENT normalizes github_copilot_vscode_agent even when COPILOT_AGENT is set", () => {
vi.stubEnv("AI_AGENT", "github_copilot_vscode_agent");
vi.stubEnv("COPILOT_AGENT", "1");
expect(detectAgent()).toEqual({ name: "copilot" });
});

describe("simple env var checks", () => {
const cases: [string, string, string][] = [
["claude", "CLAUDECODE", "1"],
Expand All @@ -67,6 +80,8 @@ describe("detectAgent", () => {
["goose", "GOOSE_PROVIDER", "openai"],
["junie", "JUNIE_DATA", "/home/user/.local/share/junie"],
["junie", "JUNIE_SHIM_PATH", "/home/user/.local/bin/junie"],
["copilot", "COPILOT_AGENT", "1"],
["copilot", "COPILOT_CLI", "1"],
["cursor", "CURSOR_AGENT", "1"],
];

Expand Down
Loading