From 39bde14dc0bda4e36b3637e5ac25f20b7a829aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:42:56 +0200 Subject: [PATCH 1/3] feat(agents): detect GitHub Copilot agent and CLI --- README.md | 2 +- src/agents.ts | 11 +++++++++-- test/agents.test.ts | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f6cb308..7e2e6e2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/agents.ts b/src/agents.ts index 63b9943..ebdb81d 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -9,6 +9,7 @@ export type AgentName = | (string & {}) | "cursor" | "claude" + | "copilot" | "devin" | "replit" | "gemini" @@ -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 (can be detected using COPILOT_AGENT, COPILOT_CLI) + ["copilot", ["COPILOT_AGENT", "COPILOT_CLI"]], + // -- 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/)]], @@ -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) { diff --git a/test/agents.test.ts b/test/agents.test.ts index b23dbc6..ac56c2b 100644 --- a/test/agents.test.ts +++ b/test/agents.test.ts @@ -16,6 +16,8 @@ const agentEnvKeys = [ "GOOSE_PROVIDER", "JUNIE_DATA", "JUNIE_SHIM_PATH", + "COPILOT_AGENT", + "COPILOT_CLI", "CURSOR_AGENT", "TERM_PROGRAM", "EDITOR", @@ -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"], @@ -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"], ]; From 4735af5fb17fb12bfb15db0db3111d68b4025219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 08:25:27 +0200 Subject: [PATCH 2/3] docs: document agent normalization in AGENTS.md --- AGENTS.md | 2 +- src/agents.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 28fc0ac..73910c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,7 @@ 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) => boolean)` - `InternalAgent = [agentName: AgentName, envChecks: EnvCheck[]]` diff --git a/src/agents.ts b/src/agents.ts index ebdb81d..b1ee97b 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -45,7 +45,7 @@ const agents: InternalAgent[] = [ // ✅ Verified by junie (can be detected using JUNIE_DATA, JUNIE_SHIM_PATH) ["junie", ["JUNIE_DATA", "JUNIE_SHIM_PATH"]], - // ❓ not tested (can be detected using COPILOT_AGENT, COPILOT_CLI) + // ❓ not tested ["copilot", ["COPILOT_AGENT", "COPILOT_CLI"]], // -- IDEs (checked last — agents running inside these should be detected first) -- From 03a6b2f6cd9aa9f4ee3786de310d34eb6a72b762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:07:47 +0200 Subject: [PATCH 3/3] docs: document Copilot markers and fix self-test command AGENTS.md still used a CJS require that no longer exists; switch the self-test to ESM and spell out COPILOT_AGENT / COPILOT_CLI detection. Leave the copilot entry as not tested until a real Copilot session observes those env vars. --- AGENTS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 73910c7..3542855 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,6 +73,7 @@ All detection modules follow the same pattern: - 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) @@ -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. @@ -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`: