detect_agent 0.1.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,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.9
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial Release
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: detect_agent
3
+ Version: 0.1.0
4
+ Summary: Detect if code is running in an AI agent or automated development environment
5
+ Project-URL: Homepage, https://github.com/togethercomputer/detect_agent
6
+ Project-URL: Repository, https://github.com/togethercomputer/detect_agent
7
+ Project-URL: Changelog, https://github.com/togethercomputer/detect_agent/blob/main/CHANGELOG.md
8
+ Requires-Python: >=3.9
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=7.0; extra == 'dev'
11
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
12
+ Description-Content-Type: text/markdown
13
+
14
+ # detect_agent
15
+
16
+ > This is a Python Port of Vercels NPM package
17
+
18
+ A lightweight utility for detecting if code is being executed by an AI agent or automated development environment.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ uv add detect_agent
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ from detect_agent import determine_agent
30
+
31
+ result = determine_agent()
32
+
33
+ if result["is_agent"]:
34
+ print(f"Running in {result["agent"]["name"]} environment");
35
+ ```
36
+
37
+ ## Supported Agents
38
+
39
+ This package can detect the following AI agents and development environments:
40
+
41
+ - **Custom agents** via `AI_AGENT` environment variable
42
+ - **Cursor** (cursor editor and cursor-cli)
43
+ - **Claude Code** (Anthropic's Claude)
44
+ - **Devin** (Cognition Labs)
45
+ - **Gemini CLI** (Google)
46
+ - **Codex** (OpenAI)
47
+ - **Antigravity** (Google DeepMind)
48
+ - **GitHub Copilot** (via `AI_AGENT=github-copilot|github-copilot-cli`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`, or `COPILOT_GITHUB_TOKEN`)
49
+ - **Replit** (online IDE)
50
+
51
+ ## The AI_AGENT Standard
52
+
53
+ We're promoting `AI_AGENT` as a universal environment variable standard for AI development tools. This allows any tool or library to easily detect when it's running in an AI-driven environment.
54
+
55
+ ### For AI Tool Developers
56
+
57
+ Set the `AI_AGENT` environment variable to identify your tool:
58
+
59
+ ```bash
60
+ export AI_AGENT="your-tool-name"
61
+ # or
62
+ AI_AGENT="your-tool-name" your-command
63
+ ```
64
+
65
+ ### Recommended Naming Convention
66
+
67
+ - Use lowercase with hyphens for multi-word names
68
+ - Include version information if needed, separated by an `@` symbol
69
+ - Examples: `claude-code`, `cursor-cli`, `devin@1`, `custom-agent@2.0`
70
+
71
+ ## Development
72
+
73
+ ```bash
74
+ uv sync --extra dev
75
+ uv run pytest
76
+ # Lint and format check (CI)
77
+ uv run ruff check . && uv run ruff format --check .
78
+ # Fix and format
79
+ uv run ruff check . --fix && uv run ruff format .
80
+ ```
81
+
82
+ ## Use Cases
83
+
84
+ ### Adaptive Behavior
85
+
86
+ ```python
87
+ from detect_agent import determine_agent
88
+ import os
89
+
90
+ def setup_environment():
91
+ result = determine_agent()
92
+
93
+ if (result["is_agent"]) {
94
+ # Running in AI environment - adjust behavior
95
+ os.environ.setdefault("TOGETHER_LOG", "debug")
96
+ print(f"🤖 Detected AI agent: {result["agent"]["name"]}");
97
+ ```
98
+
99
+ ### Telemetry and Analytics
100
+
101
+ ```python
102
+ from detect_agent import determine_agent
103
+
104
+ def track_usage(event: string):
105
+ result = determine_agent();
106
+
107
+ analytics.track(event, {
108
+ "agent": result["agent"]["name"] if result["is_agent"] else "human",
109
+ })
110
+ ```
111
+ ### Adding New Agent Support
112
+
113
+ To add support for a new AI agent:
114
+
115
+ 1. Add detection logic to `main.py`
116
+ 2. Add comprehensive test cases in `test.py`
117
+ 3. Update this README with the new agent information
118
+ 4. Follow the existing priority order pattern
@@ -0,0 +1,105 @@
1
+ # detect_agent
2
+
3
+ > This is a Python Port of Vercels NPM package
4
+
5
+ A lightweight utility for detecting if code is being executed by an AI agent or automated development environment.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ uv add detect_agent
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from detect_agent import determine_agent
17
+
18
+ result = determine_agent()
19
+
20
+ if result["is_agent"]:
21
+ print(f"Running in {result["agent"]["name"]} environment");
22
+ ```
23
+
24
+ ## Supported Agents
25
+
26
+ This package can detect the following AI agents and development environments:
27
+
28
+ - **Custom agents** via `AI_AGENT` environment variable
29
+ - **Cursor** (cursor editor and cursor-cli)
30
+ - **Claude Code** (Anthropic's Claude)
31
+ - **Devin** (Cognition Labs)
32
+ - **Gemini CLI** (Google)
33
+ - **Codex** (OpenAI)
34
+ - **Antigravity** (Google DeepMind)
35
+ - **GitHub Copilot** (via `AI_AGENT=github-copilot|github-copilot-cli`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`, or `COPILOT_GITHUB_TOKEN`)
36
+ - **Replit** (online IDE)
37
+
38
+ ## The AI_AGENT Standard
39
+
40
+ We're promoting `AI_AGENT` as a universal environment variable standard for AI development tools. This allows any tool or library to easily detect when it's running in an AI-driven environment.
41
+
42
+ ### For AI Tool Developers
43
+
44
+ Set the `AI_AGENT` environment variable to identify your tool:
45
+
46
+ ```bash
47
+ export AI_AGENT="your-tool-name"
48
+ # or
49
+ AI_AGENT="your-tool-name" your-command
50
+ ```
51
+
52
+ ### Recommended Naming Convention
53
+
54
+ - Use lowercase with hyphens for multi-word names
55
+ - Include version information if needed, separated by an `@` symbol
56
+ - Examples: `claude-code`, `cursor-cli`, `devin@1`, `custom-agent@2.0`
57
+
58
+ ## Development
59
+
60
+ ```bash
61
+ uv sync --extra dev
62
+ uv run pytest
63
+ # Lint and format check (CI)
64
+ uv run ruff check . && uv run ruff format --check .
65
+ # Fix and format
66
+ uv run ruff check . --fix && uv run ruff format .
67
+ ```
68
+
69
+ ## Use Cases
70
+
71
+ ### Adaptive Behavior
72
+
73
+ ```python
74
+ from detect_agent import determine_agent
75
+ import os
76
+
77
+ def setup_environment():
78
+ result = determine_agent()
79
+
80
+ if (result["is_agent"]) {
81
+ # Running in AI environment - adjust behavior
82
+ os.environ.setdefault("TOGETHER_LOG", "debug")
83
+ print(f"🤖 Detected AI agent: {result["agent"]["name"]}");
84
+ ```
85
+
86
+ ### Telemetry and Analytics
87
+
88
+ ```python
89
+ from detect_agent import determine_agent
90
+
91
+ def track_usage(event: string):
92
+ result = determine_agent();
93
+
94
+ analytics.track(event, {
95
+ "agent": result["agent"]["name"] if result["is_agent"] else "human",
96
+ })
97
+ ```
98
+ ### Adding New Agent Support
99
+
100
+ To add support for a new AI agent:
101
+
102
+ 1. Add detection logic to `main.py`
103
+ 2. Add comprehensive test cases in `test.py`
104
+ 3. Update this README with the new agent information
105
+ 4. Follow the existing priority order pattern
@@ -0,0 +1,121 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Literal, TypedDict, Union
4
+
5
+ DEVIN_LOCAL_PATH = "/opt/.devin"
6
+
7
+ CURSOR: Literal["cursor"] = "cursor"
8
+ CURSOR_CLI: Literal["cursor-cli"] = "cursor-cli"
9
+ CLAUDE: Literal["claude"] = "claude"
10
+ COWORK: Literal["cowork"] = "cowork"
11
+ DEVIN: Literal["devin"] = "devin"
12
+ REPLIT: Literal["replit"] = "replit"
13
+ GEMINI: Literal["gemini"] = "gemini"
14
+ CODEX: Literal["codex"] = "codex"
15
+ ANTIGRAVITY: Literal["antigravity"] = "antigravity"
16
+ AUGMENT_CLI: Literal["augment-cli"] = "augment-cli"
17
+ OPENCODE: Literal["opencode"] = "opencode"
18
+ GITHUB_COPILOT: Literal["github-copilot"] = "github-copilot"
19
+ GITHUB_COPILOT_CLI: Literal["github-copilot-cli"] = "github-copilot-cli"
20
+
21
+ KnownAgentNames = Literal[
22
+ "cursor",
23
+ "cursor-cli",
24
+ "claude",
25
+ "cowork",
26
+ "devin",
27
+ "replit",
28
+ "gemini",
29
+ "codex",
30
+ "antigravity",
31
+ "augment-cli",
32
+ "opencode",
33
+ "github-copilot",
34
+ ]
35
+
36
+
37
+ class KnownAgentDetails(TypedDict):
38
+ name: KnownAgentNames
39
+
40
+
41
+ class AgentResultAgent(TypedDict):
42
+ is_agent: Literal[True]
43
+ agent: KnownAgentDetails
44
+
45
+
46
+ class AgentResultNone(TypedDict):
47
+ is_agent: Literal[False]
48
+ agent: None
49
+
50
+
51
+ AgentResult = Union[AgentResultAgent, AgentResultNone]
52
+
53
+ KNOWN_AGENTS = {
54
+ "CURSOR": CURSOR,
55
+ "CURSOR_CLI": CURSOR_CLI,
56
+ "CLAUDE": CLAUDE,
57
+ "COWORK": COWORK,
58
+ "DEVIN": DEVIN,
59
+ "REPLIT": REPLIT,
60
+ "GEMINI": GEMINI,
61
+ "CODEX": CODEX,
62
+ "ANTIGRAVITY": ANTIGRAVITY,
63
+ "AUGMENT_CLI": AUGMENT_CLI,
64
+ "OPENCODE": OPENCODE,
65
+ "GITHUB_COPILOT": GITHUB_COPILOT,
66
+ }
67
+
68
+
69
+ def determine_agent() -> AgentResult:
70
+ ai_agent = os.environ.get("AI_AGENT")
71
+ if ai_agent:
72
+ name = ai_agent.strip()
73
+ if name:
74
+ if name in (GITHUB_COPILOT, GITHUB_COPILOT_CLI):
75
+ return {"is_agent": True, "agent": {"name": GITHUB_COPILOT}}
76
+ return {"is_agent": True, "agent": {"name": name}} # type: ignore[return-value]
77
+
78
+ if os.environ.get("CURSOR_TRACE_ID"):
79
+ return {"is_agent": True, "agent": {"name": CURSOR}}
80
+
81
+ if os.environ.get("CURSOR_AGENT"):
82
+ return {"is_agent": True, "agent": {"name": CURSOR_CLI}}
83
+
84
+ if os.environ.get("GEMINI_CLI"):
85
+ return {"is_agent": True, "agent": {"name": GEMINI}}
86
+
87
+ if (
88
+ os.environ.get("CODEX_SANDBOX")
89
+ or os.environ.get("CODEX_CI")
90
+ or os.environ.get("CODEX_THREAD_ID")
91
+ ):
92
+ return {"is_agent": True, "agent": {"name": CODEX}}
93
+
94
+ if os.environ.get("ANTIGRAVITY_AGENT"):
95
+ return {"is_agent": True, "agent": {"name": ANTIGRAVITY}}
96
+
97
+ if os.environ.get("AUGMENT_AGENT"):
98
+ return {"is_agent": True, "agent": {"name": AUGMENT_CLI}}
99
+
100
+ if os.environ.get("OPENCODE_CLIENT"):
101
+ return {"is_agent": True, "agent": {"name": OPENCODE}}
102
+
103
+ if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_CODE"):
104
+ if os.environ.get("CLAUDE_CODE_IS_COWORK"):
105
+ return {"is_agent": True, "agent": {"name": COWORK}}
106
+ return {"is_agent": True, "agent": {"name": CLAUDE}}
107
+
108
+ if os.environ.get("REPL_ID"):
109
+ return {"is_agent": True, "agent": {"name": REPLIT}}
110
+
111
+ if (
112
+ os.environ.get("COPILOT_MODEL")
113
+ or os.environ.get("COPILOT_ALLOW_ALL")
114
+ or os.environ.get("COPILOT_GITHUB_TOKEN")
115
+ ):
116
+ return {"is_agent": True, "agent": {"name": GITHUB_COPILOT}}
117
+
118
+ if Path(DEVIN_LOCAL_PATH).exists():
119
+ return {"is_agent": True, "agent": {"name": DEVIN}}
120
+
121
+ return {"is_agent": False, "agent": None}
File without changes
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = ["detect_agent"]
7
+
8
+ [project]
9
+ name = "detect_agent"
10
+ version = "0.1.0"
11
+ description = "Detect if code is running in an AI agent or automated development environment"
12
+ readme = "README.md"
13
+ requires-python = ">=3.9"
14
+ dependencies = []
15
+ packages = ["detect_agent"]
16
+
17
+ [project.optional-dependencies]
18
+ dev = ["pytest>=7.0", "ruff>=0.8.0"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/togethercomputer/detect_agent"
22
+ Repository = "https://github.com/togethercomputer/detect_agent"
23
+ Changelog = "https://github.com/togethercomputer/detect_agent/blob/main/CHANGELOG.md"
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
27
+ xfail_strict = true
28
+ filterwarnings = [
29
+ "error"
30
+ ]
31
+
32
+ [tool.ruff]
33
+ line-length = 100
34
+ target-version = "py39"
35
+
36
+ [tool.ruff.lint]
37
+ select = ["E", "F", "I", "UP"]
38
+ ignore = []
39
+
40
+ [tool.ruff.lint.per-file-ignores]
41
+ "tests/**/*.py" = ["S101"] # allow assert in tests
File without changes
@@ -0,0 +1,367 @@
1
+ """Tests for determine_agent."""
2
+
3
+ from pathlib import Path
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+
8
+ from detect_agent import (
9
+ DEVIN_LOCAL_PATH,
10
+ KNOWN_AGENTS,
11
+ determine_agent,
12
+ )
13
+
14
+ # Env vars we reset so tests don't leak into each other
15
+ _AGENT_ENV_VARS = (
16
+ "AI_AGENT",
17
+ "CURSOR_TRACE_ID",
18
+ "CURSOR_AGENT",
19
+ "GEMINI_CLI",
20
+ "CODEX_SANDBOX",
21
+ "CODEX_CI",
22
+ "CODEX_THREAD_ID",
23
+ "ANTIGRAVITY_AGENT",
24
+ "AUGMENT_AGENT",
25
+ "OPENCODE_CLIENT",
26
+ "CLAUDECODE",
27
+ "CLAUDE_CODE",
28
+ "CLAUDE_CODE_IS_COWORK",
29
+ "REPL_ID",
30
+ "COPILOT_MODEL",
31
+ "COPILOT_ALLOW_ALL",
32
+ "COPILOT_GITHUB_TOKEN",
33
+ )
34
+
35
+
36
+ @pytest.fixture(autouse=True)
37
+ def _clear_agent_env(monkeypatch):
38
+ for key in _AGENT_ENV_VARS:
39
+ monkeypatch.delenv(key, raising=False)
40
+ yield
41
+ for key in _AGENT_ENV_VARS:
42
+ monkeypatch.delenv(key, raising=False)
43
+
44
+
45
+ class TestCustomAgentFromAI_AGENT:
46
+ """Custom agent detection from AI_AGENT."""
47
+
48
+ def test_ai_agent_not_set_returns_no_agent(self):
49
+ result = determine_agent()
50
+ assert result == {"is_agent": False, "agent": None}
51
+
52
+ def test_ai_agent_set_detects_custom_agent(self, monkeypatch):
53
+ monkeypatch.setenv("AI_AGENT", "custom-agent")
54
+ result = determine_agent()
55
+ assert result == {"is_agent": True, "agent": {"name": "custom-agent"}}
56
+
57
+
58
+ class TestGitHubCopilotDetection:
59
+ """GitHub Copilot detection."""
60
+
61
+ def test_from_ai_agent_github_copilot(self, monkeypatch):
62
+ monkeypatch.setenv("AI_AGENT", "github-copilot")
63
+ result = determine_agent()
64
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GITHUB_COPILOT"]}}
65
+
66
+ def test_from_ai_agent_github_copilot_cli(self, monkeypatch):
67
+ monkeypatch.setenv("AI_AGENT", "github-copilot-cli")
68
+ result = determine_agent()
69
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GITHUB_COPILOT"]}}
70
+
71
+ def test_from_copilot_model(self, monkeypatch):
72
+ monkeypatch.setenv("COPILOT_MODEL", "gpt-5")
73
+ result = determine_agent()
74
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GITHUB_COPILOT"]}}
75
+
76
+ def test_from_copilot_allow_all(self, monkeypatch):
77
+ monkeypatch.setenv("COPILOT_ALLOW_ALL", "true")
78
+ result = determine_agent()
79
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GITHUB_COPILOT"]}}
80
+
81
+ def test_from_copilot_github_token(self, monkeypatch):
82
+ monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "ghp_xxx")
83
+ result = determine_agent()
84
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GITHUB_COPILOT"]}}
85
+
86
+
87
+ class TestCursorDetection:
88
+ """Cursor detection."""
89
+
90
+ def test_cursor_trace_id_not_set_returns_no_agent(self):
91
+ result = determine_agent()
92
+ assert result == {"is_agent": False, "agent": None}
93
+
94
+ def test_cursor_trace_id_set_detects_cursor(self, monkeypatch):
95
+ monkeypatch.setenv("CURSOR_TRACE_ID", "some-uuid")
96
+ result = determine_agent()
97
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CURSOR"]}}
98
+
99
+
100
+ class TestCursorCliDetection:
101
+ """Cursor CLI detection."""
102
+
103
+ def test_cursor_agent_not_set_returns_no_agent(self):
104
+ result = determine_agent()
105
+ assert result == {"is_agent": False, "agent": None}
106
+
107
+ def test_cursor_agent_set_detects_cursor_cli(self, monkeypatch):
108
+ monkeypatch.setenv("CURSOR_AGENT", "1")
109
+ result = determine_agent()
110
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CURSOR_CLI"]}}
111
+
112
+
113
+ class TestGeminiDetection:
114
+ """Gemini detection."""
115
+
116
+ def test_gemini_cli_not_set_returns_no_agent(self):
117
+ result = determine_agent()
118
+ assert result == {"is_agent": False, "agent": None}
119
+
120
+ def test_gemini_cli_set_detects_gemini(self, monkeypatch):
121
+ monkeypatch.setenv("GEMINI_CLI", "1")
122
+ result = determine_agent()
123
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["GEMINI"]}}
124
+
125
+
126
+ class TestCodexDetection:
127
+ """Codex detection."""
128
+
129
+ def test_codex_sandbox_not_set_returns_no_agent(self):
130
+ result = determine_agent()
131
+ assert result == {"is_agent": False, "agent": None}
132
+
133
+ def test_codex_sandbox_set_detects_codex(self, monkeypatch):
134
+ monkeypatch.setenv("CODEX_SANDBOX", "seatbelt")
135
+ result = determine_agent()
136
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CODEX"]}}
137
+
138
+ def test_codex_ci_set_detects_codex(self, monkeypatch):
139
+ monkeypatch.setenv("CODEX_CI", "1")
140
+ result = determine_agent()
141
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CODEX"]}}
142
+
143
+ def test_codex_thread_id_set_detects_codex(self, monkeypatch):
144
+ monkeypatch.setenv("CODEX_THREAD_ID", "thread-123")
145
+ result = determine_agent()
146
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CODEX"]}}
147
+
148
+
149
+ class TestAntigravityDetection:
150
+ """Antigravity detection."""
151
+
152
+ def test_antigravity_agent_not_set_returns_no_agent(self):
153
+ result = determine_agent()
154
+ assert result == {"is_agent": False, "agent": None}
155
+
156
+ def test_antigravity_agent_set_detects_antigravity(self, monkeypatch):
157
+ monkeypatch.setenv("ANTIGRAVITY_AGENT", "1")
158
+ result = determine_agent()
159
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["ANTIGRAVITY"]}}
160
+
161
+
162
+ class TestAugmentCliDetection:
163
+ """Augment CLI detection."""
164
+
165
+ def test_augment_agent_not_set_returns_no_agent(self):
166
+ result = determine_agent()
167
+ assert result == {"is_agent": False, "agent": None}
168
+
169
+ def test_augment_agent_set_detects_augment_cli(self, monkeypatch):
170
+ monkeypatch.setenv("AUGMENT_AGENT", "1")
171
+ result = determine_agent()
172
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["AUGMENT_CLI"]}}
173
+
174
+
175
+ class TestOpencodeDetection:
176
+ """Opencode detection."""
177
+
178
+ def test_opencode_client_not_set_returns_no_agent(self):
179
+ result = determine_agent()
180
+ assert result == {"is_agent": False, "agent": None}
181
+
182
+ def test_opencode_client_set_detects_opencode(self, monkeypatch):
183
+ monkeypatch.setenv("OPENCODE_CLIENT", "opencode")
184
+ result = determine_agent()
185
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["OPENCODE"]}}
186
+
187
+
188
+ class TestClaudeDetection:
189
+ """Claude detection."""
190
+
191
+ def test_claude_code_not_set_returns_no_agent(self):
192
+ result = determine_agent()
193
+ assert result == {"is_agent": False, "agent": None}
194
+
195
+ def test_claude_code_set_detects_claude(self, monkeypatch):
196
+ monkeypatch.setenv("CLAUDE_CODE", "1")
197
+ result = determine_agent()
198
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CLAUDE"]}}
199
+
200
+ def test_claudecode_set_detects_claude(self, monkeypatch):
201
+ monkeypatch.setenv("CLAUDECODE", "1")
202
+ result = determine_agent()
203
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CLAUDE"]}}
204
+
205
+
206
+ class TestCoworkDetection:
207
+ """Cowork detection."""
208
+
209
+ def test_claude_code_is_cowork_not_set_detects_claude(self, monkeypatch):
210
+ monkeypatch.setenv("CLAUDECODE", "1")
211
+ result = determine_agent()
212
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CLAUDE"]}}
213
+
214
+ def test_claude_code_is_cowork_set_with_claudecode_detects_cowork(self, monkeypatch):
215
+ monkeypatch.setenv("CLAUDECODE", "1")
216
+ monkeypatch.setenv("CLAUDE_CODE_IS_COWORK", "1")
217
+ result = determine_agent()
218
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["COWORK"]}}
219
+
220
+ def test_claude_code_is_cowork_set_with_claude_code_detects_cowork(self, monkeypatch):
221
+ monkeypatch.setenv("CLAUDE_CODE", "1")
222
+ monkeypatch.setenv("CLAUDE_CODE_IS_COWORK", "1")
223
+ result = determine_agent()
224
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["COWORK"]}}
225
+
226
+ def test_claude_code_is_cowork_set_without_claudecode_or_claude_code_returns_no_agent(
227
+ self, monkeypatch
228
+ ):
229
+ monkeypatch.setenv("CLAUDE_CODE_IS_COWORK", "1")
230
+ result = determine_agent()
231
+ assert result == {"is_agent": False, "agent": None}
232
+
233
+
234
+ class TestDevinDetection:
235
+ """Devin detection."""
236
+
237
+ def test_devin_path_does_not_exist_returns_no_agent(self):
238
+ with patch.object(Path, "exists", return_value=False):
239
+ result = determine_agent()
240
+ assert result == {"is_agent": False, "agent": None}
241
+
242
+ def test_devin_path_exists_detects_devin(self):
243
+ with patch.object(Path, "exists", return_value=True):
244
+ result = determine_agent()
245
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["DEVIN"]}}
246
+
247
+
248
+ class TestReplitDetection:
249
+ """Replit detection."""
250
+
251
+ def test_repl_id_not_set_returns_no_agent(self):
252
+ result = determine_agent()
253
+ assert result == {"is_agent": False, "agent": None}
254
+
255
+ def test_repl_id_set_detects_replit(self, monkeypatch):
256
+ monkeypatch.setenv("REPL_ID", "1")
257
+ result = determine_agent()
258
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["REPLIT"]}}
259
+
260
+
261
+ class TestPriorityOrderDetection:
262
+ """Priority order detection."""
263
+
264
+ def test_ai_agent_takes_highest_priority(self, monkeypatch):
265
+ monkeypatch.setenv("AI_AGENT", "custom-priority")
266
+ monkeypatch.setenv("CURSOR_TRACE_ID", "some-uuid")
267
+ monkeypatch.setenv("CURSOR_AGENT", "1")
268
+ monkeypatch.setenv("GEMINI_CLI", "1")
269
+ monkeypatch.setenv("CODEX_SANDBOX", "seatbelt")
270
+ monkeypatch.setenv("ANTIGRAVITY_AGENT", "1")
271
+ monkeypatch.setenv("AUGMENT_AGENT", "1")
272
+ monkeypatch.setenv("OPENCODE_CLIENT", "opencode")
273
+ monkeypatch.setenv("CLAUDE_CODE", "1")
274
+ monkeypatch.setenv("REPL_ID", "1")
275
+ monkeypatch.setenv("COPILOT_MODEL", "gpt-5")
276
+ monkeypatch.setenv("COPILOT_ALLOW_ALL", "true")
277
+ monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "ghp_xxx")
278
+ with patch.object(Path, "exists") as mock_exists:
279
+ mock_exists.side_effect = lambda self: str(self) == DEVIN_LOCAL_PATH
280
+ result = determine_agent()
281
+ assert result == {"is_agent": True, "agent": {"name": "custom-priority"}}
282
+
283
+ def test_cursor_trace_id_takes_priority_over_other_agents(self, monkeypatch):
284
+ monkeypatch.setenv("CURSOR_TRACE_ID", "some-uuid")
285
+ monkeypatch.setenv("CURSOR_AGENT", "1")
286
+ monkeypatch.setenv("GEMINI_CLI", "1")
287
+ monkeypatch.setenv("CODEX_SANDBOX", "seatbelt")
288
+ monkeypatch.setenv("ANTIGRAVITY_AGENT", "1")
289
+ monkeypatch.setenv("AUGMENT_AGENT", "1")
290
+ monkeypatch.setenv("OPENCODE_CLIENT", "opencode")
291
+ monkeypatch.setenv("CLAUDE_CODE", "1")
292
+ monkeypatch.setenv("REPL_ID", "1")
293
+ monkeypatch.setenv("COPILOT_MODEL", "gpt-5")
294
+ monkeypatch.setenv("COPILOT_ALLOW_ALL", "true")
295
+ monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "ghp_xxx")
296
+ with patch.object(Path, "exists") as mock_exists:
297
+ mock_exists.side_effect = lambda self: str(self) == DEVIN_LOCAL_PATH
298
+ result = determine_agent()
299
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CURSOR"]}}
300
+
301
+ def test_cursor_agent_takes_priority_over_remaining_agents(self, monkeypatch):
302
+ monkeypatch.setenv("CURSOR_AGENT", "1")
303
+ monkeypatch.setenv("GEMINI_CLI", "1")
304
+ monkeypatch.setenv("CODEX_SANDBOX", "seatbelt")
305
+ monkeypatch.setenv("ANTIGRAVITY_AGENT", "1")
306
+ monkeypatch.setenv("AUGMENT_AGENT", "1")
307
+ monkeypatch.setenv("OPENCODE_CLIENT", "opencode")
308
+ monkeypatch.setenv("CLAUDE_CODE", "1")
309
+ monkeypatch.setenv("REPL_ID", "1")
310
+ monkeypatch.setenv("COPILOT_MODEL", "gpt-5")
311
+ monkeypatch.setenv("COPILOT_ALLOW_ALL", "true")
312
+ monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "ghp_xxx")
313
+ with patch.object(Path, "exists") as mock_exists:
314
+ mock_exists.side_effect = lambda self: str(self) == DEVIN_LOCAL_PATH
315
+ result = determine_agent()
316
+ assert result == {"is_agent": True, "agent": {"name": KNOWN_AGENTS["CURSOR_CLI"]}}
317
+
318
+
319
+ class TestEdgeCases:
320
+ """Edge cases."""
321
+
322
+ def test_empty_string_env_vars(self, monkeypatch):
323
+ monkeypatch.setenv("AI_AGENT", "")
324
+ monkeypatch.setenv("CURSOR_TRACE_ID", "")
325
+ result = determine_agent()
326
+ assert result == {"is_agent": False, "agent": None}
327
+
328
+ def test_whitespace_only_ai_agent(self, monkeypatch):
329
+ monkeypatch.setenv("AI_AGENT", " ")
330
+ result = determine_agent()
331
+ assert result == {"is_agent": False, "agent": None}
332
+
333
+ def test_special_characters_in_ai_agent(self, monkeypatch):
334
+ monkeypatch.setenv("AI_AGENT", "my-custom-agent@v1.0")
335
+ result = determine_agent()
336
+ assert result == {"is_agent": True, "agent": {"name": "my-custom-agent@v1.0"}}
337
+
338
+ def test_trims_whitespace_from_ai_agent(self, monkeypatch):
339
+ monkeypatch.setenv("AI_AGENT", " custom-agent ")
340
+ result = determine_agent()
341
+ assert result == {"is_agent": True, "agent": {"name": "custom-agent"}}
342
+
343
+ def test_devin_path_not_accessible_returns_no_agent(self):
344
+ with patch.object(Path, "exists", return_value=False):
345
+ result = determine_agent()
346
+ assert result == {"is_agent": False, "agent": None}
347
+
348
+
349
+ class TestConvenienceMethods:
350
+ """Convenience methods."""
351
+
352
+ def test_is_agent_boolean(self, monkeypatch):
353
+ monkeypatch.setenv("AI_AGENT", "test-agent")
354
+ result = determine_agent()
355
+ assert result["is_agent"] is True
356
+
357
+ def test_agent_details_when_detected(self, monkeypatch):
358
+ monkeypatch.setenv("CURSOR_TRACE_ID", "some-id")
359
+ result = determine_agent()
360
+ assert result["is_agent"] is True
361
+ assert result.get("agent") is not None
362
+ assert result["agent"]["name"] == KNOWN_AGENTS["CURSOR"]
363
+
364
+ def test_no_agent_details_when_not_detected(self):
365
+ result = determine_agent()
366
+ assert result["is_agent"] is False
367
+ assert result.get("agent") is None
@@ -0,0 +1,228 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.9"
4
+ resolution-markers = [
5
+ "python_full_version >= '3.10'",
6
+ "python_full_version < '3.10'",
7
+ ]
8
+
9
+ [[package]]
10
+ name = "colorama"
11
+ version = "0.4.6"
12
+ source = { registry = "https://pypi.org/simple" }
13
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
14
+ wheels = [
15
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
16
+ ]
17
+
18
+ [[package]]
19
+ name = "detect-agent"
20
+ version = "0.1.0"
21
+ source = { editable = "." }
22
+
23
+ [package.optional-dependencies]
24
+ dev = [
25
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
26
+ { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
27
+ { name = "ruff" },
28
+ ]
29
+
30
+ [package.metadata]
31
+ requires-dist = [
32
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
33
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" },
34
+ ]
35
+ provides-extras = ["dev"]
36
+
37
+ [[package]]
38
+ name = "exceptiongroup"
39
+ version = "1.3.1"
40
+ source = { registry = "https://pypi.org/simple" }
41
+ dependencies = [
42
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
43
+ ]
44
+ sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
45
+ wheels = [
46
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
47
+ ]
48
+
49
+ [[package]]
50
+ name = "iniconfig"
51
+ version = "2.1.0"
52
+ source = { registry = "https://pypi.org/simple" }
53
+ resolution-markers = [
54
+ "python_full_version < '3.10'",
55
+ ]
56
+ sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
57
+ wheels = [
58
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
59
+ ]
60
+
61
+ [[package]]
62
+ name = "iniconfig"
63
+ version = "2.3.0"
64
+ source = { registry = "https://pypi.org/simple" }
65
+ resolution-markers = [
66
+ "python_full_version >= '3.10'",
67
+ ]
68
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
69
+ wheels = [
70
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
71
+ ]
72
+
73
+ [[package]]
74
+ name = "packaging"
75
+ version = "26.0"
76
+ source = { registry = "https://pypi.org/simple" }
77
+ sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
78
+ wheels = [
79
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
80
+ ]
81
+
82
+ [[package]]
83
+ name = "pluggy"
84
+ version = "1.6.0"
85
+ source = { registry = "https://pypi.org/simple" }
86
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
87
+ wheels = [
88
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
89
+ ]
90
+
91
+ [[package]]
92
+ name = "pygments"
93
+ version = "2.19.2"
94
+ source = { registry = "https://pypi.org/simple" }
95
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
96
+ wheels = [
97
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
98
+ ]
99
+
100
+ [[package]]
101
+ name = "pytest"
102
+ version = "8.4.2"
103
+ source = { registry = "https://pypi.org/simple" }
104
+ resolution-markers = [
105
+ "python_full_version < '3.10'",
106
+ ]
107
+ dependencies = [
108
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
109
+ { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
110
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
111
+ { name = "packaging", marker = "python_full_version < '3.10'" },
112
+ { name = "pluggy", marker = "python_full_version < '3.10'" },
113
+ { name = "pygments", marker = "python_full_version < '3.10'" },
114
+ { name = "tomli", marker = "python_full_version < '3.10'" },
115
+ ]
116
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
117
+ wheels = [
118
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
119
+ ]
120
+
121
+ [[package]]
122
+ name = "pytest"
123
+ version = "9.0.2"
124
+ source = { registry = "https://pypi.org/simple" }
125
+ resolution-markers = [
126
+ "python_full_version >= '3.10'",
127
+ ]
128
+ dependencies = [
129
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
130
+ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
131
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
132
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
133
+ { name = "pluggy", marker = "python_full_version >= '3.10'" },
134
+ { name = "pygments", marker = "python_full_version >= '3.10'" },
135
+ { name = "tomli", marker = "python_full_version == '3.10.*'" },
136
+ ]
137
+ sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
138
+ wheels = [
139
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
140
+ ]
141
+
142
+ [[package]]
143
+ name = "ruff"
144
+ version = "0.15.6"
145
+ source = { registry = "https://pypi.org/simple" }
146
+ sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" }
147
+ wheels = [
148
+ { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" },
149
+ { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" },
150
+ { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" },
151
+ { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" },
152
+ { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" },
153
+ { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" },
154
+ { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" },
155
+ { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" },
156
+ { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" },
157
+ { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" },
158
+ { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" },
159
+ { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" },
160
+ { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" },
161
+ { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" },
162
+ { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" },
163
+ { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" },
164
+ { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" },
165
+ ]
166
+
167
+ [[package]]
168
+ name = "tomli"
169
+ version = "2.4.0"
170
+ source = { registry = "https://pypi.org/simple" }
171
+ sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
172
+ wheels = [
173
+ { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
174
+ { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
175
+ { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
176
+ { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
177
+ { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
178
+ { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
179
+ { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
180
+ { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
181
+ { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
182
+ { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
183
+ { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
184
+ { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
185
+ { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
186
+ { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
187
+ { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
188
+ { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
189
+ { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
190
+ { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
191
+ { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
192
+ { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
193
+ { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
194
+ { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
195
+ { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
196
+ { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
197
+ { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
198
+ { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
199
+ { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
200
+ { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
201
+ { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
202
+ { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
203
+ { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
204
+ { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
205
+ { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
206
+ { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
207
+ { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
208
+ { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
209
+ { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
210
+ { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
211
+ { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
212
+ { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
213
+ { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
214
+ { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
215
+ { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
216
+ { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
217
+ { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
218
+ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
219
+ ]
220
+
221
+ [[package]]
222
+ name = "typing-extensions"
223
+ version = "4.15.0"
224
+ source = { registry = "https://pypi.org/simple" }
225
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
226
+ wheels = [
227
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
228
+ ]