agenthint 0.4.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forjd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: agenthint
3
+ Version: 0.4.0
4
+ Summary: Detect AI agent runtimes and adapt CLI output.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/forjd/agenthint
7
+ Project-URL: Repository, https://github.com/forjd/agenthint
8
+ Project-URL: Issues, https://github.com/forjd/agenthint/issues
9
+ Keywords: ai,agent,cli,detection
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Software Development
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # agenthint
22
+
23
+ [![CI](https://github.com/forjd/agenthint/actions/workflows/ci.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/ci.yml)
24
+ [![Release](https://github.com/forjd/agenthint/actions/workflows/release.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/release.yml)
25
+ [![npm](https://img.shields.io/npm/v/agenthint?logo=npm&color=cb3837)](https://www.npmjs.com/package/agenthint)
26
+ [![crates.io](https://img.shields.io/crates/v/agenthint?logo=rust&color=dea584)](https://crates.io/crates/agenthint)
27
+ [![License](https://img.shields.io/github/license/forjd/agenthint)](LICENSE)
28
+ [![GitHub Repo stars](https://img.shields.io/github/stars/forjd/agenthint?style=social)](https://github.com/forjd/agenthint)
29
+
30
+ Detect AI agent runtimes and adapt CLI output.
31
+
32
+ `agenthint` is a small runtime detection spec, CLI, and library for developer tools that want to know when they are probably being run by an AI agent such as Codex, Claude Code, Cursor, Gemini CLI, or Aider.
33
+
34
+ It is built for ergonomics, not security. Use it to choose better output defaults for agents; do not use it as a trust boundary.
35
+
36
+ ## Why
37
+
38
+ AI agents benefit from different CLI defaults than humans:
39
+
40
+ - structured output instead of decorative output
41
+ - no spinners, pagers, prompts, or browser launches
42
+ - stable section markers and clear exit-code meanings
43
+ - absolute paths and line-oriented diagnostics
44
+ - concise logs that preserve useful debugging context
45
+
46
+ `agenthint` gives CLIs and libraries a shared way to make that decision.
47
+
48
+ ## Quick Start
49
+
50
+ ```sh
51
+ npm install -g agenthint
52
+ # or
53
+ cargo install agenthint
54
+ ```
55
+
56
+ ```sh
57
+ if agenthint; then
58
+ my-tool --json --no-progress
59
+ else
60
+ my-tool
61
+ fi
62
+ ```
63
+
64
+ Use it inside another CLI or script to choose agent-friendly output:
65
+
66
+ ```sh
67
+ if agenthint >/dev/null; then
68
+ exec my-cli --json --no-progress --no-pager "$@"
69
+ else
70
+ exec my-cli "$@"
71
+ fi
72
+ ```
73
+
74
+ For agents and wrappers, the preferred explicit convention is `AI_AGENT`:
75
+
76
+ ```sh
77
+ AI_AGENT=codex my-tool
78
+ AI_AGENT=claude-code my-tool
79
+ AI_AGENT=my-custom-agent my-tool
80
+ ```
81
+
82
+ ## CLI
83
+
84
+ ```sh
85
+ agenthint # exit 0 if an agent is likely detected, otherwise 1
86
+ agenthint --json # print the structured detection result
87
+ agenthint --explain # print a short explanation
88
+ agenthint doctor # print detection details and setup advice
89
+ agenthint doctor --json
90
+ # print detection details and setup advice as JSON
91
+ agenthint init codex # print the recommended AI_AGENT value
92
+ ```
93
+
94
+ Example JSON output:
95
+
96
+ ```json
97
+ {
98
+ "isAgent": true,
99
+ "agent": "codex",
100
+ "confidence": 0.92,
101
+ "signals": ["env:CODEX_CI", "env:CODEX_THREAD_ID"]
102
+ }
103
+ ```
104
+
105
+ ## Install
106
+
107
+ Install from npm:
108
+
109
+ ```sh
110
+ npm install -g agenthint
111
+ agenthint --json
112
+ ```
113
+
114
+ Install from crates.io:
115
+
116
+ ```sh
117
+ cargo install agenthint
118
+ agenthint --json
119
+ ```
120
+
121
+ Install from PyPI:
122
+
123
+ ```sh
124
+ python3 -m pip install agenthint
125
+ agenthint --json
126
+ ```
127
+
128
+ Install the latest native binary from GitHub Releases:
129
+
130
+ ```sh
131
+ curl -fsSL https://raw.githubusercontent.com/forjd/agenthint/main/install.sh | sh
132
+ ```
133
+
134
+ Override the install directory or version:
135
+
136
+ ```sh
137
+ AGENTHINT_INSTALL_DIR=/usr/local/bin sh install.sh
138
+ AGENTHINT_VERSION=agenthint-vX.Y.Z sh install.sh
139
+ ```
140
+
141
+ Native binaries are built by GitHub Actions for release assets. The installer verifies `SHA256SUMS` when the selected release provides it.
142
+
143
+ ## TypeScript API
144
+
145
+ ```ts
146
+ import { detectAgent } from "agenthint";
147
+
148
+ const result = detectAgent();
149
+
150
+ if (result.isAgent) {
151
+ // Prefer structured, quiet, non-interactive output.
152
+ }
153
+ ```
154
+
155
+ ## Rust API
156
+
157
+ The repository also contains a Rust implementation under `crates/agenthint`.
158
+
159
+ ```rust
160
+ use agenthint::detect_agent;
161
+
162
+ let result = detect_agent();
163
+
164
+ if result.is_agent {
165
+ // Prefer structured, quiet, non-interactive output.
166
+ }
167
+ ```
168
+
169
+ Run the Rust CLI locally:
170
+
171
+ ```sh
172
+ cargo run -q -p agenthint -- --json
173
+ ```
174
+
175
+ ## Python API
176
+
177
+ ```python
178
+ from agenthint import detect_agent
179
+
180
+ result = detect_agent()
181
+
182
+ if result.is_agent:
183
+ # Prefer structured, quiet, non-interactive output.
184
+ pass
185
+ ```
186
+
187
+ ## Detection Model
188
+
189
+ The result includes:
190
+
191
+ - `isAgent`: whether an agent runtime is likely detected
192
+ - `agent`: known or custom agent name
193
+ - `confidence`: a number from `0` to `1`
194
+ - `signals`: diagnostic signal names, never secret values
195
+
196
+ Detection priority:
197
+
198
+ 1. `AGENTHINT_DISABLE`
199
+ 2. `AGENTHINT_FORCE`
200
+ 3. explicit `AI_AGENT`
201
+ 4. known environment signals
202
+ 5. documented filesystem signals
203
+ 6. low-confidence parent process signals
204
+ 7. low-confidence stdio hints
205
+
206
+ ## Supported Agents
207
+
208
+ Current known agent names include:
209
+
210
+ - Codex
211
+ - Claude Code
212
+ - Cowork
213
+ - Cursor
214
+ - Gemini CLI
215
+ - Aider
216
+ - Augment CLI
217
+ - AMP
218
+ - OpenCode
219
+ - OpenClaw
220
+ - GitHub Copilot
221
+ - Replit
222
+ - Devin
223
+ - Google Antigravity
224
+ - Pi
225
+ - Kiro CLI
226
+ - Windsurf
227
+ - Cline
228
+ - Roo Code
229
+ - Kilo Code
230
+ - Mistral Vibe
231
+ - v0
232
+
233
+ Custom agents are supported through any non-empty `AI_AGENT` value.
234
+
235
+ See [docs/agents.md](docs/agents.md) for recommended `AI_AGENT` values.
236
+
237
+ See [docs/integrations.md](docs/integrations.md) for Bash, Zsh, Fish, Node.js, Rust, and Python integration snippets.
238
+
239
+ See [docs/signals.md](docs/signals.md) for the signal registry and confidence levels.
240
+
241
+ ## Principles
242
+
243
+ - Detection is advisory and can be spoofed.
244
+ - Prefer `AI_AGENT` when an agent can set an explicit hint.
245
+ - Prefer explicit environment signals over brittle heuristics.
246
+ - Return confidence, not false certainty.
247
+ - Print signal names, not environment variable values.
248
+ - Keep output quiet and machine-readable when requested.
249
+ - Make the convention useful across languages and toolchains.
250
+
251
+ ## Packages
252
+
253
+ Current:
254
+
255
+ - `agenthint` JavaScript/TypeScript package
256
+ - `agenthint` Rust crate and CLI implementation
257
+ - `agenthint` Python package
258
+
259
+ Planned:
260
+
261
+ - standalone native binary releases
262
+
263
+ The packages use the unscoped `agenthint` name across npm, crates.io, and PyPI. If the npm name becomes unavailable before first publish, the fallback package name is `@forjd/agenthint`.
264
+
265
+ ## CI and Releases
266
+
267
+ GitHub Actions runs formatting, linting, TypeScript tests, Rust tests, Python tests, npm package checks, Python package build checks, and `cargo publish --dry-run`.
268
+
269
+ Releases use release-please with Conventional Commits. npm and PyPI publishing use trusted publishing via GitHub Actions OIDC, so no long-lived package tokens are required. Before the first publish, configure trusted publishers for `forjd/agenthint` and `.github/workflows/release.yml`.
270
+
271
+ See [docs/releases.md](docs/releases.md) for release details.
272
+
273
+ ## Development
274
+
275
+ Use [mise](https://mise.jdx.dev/) for local toolchain versions:
276
+
277
+ ```sh
278
+ mise install
279
+ ```
280
+
281
+ Install dependencies and run checks:
282
+
283
+ ```sh
284
+ npm install
285
+ npm run check
286
+ ```
287
+
288
+ Useful commands:
289
+
290
+ ```sh
291
+ npm run format
292
+ npm run lint
293
+ npm test
294
+ npm run python:build
295
+ npm run python:test
296
+ npm run generate:rules
297
+ cargo test --workspace
298
+ cargo clippy --workspace --all-targets -- -D warnings
299
+ ```
300
+
301
+ ## Security
302
+
303
+ `agenthint` is not an authentication, authorization, sandboxing, or policy tool. Environment variables, parent process names, and filesystem markers can be spoofed. Treat all results as UX hints only.
304
+
305
+ ## License
306
+
307
+ MIT © Forjd
@@ -0,0 +1,287 @@
1
+ # agenthint
2
+
3
+ [![CI](https://github.com/forjd/agenthint/actions/workflows/ci.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/ci.yml)
4
+ [![Release](https://github.com/forjd/agenthint/actions/workflows/release.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/release.yml)
5
+ [![npm](https://img.shields.io/npm/v/agenthint?logo=npm&color=cb3837)](https://www.npmjs.com/package/agenthint)
6
+ [![crates.io](https://img.shields.io/crates/v/agenthint?logo=rust&color=dea584)](https://crates.io/crates/agenthint)
7
+ [![License](https://img.shields.io/github/license/forjd/agenthint)](LICENSE)
8
+ [![GitHub Repo stars](https://img.shields.io/github/stars/forjd/agenthint?style=social)](https://github.com/forjd/agenthint)
9
+
10
+ Detect AI agent runtimes and adapt CLI output.
11
+
12
+ `agenthint` is a small runtime detection spec, CLI, and library for developer tools that want to know when they are probably being run by an AI agent such as Codex, Claude Code, Cursor, Gemini CLI, or Aider.
13
+
14
+ It is built for ergonomics, not security. Use it to choose better output defaults for agents; do not use it as a trust boundary.
15
+
16
+ ## Why
17
+
18
+ AI agents benefit from different CLI defaults than humans:
19
+
20
+ - structured output instead of decorative output
21
+ - no spinners, pagers, prompts, or browser launches
22
+ - stable section markers and clear exit-code meanings
23
+ - absolute paths and line-oriented diagnostics
24
+ - concise logs that preserve useful debugging context
25
+
26
+ `agenthint` gives CLIs and libraries a shared way to make that decision.
27
+
28
+ ## Quick Start
29
+
30
+ ```sh
31
+ npm install -g agenthint
32
+ # or
33
+ cargo install agenthint
34
+ ```
35
+
36
+ ```sh
37
+ if agenthint; then
38
+ my-tool --json --no-progress
39
+ else
40
+ my-tool
41
+ fi
42
+ ```
43
+
44
+ Use it inside another CLI or script to choose agent-friendly output:
45
+
46
+ ```sh
47
+ if agenthint >/dev/null; then
48
+ exec my-cli --json --no-progress --no-pager "$@"
49
+ else
50
+ exec my-cli "$@"
51
+ fi
52
+ ```
53
+
54
+ For agents and wrappers, the preferred explicit convention is `AI_AGENT`:
55
+
56
+ ```sh
57
+ AI_AGENT=codex my-tool
58
+ AI_AGENT=claude-code my-tool
59
+ AI_AGENT=my-custom-agent my-tool
60
+ ```
61
+
62
+ ## CLI
63
+
64
+ ```sh
65
+ agenthint # exit 0 if an agent is likely detected, otherwise 1
66
+ agenthint --json # print the structured detection result
67
+ agenthint --explain # print a short explanation
68
+ agenthint doctor # print detection details and setup advice
69
+ agenthint doctor --json
70
+ # print detection details and setup advice as JSON
71
+ agenthint init codex # print the recommended AI_AGENT value
72
+ ```
73
+
74
+ Example JSON output:
75
+
76
+ ```json
77
+ {
78
+ "isAgent": true,
79
+ "agent": "codex",
80
+ "confidence": 0.92,
81
+ "signals": ["env:CODEX_CI", "env:CODEX_THREAD_ID"]
82
+ }
83
+ ```
84
+
85
+ ## Install
86
+
87
+ Install from npm:
88
+
89
+ ```sh
90
+ npm install -g agenthint
91
+ agenthint --json
92
+ ```
93
+
94
+ Install from crates.io:
95
+
96
+ ```sh
97
+ cargo install agenthint
98
+ agenthint --json
99
+ ```
100
+
101
+ Install from PyPI:
102
+
103
+ ```sh
104
+ python3 -m pip install agenthint
105
+ agenthint --json
106
+ ```
107
+
108
+ Install the latest native binary from GitHub Releases:
109
+
110
+ ```sh
111
+ curl -fsSL https://raw.githubusercontent.com/forjd/agenthint/main/install.sh | sh
112
+ ```
113
+
114
+ Override the install directory or version:
115
+
116
+ ```sh
117
+ AGENTHINT_INSTALL_DIR=/usr/local/bin sh install.sh
118
+ AGENTHINT_VERSION=agenthint-vX.Y.Z sh install.sh
119
+ ```
120
+
121
+ Native binaries are built by GitHub Actions for release assets. The installer verifies `SHA256SUMS` when the selected release provides it.
122
+
123
+ ## TypeScript API
124
+
125
+ ```ts
126
+ import { detectAgent } from "agenthint";
127
+
128
+ const result = detectAgent();
129
+
130
+ if (result.isAgent) {
131
+ // Prefer structured, quiet, non-interactive output.
132
+ }
133
+ ```
134
+
135
+ ## Rust API
136
+
137
+ The repository also contains a Rust implementation under `crates/agenthint`.
138
+
139
+ ```rust
140
+ use agenthint::detect_agent;
141
+
142
+ let result = detect_agent();
143
+
144
+ if result.is_agent {
145
+ // Prefer structured, quiet, non-interactive output.
146
+ }
147
+ ```
148
+
149
+ Run the Rust CLI locally:
150
+
151
+ ```sh
152
+ cargo run -q -p agenthint -- --json
153
+ ```
154
+
155
+ ## Python API
156
+
157
+ ```python
158
+ from agenthint import detect_agent
159
+
160
+ result = detect_agent()
161
+
162
+ if result.is_agent:
163
+ # Prefer structured, quiet, non-interactive output.
164
+ pass
165
+ ```
166
+
167
+ ## Detection Model
168
+
169
+ The result includes:
170
+
171
+ - `isAgent`: whether an agent runtime is likely detected
172
+ - `agent`: known or custom agent name
173
+ - `confidence`: a number from `0` to `1`
174
+ - `signals`: diagnostic signal names, never secret values
175
+
176
+ Detection priority:
177
+
178
+ 1. `AGENTHINT_DISABLE`
179
+ 2. `AGENTHINT_FORCE`
180
+ 3. explicit `AI_AGENT`
181
+ 4. known environment signals
182
+ 5. documented filesystem signals
183
+ 6. low-confidence parent process signals
184
+ 7. low-confidence stdio hints
185
+
186
+ ## Supported Agents
187
+
188
+ Current known agent names include:
189
+
190
+ - Codex
191
+ - Claude Code
192
+ - Cowork
193
+ - Cursor
194
+ - Gemini CLI
195
+ - Aider
196
+ - Augment CLI
197
+ - AMP
198
+ - OpenCode
199
+ - OpenClaw
200
+ - GitHub Copilot
201
+ - Replit
202
+ - Devin
203
+ - Google Antigravity
204
+ - Pi
205
+ - Kiro CLI
206
+ - Windsurf
207
+ - Cline
208
+ - Roo Code
209
+ - Kilo Code
210
+ - Mistral Vibe
211
+ - v0
212
+
213
+ Custom agents are supported through any non-empty `AI_AGENT` value.
214
+
215
+ See [docs/agents.md](docs/agents.md) for recommended `AI_AGENT` values.
216
+
217
+ See [docs/integrations.md](docs/integrations.md) for Bash, Zsh, Fish, Node.js, Rust, and Python integration snippets.
218
+
219
+ See [docs/signals.md](docs/signals.md) for the signal registry and confidence levels.
220
+
221
+ ## Principles
222
+
223
+ - Detection is advisory and can be spoofed.
224
+ - Prefer `AI_AGENT` when an agent can set an explicit hint.
225
+ - Prefer explicit environment signals over brittle heuristics.
226
+ - Return confidence, not false certainty.
227
+ - Print signal names, not environment variable values.
228
+ - Keep output quiet and machine-readable when requested.
229
+ - Make the convention useful across languages and toolchains.
230
+
231
+ ## Packages
232
+
233
+ Current:
234
+
235
+ - `agenthint` JavaScript/TypeScript package
236
+ - `agenthint` Rust crate and CLI implementation
237
+ - `agenthint` Python package
238
+
239
+ Planned:
240
+
241
+ - standalone native binary releases
242
+
243
+ The packages use the unscoped `agenthint` name across npm, crates.io, and PyPI. If the npm name becomes unavailable before first publish, the fallback package name is `@forjd/agenthint`.
244
+
245
+ ## CI and Releases
246
+
247
+ GitHub Actions runs formatting, linting, TypeScript tests, Rust tests, Python tests, npm package checks, Python package build checks, and `cargo publish --dry-run`.
248
+
249
+ Releases use release-please with Conventional Commits. npm and PyPI publishing use trusted publishing via GitHub Actions OIDC, so no long-lived package tokens are required. Before the first publish, configure trusted publishers for `forjd/agenthint` and `.github/workflows/release.yml`.
250
+
251
+ See [docs/releases.md](docs/releases.md) for release details.
252
+
253
+ ## Development
254
+
255
+ Use [mise](https://mise.jdx.dev/) for local toolchain versions:
256
+
257
+ ```sh
258
+ mise install
259
+ ```
260
+
261
+ Install dependencies and run checks:
262
+
263
+ ```sh
264
+ npm install
265
+ npm run check
266
+ ```
267
+
268
+ Useful commands:
269
+
270
+ ```sh
271
+ npm run format
272
+ npm run lint
273
+ npm test
274
+ npm run python:build
275
+ npm run python:test
276
+ npm run generate:rules
277
+ cargo test --workspace
278
+ cargo clippy --workspace --all-targets -- -D warnings
279
+ ```
280
+
281
+ ## Security
282
+
283
+ `agenthint` is not an authentication, authorization, sandboxing, or policy tool. Environment variables, parent process names, and filesystem markers can be spoofed. Treat all results as UX hints only.
284
+
285
+ ## License
286
+
287
+ MIT © Forjd
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agenthint"
7
+ version = "0.4.0"
8
+ description = "Detect AI agent runtimes and adapt CLI output."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ keywords = ["ai", "agent", "cli", "detection"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Topic :: Software Development",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/forjd/agenthint"
24
+ Repository = "https://github.com/forjd/agenthint"
25
+ Issues = "https://github.com/forjd/agenthint/issues"
26
+
27
+ [project.scripts]
28
+ agenthint = "agenthint.cli:main"
29
+
30
+ [tool.setuptools]
31
+ package-dir = {"" = "python"}
32
+
33
+ [tool.setuptools.package-data]
34
+ agenthint = ["detection-rules.json"]
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ from dataclasses import dataclass
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+ from typing import Callable, Mapping
10
+
11
+ AgentName = str
12
+
13
+ TRUE_VALUES = {"1", "true", "yes", "on"}
14
+ PARENT_CONFIDENCE = 0.55
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class AgentHintResult:
19
+ is_agent: bool
20
+ agent: AgentName | None
21
+ confidence: float
22
+ signals: list[str]
23
+
24
+ def to_dict(self) -> dict[str, object]:
25
+ return {
26
+ "isAgent": self.is_agent,
27
+ "agent": self.agent,
28
+ "confidence": self.confidence,
29
+ "signals": self.signals,
30
+ }
31
+
32
+
33
+ def detect_agent(
34
+ *,
35
+ env: Mapping[str, str] | None = None,
36
+ stdout_is_tty: bool | None = None,
37
+ stdin_is_tty: bool | None = None,
38
+ check_filesystem: bool = True,
39
+ check_parent_process: bool = True,
40
+ parent_process_name: str | None = None,
41
+ file_exists: Callable[[str], bool] = os.path.exists,
42
+ ) -> AgentHintResult:
43
+ env = os.environ if env is None else env
44
+
45
+ if _is_truthy(env.get("AGENTHINT_DISABLE")):
46
+ return AgentHintResult(False, None, 1, ["env:AGENTHINT_DISABLE"])
47
+
48
+ if _is_truthy(env.get("AGENTHINT_FORCE")):
49
+ return AgentHintResult(
50
+ True,
51
+ _normalize_agent_name(env.get("AGENTHINT_AGENT")) or "unknown",
52
+ 1,
53
+ ["env:AGENTHINT_FORCE"],
54
+ )
55
+
56
+ ai_agent = _from_ai_agent(env)
57
+ if ai_agent is not None:
58
+ return ai_agent
59
+
60
+ matches = _detection_matches(env)
61
+ if matches:
62
+ best = matches[0]
63
+ return AgentHintResult(True, best["agent"], best["confidence"], [signal for match in matches for signal in match["signals"]])
64
+
65
+ if check_filesystem and file_exists("/opt/.devin"):
66
+ return AgentHintResult(True, "devin", 0.9, ["file:/opt/.devin"])
67
+
68
+ parent_result = _from_parent_process(check_parent_process, parent_process_name)
69
+ if parent_result is not None:
70
+ return parent_result
71
+
72
+ tty_signals: list[str] = []
73
+ if stdout_is_tty is False:
74
+ tty_signals.append("stdio:stdout-not-tty")
75
+ if stdin_is_tty is False:
76
+ tty_signals.append("stdio:stdin-not-tty")
77
+ if tty_signals:
78
+ return AgentHintResult(False, None, 0.2, tty_signals)
79
+
80
+ return AgentHintResult(False, None, 0, [])
81
+
82
+
83
+ def format_explanation(result: AgentHintResult) -> str:
84
+ status = "agent runtime likely detected" if result.is_agent else "agent runtime not detected"
85
+ agent = f"\nagent: {result.agent}" if result.agent else ""
86
+ signals = ", ".join(result.signals) if result.signals else "none"
87
+ return f"{status}{agent}\nconfidence: {result.confidence:.2f}\nsignals: {signals}"
88
+
89
+
90
+ def format_json(result: AgentHintResult) -> str:
91
+ return json.dumps(result.to_dict(), indent=2)
92
+
93
+
94
+ def _from_ai_agent(env: Mapping[str, str]) -> AgentHintResult | None:
95
+ value = env.get("AI_AGENT")
96
+ if value is None or not value.strip():
97
+ return None
98
+
99
+ return AgentHintResult(True, _normalize_agent_name(value) or value.strip(), 0.98, ["env:AI_AGENT"])
100
+
101
+
102
+ def _detection_matches(env: Mapping[str, str]) -> list[dict[str, object]]:
103
+ rules = _rules()
104
+ matches: list[dict[str, object]] = []
105
+
106
+ for rule in rules["environmentRules"]:
107
+ signals = _present(env, rule["names"])
108
+ if signals:
109
+ matches.append({"agent": rule["agent"], "confidence": rule["confidence"], "signals": signals})
110
+
111
+ if rule["agent"] == "opencode":
112
+ claude_signals = _present(env, ["CLAUDECODE", "CLAUDE_CODE", "CLAUDECODE_CWD"])
113
+ if claude_signals:
114
+ agent = "cowork" if _present(env, ["CLAUDE_CODE_IS_COWORK"]) else "claude-code"
115
+ matches.append({"agent": agent, "confidence": 0.9, "signals": claude_signals})
116
+
117
+ for rule in rules["prefixRules"]:
118
+ signals = _prefix_present(env, rule["prefix"])
119
+ if signals:
120
+ matches.append({"agent": rule["agent"], "confidence": rule["confidence"], "signals": signals})
121
+
122
+ return matches
123
+
124
+
125
+ def _from_parent_process(check_parent_process: bool, parent_process_name: str | None) -> AgentHintResult | None:
126
+ if not check_parent_process:
127
+ return None
128
+
129
+ name = _normalize_process_name(parent_process_name or _parent_process_name())
130
+ if name is None:
131
+ return None
132
+
133
+ for rule in _rules()["parentProcessRules"]:
134
+ if name in rule["names"]:
135
+ return AgentHintResult(True, rule["agent"], PARENT_CONFIDENCE, [f"process:parent:{name}"])
136
+
137
+ return None
138
+
139
+
140
+ def _parent_process_name() -> str | None:
141
+ ppid = os.getppid()
142
+ if ppid <= 0:
143
+ return None
144
+
145
+ proc_path = Path(f"/proc/{ppid}/comm")
146
+ try:
147
+ value = proc_path.read_text(encoding="utf8").strip()
148
+ if value:
149
+ return value
150
+ except OSError:
151
+ pass
152
+
153
+ try:
154
+ return subprocess.run(
155
+ ["ps", "-o", "comm=", "-p", str(ppid)],
156
+ check=True,
157
+ capture_output=True,
158
+ text=True,
159
+ ).stdout.strip()
160
+ except (OSError, subprocess.CalledProcessError):
161
+ return None
162
+
163
+
164
+ def _present(env: Mapping[str, str], names: list[str]) -> list[str]:
165
+ return [f"env:{name}" for name in names if env.get(name)]
166
+
167
+
168
+ def _prefix_present(env: Mapping[str, str], prefix: str) -> list[str]:
169
+ return [f"env:{name}" for name, value in env.items() if name.startswith(prefix) and value]
170
+
171
+
172
+ def _is_truthy(value: str | None) -> bool:
173
+ return value is not None and value.lower() in TRUE_VALUES
174
+
175
+
176
+ def _normalize_agent_name(value: str | None) -> str | None:
177
+ if value is None or not value.strip():
178
+ return None
179
+
180
+ normalized = value.strip()
181
+ if normalized in {"github-copilot", "github-copilot-cli"}:
182
+ return "copilot"
183
+ if normalized.startswith("claude-code"):
184
+ return "claude-code"
185
+ if normalized in {"roo", "roo-code"}:
186
+ return "roo-code"
187
+ if normalized in {"kilo-code", "kilocode"}:
188
+ return "kilocode"
189
+ if normalized in {"mistral-vibe", "vibe"}:
190
+ return "mistral-vibe"
191
+ return normalized
192
+
193
+
194
+ def _normalize_process_name(value: str | None) -> str | None:
195
+ if value is None or not value.strip():
196
+ return None
197
+ return Path(value.strip()).name.removesuffix(".exe").lower()
198
+
199
+
200
+ def _rules() -> dict[str, object]:
201
+ return json.loads(files("agenthint").joinpath("detection-rules.json").read_text(encoding="utf8"))
202
+
203
+
204
+ __all__ = ["AgentHintResult", "detect_agent", "format_explanation", "format_json"]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from agenthint import detect_agent, format_explanation, format_json
6
+
7
+
8
+ def main() -> None:
9
+ args = sys.argv[1:]
10
+
11
+ if "-h" in args or "--help" in args:
12
+ print_help()
13
+ raise SystemExit(0)
14
+
15
+ result = detect_agent()
16
+
17
+ if "--json" in args:
18
+ print(format_json(result))
19
+ elif "--explain" in args:
20
+ print(format_explanation(result))
21
+
22
+ raise SystemExit(0 if result.is_agent else 1)
23
+
24
+
25
+ def print_help() -> None:
26
+ print(
27
+ """agenthint
28
+
29
+ Detect whether the current process is probably running under an AI agent.
30
+
31
+ Usage:
32
+ agenthint Exit 0 if an agent is likely detected, otherwise 1
33
+ agenthint --json Print the structured detection result
34
+ agenthint --explain Print a short human-readable explanation
35
+ agenthint --help Show this help"""
36
+ )
37
+
38
+
39
+ if __name__ == "__main__":
40
+ main()
@@ -0,0 +1,151 @@
1
+ {
2
+ "knownAgents": [
3
+ "codex",
4
+ "claude-code",
5
+ "cowork",
6
+ "aider",
7
+ "cursor",
8
+ "gemini",
9
+ "augment-cli",
10
+ "amp",
11
+ "opencode",
12
+ "copilot",
13
+ "replit",
14
+ "devin",
15
+ "antigravity",
16
+ "pi",
17
+ "kiro-cli",
18
+ "windsurf",
19
+ "cline",
20
+ "roo-code",
21
+ "kilocode",
22
+ "openclaw",
23
+ "mistral-vibe",
24
+ "v0",
25
+ "unknown"
26
+ ],
27
+ "environmentRules": [
28
+ {
29
+ "agent": "cursor",
30
+ "confidence": 0.92,
31
+ "names": ["CURSOR_AGENT"]
32
+ },
33
+ {
34
+ "agent": "gemini",
35
+ "confidence": 0.92,
36
+ "names": ["GEMINI_CLI"]
37
+ },
38
+ {
39
+ "agent": "codex",
40
+ "confidence": 0.92,
41
+ "names": ["CODEX_SANDBOX", "CODEX_CI", "CODEX_THREAD_ID", "CODEX_HOME", "CODEX_USER_AGENT"]
42
+ },
43
+ {
44
+ "agent": "augment-cli",
45
+ "confidence": 0.9,
46
+ "names": ["AUGMENT_AGENT"]
47
+ },
48
+ {
49
+ "agent": "amp",
50
+ "confidence": 0.9,
51
+ "names": ["AMP_CURRENT_THREAD_ID"]
52
+ },
53
+ {
54
+ "agent": "opencode",
55
+ "confidence": 0.9,
56
+ "names": ["OPENCODE_CLIENT", "OPENCODE"]
57
+ },
58
+ {
59
+ "agent": "copilot",
60
+ "confidence": 0.88,
61
+ "names": ["COPILOT_MODEL", "COPILOT_ALLOW_ALL", "COPILOT_GITHUB_TOKEN", "COPILOT_CLI"]
62
+ },
63
+ {
64
+ "agent": "replit",
65
+ "confidence": 0.65,
66
+ "names": ["REPL_ID"]
67
+ },
68
+ {
69
+ "agent": "antigravity",
70
+ "confidence": 0.9,
71
+ "names": ["ANTIGRAVITY_AGENT"]
72
+ },
73
+ {
74
+ "agent": "pi",
75
+ "confidence": 0.9,
76
+ "names": ["PI_CODING_AGENT"]
77
+ },
78
+ {
79
+ "agent": "kiro-cli",
80
+ "confidence": 0.9,
81
+ "names": ["KIRO_AGENT_PATH"]
82
+ },
83
+ {
84
+ "agent": "windsurf",
85
+ "confidence": 0.82,
86
+ "names": ["WINDSURF_AGENT"]
87
+ },
88
+ {
89
+ "agent": "cline",
90
+ "confidence": 0.82,
91
+ "names": ["CLINE_AGENT"]
92
+ },
93
+ {
94
+ "agent": "roo-code",
95
+ "confidence": 0.82,
96
+ "names": ["ROO_CODE_AGENT", "ROO_CODE"]
97
+ },
98
+ {
99
+ "agent": "kilocode",
100
+ "confidence": 0.82,
101
+ "names": ["KILOCODE_AGENT"]
102
+ },
103
+ {
104
+ "agent": "openclaw",
105
+ "confidence": 0.82,
106
+ "names": ["OPENCLAW_AGENT"]
107
+ }
108
+ ],
109
+ "prefixRules": [
110
+ {
111
+ "agent": "aider",
112
+ "confidence": 0.86,
113
+ "prefix": "AIDER_"
114
+ },
115
+ {
116
+ "agent": "cursor",
117
+ "confidence": 0.82,
118
+ "prefix": "CURSOR_"
119
+ }
120
+ ],
121
+ "parentProcessRules": [
122
+ {
123
+ "agent": "codex",
124
+ "names": ["codex"]
125
+ },
126
+ {
127
+ "agent": "claude-code",
128
+ "names": ["claude", "claude-code"]
129
+ },
130
+ {
131
+ "agent": "cursor",
132
+ "names": ["cursor-agent", "cursor"]
133
+ },
134
+ {
135
+ "agent": "gemini",
136
+ "names": ["gemini"]
137
+ },
138
+ {
139
+ "agent": "aider",
140
+ "names": ["aider"]
141
+ },
142
+ {
143
+ "agent": "opencode",
144
+ "names": ["opencode"]
145
+ },
146
+ {
147
+ "agent": "amp",
148
+ "names": ["amp"]
149
+ }
150
+ ]
151
+ }
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: agenthint
3
+ Version: 0.4.0
4
+ Summary: Detect AI agent runtimes and adapt CLI output.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/forjd/agenthint
7
+ Project-URL: Repository, https://github.com/forjd/agenthint
8
+ Project-URL: Issues, https://github.com/forjd/agenthint/issues
9
+ Keywords: ai,agent,cli,detection
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Software Development
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # agenthint
22
+
23
+ [![CI](https://github.com/forjd/agenthint/actions/workflows/ci.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/ci.yml)
24
+ [![Release](https://github.com/forjd/agenthint/actions/workflows/release.yml/badge.svg)](https://github.com/forjd/agenthint/actions/workflows/release.yml)
25
+ [![npm](https://img.shields.io/npm/v/agenthint?logo=npm&color=cb3837)](https://www.npmjs.com/package/agenthint)
26
+ [![crates.io](https://img.shields.io/crates/v/agenthint?logo=rust&color=dea584)](https://crates.io/crates/agenthint)
27
+ [![License](https://img.shields.io/github/license/forjd/agenthint)](LICENSE)
28
+ [![GitHub Repo stars](https://img.shields.io/github/stars/forjd/agenthint?style=social)](https://github.com/forjd/agenthint)
29
+
30
+ Detect AI agent runtimes and adapt CLI output.
31
+
32
+ `agenthint` is a small runtime detection spec, CLI, and library for developer tools that want to know when they are probably being run by an AI agent such as Codex, Claude Code, Cursor, Gemini CLI, or Aider.
33
+
34
+ It is built for ergonomics, not security. Use it to choose better output defaults for agents; do not use it as a trust boundary.
35
+
36
+ ## Why
37
+
38
+ AI agents benefit from different CLI defaults than humans:
39
+
40
+ - structured output instead of decorative output
41
+ - no spinners, pagers, prompts, or browser launches
42
+ - stable section markers and clear exit-code meanings
43
+ - absolute paths and line-oriented diagnostics
44
+ - concise logs that preserve useful debugging context
45
+
46
+ `agenthint` gives CLIs and libraries a shared way to make that decision.
47
+
48
+ ## Quick Start
49
+
50
+ ```sh
51
+ npm install -g agenthint
52
+ # or
53
+ cargo install agenthint
54
+ ```
55
+
56
+ ```sh
57
+ if agenthint; then
58
+ my-tool --json --no-progress
59
+ else
60
+ my-tool
61
+ fi
62
+ ```
63
+
64
+ Use it inside another CLI or script to choose agent-friendly output:
65
+
66
+ ```sh
67
+ if agenthint >/dev/null; then
68
+ exec my-cli --json --no-progress --no-pager "$@"
69
+ else
70
+ exec my-cli "$@"
71
+ fi
72
+ ```
73
+
74
+ For agents and wrappers, the preferred explicit convention is `AI_AGENT`:
75
+
76
+ ```sh
77
+ AI_AGENT=codex my-tool
78
+ AI_AGENT=claude-code my-tool
79
+ AI_AGENT=my-custom-agent my-tool
80
+ ```
81
+
82
+ ## CLI
83
+
84
+ ```sh
85
+ agenthint # exit 0 if an agent is likely detected, otherwise 1
86
+ agenthint --json # print the structured detection result
87
+ agenthint --explain # print a short explanation
88
+ agenthint doctor # print detection details and setup advice
89
+ agenthint doctor --json
90
+ # print detection details and setup advice as JSON
91
+ agenthint init codex # print the recommended AI_AGENT value
92
+ ```
93
+
94
+ Example JSON output:
95
+
96
+ ```json
97
+ {
98
+ "isAgent": true,
99
+ "agent": "codex",
100
+ "confidence": 0.92,
101
+ "signals": ["env:CODEX_CI", "env:CODEX_THREAD_ID"]
102
+ }
103
+ ```
104
+
105
+ ## Install
106
+
107
+ Install from npm:
108
+
109
+ ```sh
110
+ npm install -g agenthint
111
+ agenthint --json
112
+ ```
113
+
114
+ Install from crates.io:
115
+
116
+ ```sh
117
+ cargo install agenthint
118
+ agenthint --json
119
+ ```
120
+
121
+ Install from PyPI:
122
+
123
+ ```sh
124
+ python3 -m pip install agenthint
125
+ agenthint --json
126
+ ```
127
+
128
+ Install the latest native binary from GitHub Releases:
129
+
130
+ ```sh
131
+ curl -fsSL https://raw.githubusercontent.com/forjd/agenthint/main/install.sh | sh
132
+ ```
133
+
134
+ Override the install directory or version:
135
+
136
+ ```sh
137
+ AGENTHINT_INSTALL_DIR=/usr/local/bin sh install.sh
138
+ AGENTHINT_VERSION=agenthint-vX.Y.Z sh install.sh
139
+ ```
140
+
141
+ Native binaries are built by GitHub Actions for release assets. The installer verifies `SHA256SUMS` when the selected release provides it.
142
+
143
+ ## TypeScript API
144
+
145
+ ```ts
146
+ import { detectAgent } from "agenthint";
147
+
148
+ const result = detectAgent();
149
+
150
+ if (result.isAgent) {
151
+ // Prefer structured, quiet, non-interactive output.
152
+ }
153
+ ```
154
+
155
+ ## Rust API
156
+
157
+ The repository also contains a Rust implementation under `crates/agenthint`.
158
+
159
+ ```rust
160
+ use agenthint::detect_agent;
161
+
162
+ let result = detect_agent();
163
+
164
+ if result.is_agent {
165
+ // Prefer structured, quiet, non-interactive output.
166
+ }
167
+ ```
168
+
169
+ Run the Rust CLI locally:
170
+
171
+ ```sh
172
+ cargo run -q -p agenthint -- --json
173
+ ```
174
+
175
+ ## Python API
176
+
177
+ ```python
178
+ from agenthint import detect_agent
179
+
180
+ result = detect_agent()
181
+
182
+ if result.is_agent:
183
+ # Prefer structured, quiet, non-interactive output.
184
+ pass
185
+ ```
186
+
187
+ ## Detection Model
188
+
189
+ The result includes:
190
+
191
+ - `isAgent`: whether an agent runtime is likely detected
192
+ - `agent`: known or custom agent name
193
+ - `confidence`: a number from `0` to `1`
194
+ - `signals`: diagnostic signal names, never secret values
195
+
196
+ Detection priority:
197
+
198
+ 1. `AGENTHINT_DISABLE`
199
+ 2. `AGENTHINT_FORCE`
200
+ 3. explicit `AI_AGENT`
201
+ 4. known environment signals
202
+ 5. documented filesystem signals
203
+ 6. low-confidence parent process signals
204
+ 7. low-confidence stdio hints
205
+
206
+ ## Supported Agents
207
+
208
+ Current known agent names include:
209
+
210
+ - Codex
211
+ - Claude Code
212
+ - Cowork
213
+ - Cursor
214
+ - Gemini CLI
215
+ - Aider
216
+ - Augment CLI
217
+ - AMP
218
+ - OpenCode
219
+ - OpenClaw
220
+ - GitHub Copilot
221
+ - Replit
222
+ - Devin
223
+ - Google Antigravity
224
+ - Pi
225
+ - Kiro CLI
226
+ - Windsurf
227
+ - Cline
228
+ - Roo Code
229
+ - Kilo Code
230
+ - Mistral Vibe
231
+ - v0
232
+
233
+ Custom agents are supported through any non-empty `AI_AGENT` value.
234
+
235
+ See [docs/agents.md](docs/agents.md) for recommended `AI_AGENT` values.
236
+
237
+ See [docs/integrations.md](docs/integrations.md) for Bash, Zsh, Fish, Node.js, Rust, and Python integration snippets.
238
+
239
+ See [docs/signals.md](docs/signals.md) for the signal registry and confidence levels.
240
+
241
+ ## Principles
242
+
243
+ - Detection is advisory and can be spoofed.
244
+ - Prefer `AI_AGENT` when an agent can set an explicit hint.
245
+ - Prefer explicit environment signals over brittle heuristics.
246
+ - Return confidence, not false certainty.
247
+ - Print signal names, not environment variable values.
248
+ - Keep output quiet and machine-readable when requested.
249
+ - Make the convention useful across languages and toolchains.
250
+
251
+ ## Packages
252
+
253
+ Current:
254
+
255
+ - `agenthint` JavaScript/TypeScript package
256
+ - `agenthint` Rust crate and CLI implementation
257
+ - `agenthint` Python package
258
+
259
+ Planned:
260
+
261
+ - standalone native binary releases
262
+
263
+ The packages use the unscoped `agenthint` name across npm, crates.io, and PyPI. If the npm name becomes unavailable before first publish, the fallback package name is `@forjd/agenthint`.
264
+
265
+ ## CI and Releases
266
+
267
+ GitHub Actions runs formatting, linting, TypeScript tests, Rust tests, Python tests, npm package checks, Python package build checks, and `cargo publish --dry-run`.
268
+
269
+ Releases use release-please with Conventional Commits. npm and PyPI publishing use trusted publishing via GitHub Actions OIDC, so no long-lived package tokens are required. Before the first publish, configure trusted publishers for `forjd/agenthint` and `.github/workflows/release.yml`.
270
+
271
+ See [docs/releases.md](docs/releases.md) for release details.
272
+
273
+ ## Development
274
+
275
+ Use [mise](https://mise.jdx.dev/) for local toolchain versions:
276
+
277
+ ```sh
278
+ mise install
279
+ ```
280
+
281
+ Install dependencies and run checks:
282
+
283
+ ```sh
284
+ npm install
285
+ npm run check
286
+ ```
287
+
288
+ Useful commands:
289
+
290
+ ```sh
291
+ npm run format
292
+ npm run lint
293
+ npm test
294
+ npm run python:build
295
+ npm run python:test
296
+ npm run generate:rules
297
+ cargo test --workspace
298
+ cargo clippy --workspace --all-targets -- -D warnings
299
+ ```
300
+
301
+ ## Security
302
+
303
+ `agenthint` is not an authentication, authorization, sandboxing, or policy tool. Environment variables, parent process names, and filesystem markers can be spoofed. Treat all results as UX hints only.
304
+
305
+ ## License
306
+
307
+ MIT © Forjd
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ python/agenthint/__init__.py
5
+ python/agenthint/cli.py
6
+ python/agenthint/detection-rules.json
7
+ python/agenthint.egg-info/PKG-INFO
8
+ python/agenthint.egg-info/SOURCES.txt
9
+ python/agenthint.egg-info/dependency_links.txt
10
+ python/agenthint.egg-info/entry_points.txt
11
+ python/agenthint.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agenthint = agenthint.cli:main
@@ -0,0 +1 @@
1
+ agenthint
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+