agenthint 0.4.0__py3-none-any.whl
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.
- agenthint/__init__.py +204 -0
- agenthint/cli.py +40 -0
- agenthint/detection-rules.json +151 -0
- agenthint-0.4.0.dist-info/METADATA +307 -0
- agenthint-0.4.0.dist-info/RECORD +9 -0
- agenthint-0.4.0.dist-info/WHEEL +5 -0
- agenthint-0.4.0.dist-info/entry_points.txt +2 -0
- agenthint-0.4.0.dist-info/licenses/LICENSE +21 -0
- agenthint-0.4.0.dist-info/top_level.txt +1 -0
agenthint/__init__.py
ADDED
|
@@ -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"]
|
agenthint/cli.py
ADDED
|
@@ -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
|
+
[](https://github.com/forjd/agenthint/actions/workflows/ci.yml)
|
|
24
|
+
[](https://github.com/forjd/agenthint/actions/workflows/release.yml)
|
|
25
|
+
[](https://www.npmjs.com/package/agenthint)
|
|
26
|
+
[](https://crates.io/crates/agenthint)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
[](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,9 @@
|
|
|
1
|
+
agenthint/__init__.py,sha256=psAFlb4lH6g2ehwgpu6bZ7kfvkVhOSo-Qnyb_7RwNQ0,6462
|
|
2
|
+
agenthint/cli.py,sha256=hpDwzzzdDVhEmu1PqyklAkgO325h9jw5CIyLVVsc8iw,899
|
|
3
|
+
agenthint/detection-rules.json,sha256=wKnYSBXPY0qe3Hb16-MT-2TooHQnbJQRyLTk7gEABe0,2824
|
|
4
|
+
agenthint-0.4.0.dist-info/licenses/LICENSE,sha256=nXyn4JNOhM1tW6PbUeQ9Wu0EuToVPvK6gehLRML1VLk,1062
|
|
5
|
+
agenthint-0.4.0.dist-info/METADATA,sha256=CeCwerspuwy93hqd0QBMJYmC5ABJA2d-0SMO-o1NkoM,7759
|
|
6
|
+
agenthint-0.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
agenthint-0.4.0.dist-info/entry_points.txt,sha256=sRjtzXRUOzK0TvXmbncvVVDPRzcBIugwe6wPfV9WsO8,49
|
|
8
|
+
agenthint-0.4.0.dist-info/top_level.txt,sha256=W03YuMpAFJVQFAmqURdoN-t06ZVQJhs_EjRafnnHvnc,10
|
|
9
|
+
agenthint-0.4.0.dist-info/RECORD,,
|
|
@@ -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 @@
|
|
|
1
|
+
agenthint
|