ai-agent-detect 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +73 -12
  2. package/agents/aider/index.js +15 -0
  3. package/agents/amp/index.js +11 -0
  4. package/agents/antigravity/index.js +11 -0
  5. package/agents/augment/index.js +11 -0
  6. package/agents/claude-code/index.js +24 -0
  7. package/agents/cline/index.js +12 -0
  8. package/agents/codex/index.js +19 -0
  9. package/agents/continue/index.js +11 -0
  10. package/agents/cowork/index.js +16 -0
  11. package/agents/crush/index.js +11 -0
  12. package/agents/cursor/index.js +19 -0
  13. package/agents/devin/index.js +12 -0
  14. package/agents/gemini-cli/index.js +11 -0
  15. package/agents/github-copilot/index.js +20 -0
  16. package/agents/goose/index.js +14 -0
  17. package/agents/grok/index.js +13 -0
  18. package/agents/index.js +54 -0
  19. package/agents/junie/index.js +14 -0
  20. package/agents/kilo/index.js +17 -0
  21. package/agents/kiro/index.js +12 -0
  22. package/agents/opencode/index.js +16 -0
  23. package/agents/openhands/index.js +17 -0
  24. package/agents/pi/index.js +12 -0
  25. package/agents/qwen-code/index.js +15 -0
  26. package/agents/replit/index.js +12 -0
  27. package/agents/roo/index.js +16 -0
  28. package/fixtures/aider.json +21 -0
  29. package/fixtures/amp.json +14 -0
  30. package/fixtures/antigravity.json +20 -0
  31. package/fixtures/augment.json +20 -0
  32. package/fixtures/claude-code.json +41 -0
  33. package/fixtures/cline.json +36 -0
  34. package/fixtures/codex.json +31 -0
  35. package/fixtures/continue.json +20 -0
  36. package/fixtures/cowork.json +21 -0
  37. package/fixtures/crush.json +20 -0
  38. package/fixtures/cursor.json +24 -0
  39. package/fixtures/devin.json +16 -0
  40. package/fixtures/gemini-cli.json +14 -0
  41. package/fixtures/github-copilot.json +52 -0
  42. package/fixtures/goose.json +22 -0
  43. package/fixtures/grok.json +51 -0
  44. package/fixtures/junie.json +20 -0
  45. package/fixtures/kilo.json +28 -0
  46. package/fixtures/kiro.json +20 -0
  47. package/fixtures/opencode.json +16 -0
  48. package/fixtures/openhands.json +23 -0
  49. package/fixtures/pi.json +20 -0
  50. package/fixtures/qwen-code.json +23 -0
  51. package/fixtures/replit.json +20 -0
  52. package/fixtures/roo.json +38 -0
  53. package/index.d.ts +37 -0
  54. package/index.js +139 -5
  55. package/package.json +28 -3
package/README.md CHANGED
@@ -1,22 +1,83 @@
1
1
  # ai-agent-detect
2
2
 
3
- Detect which AI agent is running your code — structured identity and capabilities.
3
+ Detect which AI agent is running your code — structured identity, not just a boolean.
4
4
 
5
- **Status: name claimed, v0 in development.** This package does not do anything useful yet.
5
+ **Status: early but real.** 25 agents in the registry, every signal evidence-tiered and fixture-tested (82 tests); the API may still shift before `1.0`.
6
6
 
7
- ## What it will be
7
+ ```js
8
+ import { detect, isAgent } from 'ai-agent-detect';
8
9
 
9
- Runtime identification for the agentic era — `isatty()` had fifty years; agents are the third consumer class:
10
+ isAgent(); // boolean the new isatty()
11
+ isAgent('claude-code'); // assert style: am I running under this one?
10
12
 
11
- - **Structured identity**: `{ name, version, mode, source, confidence }` — parsed, not a raw string. Reads the `AI_AGENT` environment variable standard, the `AGENT` convention (Goose, Amp), and vendor tells (`CLAUDECODE=1`, …).
12
- - **Signals beyond env vars**: parent-process-tree walking (detect the agent ancestor even with a stripped environment), TTY state (interactive vs headless).
13
- - **Capability detection**: not just *which* agent, but what it honors — which context files it reads, which skill directories it discovers.
14
- - **Adaptive-output helpers**: `isAgent()` as the new `isatty()` — emit agent-optimized output when your consumer is an agent.
13
+ detect();
14
+ // {
15
+ // name: 'claude-code', vendor: 'anthropic',
16
+ // version: '2.1.205', mode: 'agent',
17
+ // source: 'env:AI_AGENT', confidence: 'high', corroborated: true,
18
+ // raw: { AI_AGENT: 'claude-code_2-1-205_agent', CLAUDECODE: '1', ... },
19
+ // claims: [ ... ] // every claim the env carries — see below
20
+ // }
21
+ ```
15
22
 
16
- ## Relationship to `detect-agent`
23
+ ## Why claims, not a single winner
17
24
 
18
- [`detect-agent`](https://www.npmjs.com/package/detect-agent) (Vercel) answers the boolean question *is* this an agent environment, and its name via declarative rules. This package aims one level up: structured identity, richer signals than env/file existence checks can express, and capabilities. It will consume the `agents.json` definitions as a compatibility tier.
25
+ Agents run agents. A grok session spawned inside a Claude Code session passes the inherited `AI_AGENT` through to its children (live-probed) environment variables cannot tell you which *layer* set them. So `detect()` reports **every claim** the env carries and never silently picks between conflicting ones. The env cannot order nesting layers, so `claims[]` doesn't pretend to; genuine stack ordering (via process-tree walk) is `detectDeep()`'s job planned, `ai-agent-detect/node` subpath.
19
26
 
20
- ## Alias
27
+ Every claim carries its evidence: `source` (which signal matched), `raw` (the matched variables), `confidence`. Apply your own trust policy instead of inheriting ours.
28
+
29
+ ## What it reads
30
+
31
+ 1. **`AI_AGENT`** — both value dialects in the wild: `name@version` (Vercel's spec: `devin@1`) and `name_version_mode` (Anthropic ships `claude-code_2-1-205_agent`).
32
+ 2. **`AGENT`** — the older convention, value-split: `AGENT=goose` names the agent; `AGENT=1` (opencode) proves agenthood without naming it — `isAgent()` true, every assert form false.
33
+ 3. **Vendor tells** — per-agent detector functions. Code, not a condition engine: the claude-code detector recovers the version from `CLAUDE_CODE_EXECPATH` when `AI_AGENT` is stripped; no declarative rule format can express that.
34
+
35
+ ## Supported agents
36
+
37
+ Evidence tiers: **probed** (live env snapshot recorded by us) > **source** (read in the agent's source code) > **doc** (vendor documentation) > **community** (reports) > **vercel-rules** (adopted from detect-agent's rules, not independently verified).
38
+
39
+ | agent | signal | evidence |
40
+ |---|---|---|
41
+ | claude-code | `AI_AGENT` triple, `CLAUDECODE=1`, version via `CLAUDE_CODE_EXECPATH` | probed |
42
+ | grok | **none** — no identity var; passes inherited `AI_AGENT` through | probed |
43
+ | gemini-cli | `GEMINI_CLI=1` (deliberate, documented) | source+doc |
44
+ | qwen-code | `QWEN_CODE=1` + session-id family (fork renamed Gemini's flag) | source |
45
+ | opencode | `OPENCODE=1`, `AGENT=1`, `OPENCODE_PID` | source |
46
+ | kilo | `KILO=1`, `KILO_CLIENT`, **version via `KILOCODE_VERSION`** (+ fork-parent `OPENCODE=1`, `AGENT=1`) | source |
47
+ | codex | sandbox side effects only (`CODEX_SANDBOX*`); **nothing under `danger-full-access`** | source |
48
+ | goose | `AGENT=goose` + `GOOSE_TERMINAL=1` — on two of three execution paths only | source |
49
+ | cline | `CLINE_ACTIVE=true` — VS Code terminals only | source |
50
+ | roo | `ROO_ACTIVE=true` (terminals), `ROO_CLI_RUNTIME=1` (headless) | source |
51
+ | aider | `OR_APP_NAME=Aider` + `OR_SITE_URL` (accidental fingerprint) | source |
52
+ | openhands | sandbox furniture: `OH_*` vars, `PS1` markers | source |
53
+ | crush | **none** — sets no identity variable | source |
54
+ | continue | **none** — sets no identity variable | source |
55
+ | github-copilot | `COPILOT_CLI=1` (documented), `COPILOT_AGENT_SESSION_ID`; `AI_AGENT=github-copilot(-cli)` | doc |
56
+ | amp | `AGENT=amp` (reported, unverified) | community |
57
+ | cursor | `CURSOR_AGENT=1`, `CURSOR_TRACE_ID`, `CURSOR_EXTENSION_HOST_ROLE` | community |
58
+ | antigravity | `ANTIGRAVITY_AGENT` | vercel-rules |
59
+ | augment | `AUGMENT_AGENT` | vercel-rules |
60
+ | cowork | `CLAUDE_CODE_IS_COWORK` atop claude-code vars | vercel-rules |
61
+ | junie | `JUNIE_DATA` / `JUNIE_SHIM_PATH` | vercel-rules |
62
+ | kiro | `TERM_PROGRAM~kiro` (low confidence — ambience, not identity) | vercel-rules |
63
+ | pi | PATH contains `.pi/agent` (low confidence) | vercel-rules |
64
+ | replit | `REPL_ID` (environment marker, not agent-specific) | vercel-rules |
65
+ | devin | file tell only (`/opt/.devin`) — outside the env core, planned for `/node` | vercel-rules |
66
+
67
+ The no-signal entries (grok, crush, continue; codex under `danger-full-access`) are deliberate: an agent that cannot be detected from env is recorded as such — honesty over reach. Any agent that sets `AI_AGENT` or a name-valued `AGENT` is detected without a registry entry. Coverage caveats (goose's per-path signals, cline's terminal-only marker) live as comments in each detector and in the fixtures.
68
+
69
+ ## Design
70
+
71
+ - **Zero runtime dependencies**, ESM, Node ≥ 18.
72
+ - **Edge-safe core**: no `node:` imports; env access is guarded (`globalThis.process?.env`), so Workers/Deno/Bun run it and env-less runtimes return `null`. Anything runtime-specific will live behind subpath exports.
73
+ - **Sync and cheap**: no spawn, no disk, no network. `process.env` is copied once per process (it's a proxy — see `bench.js`); `detect()` ≈ 0.5µs, `isAgent(name)` short-circuits at ≈ 0.15µs.
74
+ - **Fixtures are the ground truth**: every registry entry ships a recorded env snapshot with probe date and method (`fixtures/`). The test matrix runs every fixture against every detector — no false positives is as tested as no false negatives.
75
+
76
+ ## Contributing an agent
77
+
78
+ One folder, one fixture, one PR:
79
+
80
+ 1. `agents/<name>/index.js` — `{ name, vendor, procNames, pidEnvVar, detect(env) }`, registered in `agents/index.js` (alphabetical).
81
+ 2. `fixtures/<name>.json` — a recorded env snapshot from a live session (probe date + method required; the matrix test fails any entry without one).
82
+ 3. `npm test`.
21
83
 
22
- `agent-detect` is held as an alias of this package.
@@ -0,0 +1,15 @@
1
+ // aider — evidence, tiers, and probe TODOs: .kanbento/harnesses/aider.md
2
+ // Accidental fingerprint (OpenRouter attribution vars) — confidence capped at medium.
3
+ export default {
4
+ name: 'aider',
5
+ vendor: 'aider',
6
+ procNames: ['aider'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ if (env.OR_APP_NAME === 'Aider') raw.OR_APP_NAME = env.OR_APP_NAME;
11
+ if (env.OR_SITE_URL?.includes('aider.chat')) raw.OR_SITE_URL = env.OR_SITE_URL;
12
+ if (Object.keys(raw).length === 0) return null;
13
+ return { confidence: 'medium', raw };
14
+ },
15
+ };
@@ -0,0 +1,11 @@
1
+ // amp — evidence, tiers, and probe TODOs: .kanbento/harnesses/amp.md
2
+ // AGENT=amp is generic (core rung 2); no vendor var exists — this detector never fires.
3
+ export default {
4
+ name: 'amp',
5
+ vendor: 'sourcegraph',
6
+ procNames: ['amp'],
7
+ pidEnvVar: null,
8
+ detect() {
9
+ return null;
10
+ },
11
+ };
@@ -0,0 +1,11 @@
1
+ // antigravity — evidence, tiers, and probe TODOs: .kanbento/harnesses/antigravity.md
2
+ export default {
3
+ name: 'antigravity',
4
+ vendor: 'google',
5
+ procNames: ['antigravity'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ if (!env.ANTIGRAVITY_AGENT) return null;
9
+ return { confidence: 'medium', raw: { ANTIGRAVITY_AGENT: env.ANTIGRAVITY_AGENT } };
10
+ },
11
+ };
@@ -0,0 +1,11 @@
1
+ // augment — evidence, tiers, and probe TODOs: .kanbento/harnesses/augment.md
2
+ export default {
3
+ name: 'augment',
4
+ vendor: 'augment',
5
+ procNames: ['auggie'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ if (!env.AUGMENT_AGENT) return null;
9
+ return { confidence: 'medium', raw: { AUGMENT_AGENT: env.AUGMENT_AGENT } };
10
+ },
11
+ };
@@ -0,0 +1,24 @@
1
+ // claude-code — evidence, tiers, and probe TODOs: .kanbento/harnesses/claude-code.md
2
+ export default {
3
+ name: 'claude-code',
4
+ vendor: 'anthropic',
5
+ procNames: ['claude'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ if (env.CLAUDECODE !== '1' && !env.CLAUDE_CODE) return null;
9
+ // agents/cowork owns the marked case (specificity wins)
10
+ if (env.CLAUDE_CODE_IS_COWORK) return null;
11
+ const raw = {};
12
+ for (const key of [
13
+ 'CLAUDECODE',
14
+ 'CLAUDE_CODE_ENTRYPOINT',
15
+ 'CLAUDE_CODE_EXECPATH',
16
+ 'CLAUDE_CODE_CHILD_SESSION',
17
+ ]) {
18
+ if (env[key] !== undefined) raw[key] = env[key];
19
+ }
20
+ // richer than a condition engine: the exec path carries the version
21
+ const version = env.CLAUDE_CODE_EXECPATH?.match(/\/versions\/(\d[\d.]*)/)?.[1] ?? null;
22
+ return { confidence: 'high', version, raw };
23
+ },
24
+ };
@@ -0,0 +1,12 @@
1
+ // cline — evidence, tiers, and probe TODOs: .kanbento/harnesses/cline.md
2
+ // Marker exists on VS Code integrated terminals only — absence proves nothing.
3
+ export default {
4
+ name: 'cline',
5
+ vendor: 'cline',
6
+ procNames: ['cline'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (env.CLINE_ACTIVE !== 'true') return null;
10
+ return { confidence: 'high', raw: { CLINE_ACTIVE: env.CLINE_ACTIVE } };
11
+ },
12
+ };
@@ -0,0 +1,19 @@
1
+ // codex — evidence, tiers, and probe TODOs: .kanbento/harnesses/codex.md
2
+ // No marker under danger-full-access; CODEX_SANDBOX is macOS-only.
3
+ export default {
4
+ name: 'codex',
5
+ vendor: 'openai',
6
+ procNames: ['codex'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ if (env.CODEX_SANDBOX_NETWORK_DISABLED === '1') raw.CODEX_SANDBOX_NETWORK_DISABLED = env.CODEX_SANDBOX_NETWORK_DISABLED;
11
+ if (env.CODEX_SANDBOX) raw.CODEX_SANDBOX = env.CODEX_SANDBOX;
12
+ // CODEX_CI / CODEX_THREAD_ID: vercel-rules tier, hence the medium below
13
+ if (env.CODEX_CI) raw.CODEX_CI = env.CODEX_CI;
14
+ if (env.CODEX_THREAD_ID) raw.CODEX_THREAD_ID = env.CODEX_THREAD_ID;
15
+ if (Object.keys(raw).length === 0) return null;
16
+ const sourceConfirmed = raw.CODEX_SANDBOX_NETWORK_DISABLED || raw.CODEX_SANDBOX;
17
+ return { confidence: sourceConfirmed ? 'high' : 'medium', raw };
18
+ },
19
+ };
@@ -0,0 +1,11 @@
1
+ // continue — evidence, tiers, and probe TODOs: .kanbento/harnesses/continue.md
2
+ // No env signal exists — this detector never fires.
3
+ export default {
4
+ name: 'continue',
5
+ vendor: 'continue',
6
+ procNames: ['cn'],
7
+ pidEnvVar: null,
8
+ detect() {
9
+ return null;
10
+ },
11
+ };
@@ -0,0 +1,16 @@
1
+ // cowork — evidence, tiers, and probe TODOs: .kanbento/harnesses/cowork.md
2
+ // Runs Claude Code underneath; the claude-code detector defers when the marker is set.
3
+ export default {
4
+ name: 'cowork',
5
+ vendor: 'anthropic',
6
+ procNames: [],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (!env.CLAUDE_CODE_IS_COWORK) return null;
10
+ if (env.CLAUDECODE !== '1' && !env.CLAUDE_CODE) return null;
11
+ const raw = { CLAUDE_CODE_IS_COWORK: env.CLAUDE_CODE_IS_COWORK };
12
+ if (env.CLAUDECODE !== undefined) raw.CLAUDECODE = env.CLAUDECODE;
13
+ if (env.CLAUDE_CODE !== undefined) raw.CLAUDE_CODE = env.CLAUDE_CODE;
14
+ return { confidence: 'medium', raw };
15
+ },
16
+ };
@@ -0,0 +1,11 @@
1
+ // crush — evidence, tiers, and probe TODOs: .kanbento/harnesses/crush.md
2
+ // No env signal exists — this detector never fires.
3
+ export default {
4
+ name: 'crush',
5
+ vendor: 'charm',
6
+ procNames: ['crush'],
7
+ pidEnvVar: null,
8
+ detect() {
9
+ return null;
10
+ },
11
+ };
@@ -0,0 +1,19 @@
1
+ // cursor — evidence, tiers, and probe TODOs: .kanbento/harnesses/cursor.md
2
+ // All tells are community-tier (none on an official reference page).
3
+ export default {
4
+ name: 'cursor',
5
+ vendor: 'anysphere',
6
+ procNames: ['cursor-agent', 'agent'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ for (const key of ['CURSOR_AGENT', 'CURSOR_TRACE_ID', 'CURSOR_CLI', 'CURSOR_EXTENSION_HOST_ROLE']) {
11
+ if (env[key] !== undefined) raw[key] = env[key];
12
+ }
13
+ if (env.CURSOR_AGENT === '1') return { confidence: 'high', raw };
14
+ // vercel-rules tier (detect-agent@1.0.1): the CLI in extension-host mode
15
+ if (env.CURSOR_EXTENSION_HOST_ROLE === 'agent-exec') return { confidence: 'high', raw };
16
+ if (env.CURSOR_TRACE_ID) return { confidence: 'medium', raw };
17
+ return null;
18
+ },
19
+ };
@@ -0,0 +1,12 @@
1
+ // devin — evidence, tiers, and probe TODOs: .kanbento/harnesses/devin.md
2
+ // Only tell is a file check (/opt/.devin) — outside the env core; never fires here.
3
+ export default {
4
+ name: 'devin',
5
+ vendor: 'cognition',
6
+ procNames: [],
7
+ pidEnvVar: null,
8
+ filesTells: ['/opt/.devin'],
9
+ detect() {
10
+ return null;
11
+ },
12
+ };
@@ -0,0 +1,11 @@
1
+ // gemini-cli — evidence, tiers, and probe TODOs: .kanbento/harnesses/gemini-cli.md
2
+ export default {
3
+ name: 'gemini-cli',
4
+ vendor: 'google',
5
+ procNames: ['gemini'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ if (env.GEMINI_CLI !== '1') return null;
9
+ return { confidence: 'high', raw: { GEMINI_CLI: env.GEMINI_CLI } };
10
+ },
11
+ };
@@ -0,0 +1,20 @@
1
+ // github-copilot — evidence, tiers, and probe TODOs: .kanbento/harnesses/github-copilot.md
2
+ // COPILOT_CLI=1 is vendor-documented; the COPILOT_MODEL trio is an unverified fallback.
3
+ export default {
4
+ name: 'github-copilot',
5
+ vendor: 'github',
6
+ procNames: ['copilot'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ if (env.COPILOT_CLI === '1') raw.COPILOT_CLI = env.COPILOT_CLI;
11
+ if (env.COPILOT_AGENT_SESSION_ID) raw.COPILOT_AGENT_SESSION_ID = env.COPILOT_AGENT_SESSION_ID;
12
+ if (Object.keys(raw).length > 0) return { confidence: 'high', raw };
13
+ // vercel-rules tier, unverified in vendor sources
14
+ for (const key of ['COPILOT_MODEL', 'COPILOT_ALLOW_ALL', 'COPILOT_GITHUB_TOKEN']) {
15
+ if (env[key]) raw[key] = env[key];
16
+ }
17
+ if (Object.keys(raw).length === 0) return null;
18
+ return { confidence: 'medium', raw };
19
+ },
20
+ };
@@ -0,0 +1,14 @@
1
+ // goose — evidence, tiers, and probe TODOs: .kanbento/harnesses/goose.md
2
+ // Vars set on two of three execution paths — absence proves nothing.
3
+ export default {
4
+ name: 'goose',
5
+ vendor: 'block',
6
+ procNames: ['goose'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (env.GOOSE_TERMINAL !== '1') return null;
10
+ const raw = { GOOSE_TERMINAL: env.GOOSE_TERMINAL };
11
+ if (env.AGENT_SESSION_ID) raw.AGENT_SESSION_ID = env.AGENT_SESSION_ID;
12
+ return { confidence: 'high', raw };
13
+ },
14
+ };
@@ -0,0 +1,13 @@
1
+ // grok — evidence, tiers, and probe TODOs: .kanbento/harnesses/grok.md
2
+ // No identity var (probed) — this detector never fires.
3
+ export default {
4
+ name: 'grok',
5
+ vendor: 'xai',
6
+ // binary is literally `agent` (~/.grok/bin/agent) — generic name; the ppid
7
+ // rung must corroborate via the executable path, not the name alone
8
+ procNames: ['agent'],
9
+ pidEnvVar: null,
10
+ detect() {
11
+ return null;
12
+ },
13
+ };
@@ -0,0 +1,54 @@
1
+ // One agent = one folder = one easy PR. Keep alphabetical.
2
+ import aider from './aider/index.js';
3
+ import amp from './amp/index.js';
4
+ import antigravity from './antigravity/index.js';
5
+ import augment from './augment/index.js';
6
+ import claudeCode from './claude-code/index.js';
7
+ import cline from './cline/index.js';
8
+ import codex from './codex/index.js';
9
+ import continueDev from './continue/index.js';
10
+ import cowork from './cowork/index.js';
11
+ import crush from './crush/index.js';
12
+ import cursor from './cursor/index.js';
13
+ import devin from './devin/index.js';
14
+ import geminiCli from './gemini-cli/index.js';
15
+ import githubCopilot from './github-copilot/index.js';
16
+ import goose from './goose/index.js';
17
+ import grok from './grok/index.js';
18
+ import junie from './junie/index.js';
19
+ import kilo from './kilo/index.js';
20
+ import kiro from './kiro/index.js';
21
+ import opencode from './opencode/index.js';
22
+ import openhands from './openhands/index.js';
23
+ import pi from './pi/index.js';
24
+ import qwenCode from './qwen-code/index.js';
25
+ import replit from './replit/index.js';
26
+ import roo from './roo/index.js';
27
+
28
+ export const agents = [
29
+ aider,
30
+ amp,
31
+ antigravity,
32
+ augment,
33
+ claudeCode,
34
+ cline,
35
+ codex,
36
+ continueDev,
37
+ cowork,
38
+ crush,
39
+ cursor,
40
+ devin,
41
+ geminiCli,
42
+ githubCopilot,
43
+ goose,
44
+ grok,
45
+ junie,
46
+ kilo,
47
+ kiro,
48
+ opencode,
49
+ openhands,
50
+ pi,
51
+ qwenCode,
52
+ replit,
53
+ roo,
54
+ ];
@@ -0,0 +1,14 @@
1
+ // junie — evidence, tiers, and probe TODOs: .kanbento/harnesses/junie.md
2
+ export default {
3
+ name: 'junie',
4
+ vendor: 'jetbrains',
5
+ procNames: ['junie'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ const raw = {};
9
+ if (env.JUNIE_DATA) raw.JUNIE_DATA = env.JUNIE_DATA;
10
+ if (env.JUNIE_SHIM_PATH) raw.JUNIE_SHIM_PATH = env.JUNIE_SHIM_PATH;
11
+ if (Object.keys(raw).length === 0) return null;
12
+ return { confidence: 'medium', raw };
13
+ },
14
+ };
@@ -0,0 +1,17 @@
1
+ // kilo — evidence, tiers, and probe TODOs: .kanbento/harnesses/kilo.md
2
+ // opencode fork: both detectors fire by design (claims[] represents it; kilo wins by order).
3
+ export default {
4
+ name: 'kilo',
5
+ vendor: 'kilo-org',
6
+ procNames: ['kilo'],
7
+ pidEnvVar: 'KILO_PID',
8
+ detect(env) {
9
+ const raw = {};
10
+ for (const key of ['KILO', 'KILO_CLIENT', 'KILO_PLATFORM', 'KILOCODE_FEATURE', 'KILOCODE_VERSION', 'KILO_APP_VERSION', 'KILO_PID']) {
11
+ if (env[key] !== undefined) raw[key] = env[key];
12
+ }
13
+ if (env.KILO !== '1' && !env.KILO_CLIENT && !env.KILOCODE_FEATURE) return null;
14
+ const version = env.KILOCODE_VERSION || env.KILO_APP_VERSION || null;
15
+ return { confidence: 'high', version, raw };
16
+ },
17
+ };
@@ -0,0 +1,12 @@
1
+ // kiro — evidence, tiers, and probe TODOs: .kanbento/harnesses/kiro.md
2
+ // TERM_PROGRAM is ambience, not identity (their TTY gate is impossible env-only) — low.
3
+ export default {
4
+ name: 'kiro',
5
+ vendor: 'aws',
6
+ procNames: ['kiro'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (!env.TERM_PROGRAM || !env.TERM_PROGRAM.includes('kiro')) return null;
10
+ return { confidence: 'low', raw: { TERM_PROGRAM: env.TERM_PROGRAM } };
11
+ },
12
+ };
@@ -0,0 +1,16 @@
1
+ // opencode — evidence, tiers, and probe TODOs: .kanbento/harnesses/opencode.md
2
+ export default {
3
+ name: 'opencode',
4
+ vendor: 'anomaly',
5
+ procNames: ['opencode'],
6
+ pidEnvVar: 'OPENCODE_PID',
7
+ detect(env) {
8
+ // OPENCODE_CLIENT is the vercel-rules-tier sibling (detect-agent@1.0.1)
9
+ if (env.OPENCODE !== '1' && !env.OPENCODE_CLIENT) return null;
10
+ const raw = {};
11
+ for (const key of ['OPENCODE', 'OPENCODE_CLIENT', 'OPENCODE_PID']) {
12
+ if (env[key] !== undefined) raw[key] = env[key];
13
+ }
14
+ return { confidence: 'high', raw };
15
+ },
16
+ };
@@ -0,0 +1,17 @@
1
+ // openhands — evidence, tiers, and probe TODOs: .kanbento/harnesses/openhands.md
2
+ // Sandbox-furniture fingerprint, not a deliberate marker — medium.
3
+ export default {
4
+ name: 'openhands',
5
+ vendor: 'all-hands',
6
+ procNames: [],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ for (const key of ['OH_CONVERSATIONS_PATH', 'OH_BASH_EVENTS_DIR', 'OPENVSCODE_SERVER_ROOT']) {
11
+ if (env[key]) raw[key] = env[key];
12
+ }
13
+ if (env.PS1?.includes('###PS1JSON###')) raw.PS1 = '<contains ###PS1JSON### marker>';
14
+ if (Object.keys(raw).length === 0) return null;
15
+ return { confidence: 'medium', raw };
16
+ },
17
+ };
@@ -0,0 +1,12 @@
1
+ // pi — evidence, tiers, and probe TODOs: .kanbento/harnesses/pi.md
2
+ // PATH fingerprint — weak-signal class, low.
3
+ export default {
4
+ name: 'pi',
5
+ vendor: null,
6
+ procNames: ['pi'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (!env.PATH || !/\.pi[\\/]agent/.test(env.PATH)) return null;
10
+ return { confidence: 'low', raw: { PATH: '<contains .pi/agent>' } };
11
+ },
12
+ };
@@ -0,0 +1,15 @@
1
+ // qwen-code — evidence, tiers, and probe TODOs: .kanbento/harnesses/qwen-code.md
2
+ export default {
3
+ name: 'qwen-code',
4
+ vendor: 'alibaba',
5
+ procNames: ['qwen'],
6
+ pidEnvVar: null,
7
+ detect(env) {
8
+ if (env.QWEN_CODE !== '1') return null;
9
+ const raw = { QWEN_CODE: env.QWEN_CODE };
10
+ for (const key of ['QWEN_CODE_SESSION_ID', 'QWEN_CODE_AGENT_ID', 'QWEN_CODE_PROMPT_ID']) {
11
+ if (env[key]) raw[key] = env[key];
12
+ }
13
+ return { confidence: 'high', raw };
14
+ },
15
+ };
@@ -0,0 +1,12 @@
1
+ // replit — evidence, tiers, and probe TODOs: .kanbento/harnesses/replit.md
2
+ // REPL_ID marks the Replit platform, not agent operation — medium.
3
+ export default {
4
+ name: 'replit',
5
+ vendor: 'replit',
6
+ procNames: [],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ if (!env.REPL_ID) return null;
10
+ return { confidence: 'medium', raw: { REPL_ID: env.REPL_ID } };
11
+ },
12
+ };
@@ -0,0 +1,16 @@
1
+ // roo — evidence, tiers, and probe TODOs: .kanbento/harnesses/roo.md
2
+ // The execa inline provider sets neither var — absence proves nothing.
3
+ export default {
4
+ name: 'roo',
5
+ vendor: 'roo-code',
6
+ procNames: ['roo'],
7
+ pidEnvVar: null,
8
+ detect(env) {
9
+ const raw = {};
10
+ if (env.ROO_ACTIVE === 'true') raw.ROO_ACTIVE = env.ROO_ACTIVE;
11
+ if (env.ROO_CLI_RUNTIME === '1') raw.ROO_CLI_RUNTIME = env.ROO_CLI_RUNTIME;
12
+ if (Object.keys(raw).length === 0) return null;
13
+ const mode = raw.ROO_CLI_RUNTIME ? 'agent' : null;
14
+ return { confidence: 'high', mode, raw };
15
+ },
16
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "agent": "aider",
3
+ "probes": [
4
+ {
5
+ "date": "2026-07-12",
6
+ "method": "source-derived: aider/llm.py:9-13 \u2014 OpenRouter attribution vars set at import, inherited by every /run child; accidental fingerprint, not identity by intent",
7
+ "tier": "source",
8
+ "env": {
9
+ "OR_SITE_URL": "https://aider.chat",
10
+ "OR_APP_NAME": "Aider"
11
+ },
12
+ "expect": {
13
+ "isAgent": true,
14
+ "primary": "aider",
15
+ "claimNames": [
16
+ "aider"
17
+ ]
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "agent": "amp",
3
+ "probes": [
4
+ {
5
+ "date": "2026-07-12",
6
+ "method": "community-derived: agents.md adoption table (issues/136) lists amp as setting AGENT=amp — not in the official manual, unverified in source; identity arrives via the generic AGENT rung, no vendor detector",
7
+ "tier": "community",
8
+ "env": {
9
+ "AGENT": "amp"
10
+ },
11
+ "expect": { "isAgent": true, "primary": "amp", "claimNames": ["amp"] }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "agent": "antigravity",
3
+ "probes": [
4
+ {
5
+ "date": "2026-07-12",
6
+ "method": "vercel-rules-derived: detect-agent@1.0.1 agents.json (adopted, not independently verified)",
7
+ "tier": "vercel-rules",
8
+ "env": {
9
+ "ANTIGRAVITY_AGENT": "1"
10
+ },
11
+ "expect": {
12
+ "isAgent": true,
13
+ "primary": "antigravity",
14
+ "claimNames": [
15
+ "antigravity"
16
+ ]
17
+ }
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "agent": "augment",
3
+ "probes": [
4
+ {
5
+ "date": "2026-07-12",
6
+ "method": "vercel-rules-derived: detect-agent@1.0.1 agents.json (adopted, not independently verified)",
7
+ "tier": "vercel-rules",
8
+ "env": {
9
+ "AUGMENT_AGENT": "1"
10
+ },
11
+ "expect": {
12
+ "isAgent": true,
13
+ "primary": "augment",
14
+ "claimNames": [
15
+ "augment"
16
+ ]
17
+ }
18
+ }
19
+ ]
20
+ }