typed-agent-hooks 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.
Files changed (38) hide show
  1. typed_agent_hooks-0.1.0/LICENSE +21 -0
  2. typed_agent_hooks-0.1.0/PKG-INFO +216 -0
  3. typed_agent_hooks-0.1.0/README.md +191 -0
  4. typed_agent_hooks-0.1.0/pyproject.toml +89 -0
  5. typed_agent_hooks-0.1.0/pyproject.toml.orig +66 -0
  6. typed_agent_hooks-0.1.0/src/typed_agent_hooks/__init__.py +28 -0
  7. typed_agent_hooks-0.1.0/src/typed_agent_hooks/claude_code/__init__.py +15 -0
  8. typed_agent_hooks-0.1.0/src/typed_agent_hooks/claude_code/config.py +90 -0
  9. typed_agent_hooks-0.1.0/src/typed_agent_hooks/claude_code/events.py +489 -0
  10. typed_agent_hooks-0.1.0/src/typed_agent_hooks/claude_code/outputs.py +546 -0
  11. typed_agent_hooks-0.1.0/src/typed_agent_hooks/codex/__init__.py +15 -0
  12. typed_agent_hooks-0.1.0/src/typed_agent_hooks/codex/config.py +74 -0
  13. typed_agent_hooks-0.1.0/src/typed_agent_hooks/codex/events.py +166 -0
  14. typed_agent_hooks-0.1.0/src/typed_agent_hooks/codex/outputs.py +235 -0
  15. typed_agent_hooks-0.1.0/src/typed_agent_hooks/collection.py +145 -0
  16. typed_agent_hooks-0.1.0/src/typed_agent_hooks/config.py +321 -0
  17. typed_agent_hooks-0.1.0/src/typed_agent_hooks/core.py +110 -0
  18. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/__init__.py +28 -0
  19. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/__main__.py +5 -0
  20. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/bridge.py +324 -0
  21. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/config.py +143 -0
  22. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/launcher.py +68 -0
  23. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/rendezvous.py +469 -0
  24. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/shim.py +358 -0
  25. typed_agent_hooks-0.1.0/src/typed_agent_hooks/fastmcp/wire.py +111 -0
  26. typed_agent_hooks-0.1.0/src/typed_agent_hooks/internal.py +6 -0
  27. typed_agent_hooks-0.1.0/src/typed_agent_hooks/protocol.py +15 -0
  28. typed_agent_hooks-0.1.0/src/typed_agent_hooks/py.typed +0 -0
  29. typed_agent_hooks-0.1.0/src/typed_agent_hooks/registry.py +65 -0
  30. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/__init__.py +28 -0
  31. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/adapters.py +253 -0
  32. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/app.py +418 -0
  33. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/claude_code_outputs.py +277 -0
  34. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/codex_outputs.py +237 -0
  35. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/config.py +64 -0
  36. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/events.py +185 -0
  37. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/outputs.py +76 -0
  38. typed_agent_hooks-0.1.0/src/typed_agent_hooks/shared/results.py +137 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 typed-agent-hooks contributors
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,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: typed-agent-hooks
3
+ Version: 0.1.0
4
+ Summary: Code-first executable hooks and strict schemas for Codex and Claude Code.
5
+ Keywords: codex,claude-code,hooks,pydantic,schemas
6
+ Author: Nima Shoghi
7
+ Author-email: Nima Shoghi <nimashoghi@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: cyclopts>=4.23.2,<5
18
+ Requires-Dist: pydantic>=2.8,<3
19
+ Requires-Dist: pydantic-core
20
+ Requires-Dist: fastmcp>=3.3,<3.4 ; extra == 'fastmcp'
21
+ Requires-Python: >=3.10
22
+ Project-URL: Repository, https://github.com/nimashoghi/typed-agent-hooks
23
+ Provides-Extra: fastmcp
24
+ Description-Content-Type: text/markdown
25
+
26
+ # typed-agent-hooks
27
+
28
+ `typed-agent-hooks` defines executable, code-first hooks for OpenAI Codex and Anthropic Claude Code. A hook's Python file owns its handlers, provider configuration, dependency environment, and executable entry point. There is no TOML manifest, import-string loader, or global installation CLI.
29
+
30
+ The package provides strict provider wire models, a conservative shared semantic API, preservation-oriented config reconciliation, ordered collections of independent hook executables, and an optional FastMCP bridge.
31
+
32
+ ## One executable hook
33
+
34
+ Put the dependencies in the executable with PEP 723 and define configuration beside the handler:
35
+
36
+ ```python
37
+ #!/usr/bin/env -S uv run --script
38
+ # /// script
39
+ # requires-python = ">=3.11"
40
+ # dependencies = [
41
+ # "cyclopts>=4.23.2,<5",
42
+ # "pydantic-core",
43
+ # "typed-agent-hooks==0.1.0",
44
+ # ]
45
+ # ///
46
+ """Add local project context to every submitted prompt."""
47
+
48
+ import json
49
+ from functools import partial
50
+
51
+ from cyclopts import App
52
+ from pydantic_core import to_jsonable_python
53
+ from typed_agent_hooks import shared
54
+
55
+ cli = App(result_action=[partial(json.dumps, default=to_jsonable_python, allow_nan=False), print, "return_zero"])
56
+ hooks = shared.HookApp(name="project-context")
57
+
58
+
59
+ @hooks.on(shared.events.PromptSubmitted, timeout=10)
60
+ def add_project_context(event: shared.events.PromptSubmitted) -> shared.outputs.Result:
61
+ """Return context derived from one submitted prompt."""
62
+
63
+ return shared.outputs.AddContext(text=f"Working directory: {event.context.cwd}")
64
+
65
+
66
+ @cli.command
67
+ def preview(prompt: str) -> str:
68
+ """Preview the domain behavior without constructing a provider payload."""
69
+
70
+ return f"Would add context for {prompt!r}"
71
+
72
+
73
+ if __name__ == "__main__":
74
+ hooks.main(cli)
75
+ ```
76
+
77
+ Create and commit the adjacent script lockfile with `uv lock --script project_context.py`. Pin the desired TAH release in the PEP 723 declaration and refresh the lock when intentionally updating it; ordinary hook invocations then reuse the resolved installation.
78
+
79
+ Make the file executable and run its ordinary domain CLI directly:
80
+
81
+ ```console
82
+ ./project_context.py preview "fix the tests"
83
+ ```
84
+
85
+ `hooks.main(cli)` leaves ordinary arguments untouched for Cyclopts. Generated provider commands use a private `_typed-agent-hooks` protocol to invoke the same `hooks` object. That protocol is an implementation boundary, not a user-facing CLI.
86
+
87
+ The Python API is direct:
88
+
89
+ ```python
90
+ tool = ipi.import_path("/path/to/project_context.py")
91
+ tool.preview("fix the tests")
92
+ tool.hooks.render("codex", executable=tool.__file__)
93
+ ```
94
+
95
+ Keep docstrings on public functions. Cyclopts uses them for CLI help, and notebook callers get the same documentation from Python.
96
+
97
+ ## Registration metadata
98
+
99
+ `HookApp.on` accepts shared defaults plus explicit provider-only overrides:
100
+
101
+ ```python
102
+ @hooks.on(
103
+ shared.events.ToolCallProposed,
104
+ timeout=20,
105
+ status_message="Checking tool call",
106
+ codex=shared.CodexOptions(matcher="Bash"),
107
+ claude_code=shared.ClaudeCodeOptions(matcher="Bash|Read"),
108
+ )
109
+ def check_tool(event: shared.events.ToolCallProposed) -> shared.outputs.Result:
110
+ ...
111
+ ```
112
+
113
+ An app declares its enabled providers when necessary:
114
+
115
+ ```python
116
+ hooks = shared.HookApp(name="codex-only", providers=("codex",))
117
+ ```
118
+
119
+ Shared events that do not exist on an enabled provider fail during rendering. Provider-specific options on a disabled provider also fail. The library does not guess a lossy translation.
120
+
121
+ For direct programmatic installation, call `HookApp.install` or `HookApp.uninstall`. Pass the executable explicitly so the Python call has the same information as the provider config:
122
+
123
+ ```python
124
+ hooks.install(executable=__file__, provider="all", scope="project")
125
+ ```
126
+
127
+ Installation replaces only commands marked with the app's stable name, preserves unrelated JSON fields and hooks, writes atomically, and reconciles every selected provider. Disabling a provider therefore removes that app's stale entries from the provider config.
128
+
129
+ ## Ordered collections
130
+
131
+ Use a small executable installer when several hooks have independent dependencies or ownership:
132
+
133
+ ```python
134
+ #!/usr/bin/env -S uv run --script
135
+ # /// script
136
+ # requires-python = ">=3.11"
137
+ # dependencies = [
138
+ # "typed-agent-hooks==0.1.0",
139
+ # ]
140
+ # ///
141
+ """Install this repository's hooks in deterministic order."""
142
+
143
+ from pathlib import Path
144
+
145
+ from typed_agent_hooks import Collection
146
+
147
+ root = Path(__file__).parent
148
+ hooks = Collection(
149
+ name="project-hooks",
150
+ apps=(root / "first.py", root / "second.py"),
151
+ )
152
+
153
+ if __name__ == "__main__":
154
+ hooks.main()
155
+ ```
156
+
157
+ The CLI and Python API are the same bound methods:
158
+
159
+ ```console
160
+ ./install.py install --scope project
161
+ ./install.py uninstall --scope project
162
+ ```
163
+
164
+ ```python
165
+ installer = ipi.import_path("/path/to/install.py")
166
+ installer.hooks.install(scope="project")
167
+ ```
168
+
169
+ Collection installation executes each child in its own PEP 723 environment, validates every description before writing anything, preserves declared order, rejects duplicate names and paths, and removes members deleted from the collection.
170
+
171
+ ## FastMCP forwarding
172
+
173
+ `typed_agent_hooks.fastmcp.attach` connects a running FastMCP server to a normal hook application. `ForwardingHooks` installs one forwarding command for every native event supported by each provider:
174
+
175
+ ```python
176
+ from typed_agent_hooks.fastmcp import ForwardingHooks, attach
177
+
178
+ attach(server, hooks, provider="codex", server_name="ipi")
179
+
180
+ forwarding = ForwardingHooks(
181
+ name="ipi",
182
+ server_name="ipi",
183
+ timeout=70,
184
+ startup_wait=30,
185
+ response_timeout=35,
186
+ )
187
+ forwarding.install(provider="all", scope="user")
188
+ ```
189
+
190
+ The dedicated `tah-fastmcp-forward` entry point is also a direct Cyclopts view of the importable `forward` function:
191
+
192
+ ```console
193
+ tah-fastmcp-forward - --provider codex --server-name ipi
194
+ ```
195
+
196
+ ```python
197
+ from typed_agent_hooks.fastmcp import forward
198
+
199
+ output = forward(payload, provider="codex", server_name="ipi")
200
+ ```
201
+
202
+ `-` means stdin only at the CLI boundary. The forwarder is fail-open: an absent, slow, dead, unsupported, or ambiguous local bridge returns no output and does not block the harness. Invalid explicit timeout arguments still fail before forwarding.
203
+
204
+ The bridge requires the `fastmcp` extra. The forwarding subprocess itself imports Cyclopts and the small TAH rendezvous modules, but does not import FastMCP.
205
+
206
+ ## Provider schemas
207
+
208
+ Provider-native schemas remain available from `typed_agent_hooks.codex` and `typed_agent_hooks.claude_code`. Wire inputs are tolerant readers: unknown provider fields are ignored while declared fields remain strictly typed. Outputs are closed and exact. Use the shared API when one semantic handler is valid for both providers; use the provider-native models when their behavior genuinely differs.
209
+
210
+ ## Development
211
+
212
+ ```console
213
+ uv run ruff check .
214
+ uv run ty check
215
+ uv run pytest -q
216
+ ```
@@ -0,0 +1,191 @@
1
+ # typed-agent-hooks
2
+
3
+ `typed-agent-hooks` defines executable, code-first hooks for OpenAI Codex and Anthropic Claude Code. A hook's Python file owns its handlers, provider configuration, dependency environment, and executable entry point. There is no TOML manifest, import-string loader, or global installation CLI.
4
+
5
+ The package provides strict provider wire models, a conservative shared semantic API, preservation-oriented config reconciliation, ordered collections of independent hook executables, and an optional FastMCP bridge.
6
+
7
+ ## One executable hook
8
+
9
+ Put the dependencies in the executable with PEP 723 and define configuration beside the handler:
10
+
11
+ ```python
12
+ #!/usr/bin/env -S uv run --script
13
+ # /// script
14
+ # requires-python = ">=3.11"
15
+ # dependencies = [
16
+ # "cyclopts>=4.23.2,<5",
17
+ # "pydantic-core",
18
+ # "typed-agent-hooks==0.1.0",
19
+ # ]
20
+ # ///
21
+ """Add local project context to every submitted prompt."""
22
+
23
+ import json
24
+ from functools import partial
25
+
26
+ from cyclopts import App
27
+ from pydantic_core import to_jsonable_python
28
+ from typed_agent_hooks import shared
29
+
30
+ cli = App(result_action=[partial(json.dumps, default=to_jsonable_python, allow_nan=False), print, "return_zero"])
31
+ hooks = shared.HookApp(name="project-context")
32
+
33
+
34
+ @hooks.on(shared.events.PromptSubmitted, timeout=10)
35
+ def add_project_context(event: shared.events.PromptSubmitted) -> shared.outputs.Result:
36
+ """Return context derived from one submitted prompt."""
37
+
38
+ return shared.outputs.AddContext(text=f"Working directory: {event.context.cwd}")
39
+
40
+
41
+ @cli.command
42
+ def preview(prompt: str) -> str:
43
+ """Preview the domain behavior without constructing a provider payload."""
44
+
45
+ return f"Would add context for {prompt!r}"
46
+
47
+
48
+ if __name__ == "__main__":
49
+ hooks.main(cli)
50
+ ```
51
+
52
+ Create and commit the adjacent script lockfile with `uv lock --script project_context.py`. Pin the desired TAH release in the PEP 723 declaration and refresh the lock when intentionally updating it; ordinary hook invocations then reuse the resolved installation.
53
+
54
+ Make the file executable and run its ordinary domain CLI directly:
55
+
56
+ ```console
57
+ ./project_context.py preview "fix the tests"
58
+ ```
59
+
60
+ `hooks.main(cli)` leaves ordinary arguments untouched for Cyclopts. Generated provider commands use a private `_typed-agent-hooks` protocol to invoke the same `hooks` object. That protocol is an implementation boundary, not a user-facing CLI.
61
+
62
+ The Python API is direct:
63
+
64
+ ```python
65
+ tool = ipi.import_path("/path/to/project_context.py")
66
+ tool.preview("fix the tests")
67
+ tool.hooks.render("codex", executable=tool.__file__)
68
+ ```
69
+
70
+ Keep docstrings on public functions. Cyclopts uses them for CLI help, and notebook callers get the same documentation from Python.
71
+
72
+ ## Registration metadata
73
+
74
+ `HookApp.on` accepts shared defaults plus explicit provider-only overrides:
75
+
76
+ ```python
77
+ @hooks.on(
78
+ shared.events.ToolCallProposed,
79
+ timeout=20,
80
+ status_message="Checking tool call",
81
+ codex=shared.CodexOptions(matcher="Bash"),
82
+ claude_code=shared.ClaudeCodeOptions(matcher="Bash|Read"),
83
+ )
84
+ def check_tool(event: shared.events.ToolCallProposed) -> shared.outputs.Result:
85
+ ...
86
+ ```
87
+
88
+ An app declares its enabled providers when necessary:
89
+
90
+ ```python
91
+ hooks = shared.HookApp(name="codex-only", providers=("codex",))
92
+ ```
93
+
94
+ Shared events that do not exist on an enabled provider fail during rendering. Provider-specific options on a disabled provider also fail. The library does not guess a lossy translation.
95
+
96
+ For direct programmatic installation, call `HookApp.install` or `HookApp.uninstall`. Pass the executable explicitly so the Python call has the same information as the provider config:
97
+
98
+ ```python
99
+ hooks.install(executable=__file__, provider="all", scope="project")
100
+ ```
101
+
102
+ Installation replaces only commands marked with the app's stable name, preserves unrelated JSON fields and hooks, writes atomically, and reconciles every selected provider. Disabling a provider therefore removes that app's stale entries from the provider config.
103
+
104
+ ## Ordered collections
105
+
106
+ Use a small executable installer when several hooks have independent dependencies or ownership:
107
+
108
+ ```python
109
+ #!/usr/bin/env -S uv run --script
110
+ # /// script
111
+ # requires-python = ">=3.11"
112
+ # dependencies = [
113
+ # "typed-agent-hooks==0.1.0",
114
+ # ]
115
+ # ///
116
+ """Install this repository's hooks in deterministic order."""
117
+
118
+ from pathlib import Path
119
+
120
+ from typed_agent_hooks import Collection
121
+
122
+ root = Path(__file__).parent
123
+ hooks = Collection(
124
+ name="project-hooks",
125
+ apps=(root / "first.py", root / "second.py"),
126
+ )
127
+
128
+ if __name__ == "__main__":
129
+ hooks.main()
130
+ ```
131
+
132
+ The CLI and Python API are the same bound methods:
133
+
134
+ ```console
135
+ ./install.py install --scope project
136
+ ./install.py uninstall --scope project
137
+ ```
138
+
139
+ ```python
140
+ installer = ipi.import_path("/path/to/install.py")
141
+ installer.hooks.install(scope="project")
142
+ ```
143
+
144
+ Collection installation executes each child in its own PEP 723 environment, validates every description before writing anything, preserves declared order, rejects duplicate names and paths, and removes members deleted from the collection.
145
+
146
+ ## FastMCP forwarding
147
+
148
+ `typed_agent_hooks.fastmcp.attach` connects a running FastMCP server to a normal hook application. `ForwardingHooks` installs one forwarding command for every native event supported by each provider:
149
+
150
+ ```python
151
+ from typed_agent_hooks.fastmcp import ForwardingHooks, attach
152
+
153
+ attach(server, hooks, provider="codex", server_name="ipi")
154
+
155
+ forwarding = ForwardingHooks(
156
+ name="ipi",
157
+ server_name="ipi",
158
+ timeout=70,
159
+ startup_wait=30,
160
+ response_timeout=35,
161
+ )
162
+ forwarding.install(provider="all", scope="user")
163
+ ```
164
+
165
+ The dedicated `tah-fastmcp-forward` entry point is also a direct Cyclopts view of the importable `forward` function:
166
+
167
+ ```console
168
+ tah-fastmcp-forward - --provider codex --server-name ipi
169
+ ```
170
+
171
+ ```python
172
+ from typed_agent_hooks.fastmcp import forward
173
+
174
+ output = forward(payload, provider="codex", server_name="ipi")
175
+ ```
176
+
177
+ `-` means stdin only at the CLI boundary. The forwarder is fail-open: an absent, slow, dead, unsupported, or ambiguous local bridge returns no output and does not block the harness. Invalid explicit timeout arguments still fail before forwarding.
178
+
179
+ The bridge requires the `fastmcp` extra. The forwarding subprocess itself imports Cyclopts and the small TAH rendezvous modules, but does not import FastMCP.
180
+
181
+ ## Provider schemas
182
+
183
+ Provider-native schemas remain available from `typed_agent_hooks.codex` and `typed_agent_hooks.claude_code`. Wire inputs are tolerant readers: unknown provider fields are ignored while declared fields remain strictly typed. Outputs are closed and exact. Use the shared API when one semantic handler is valid for both providers; use the provider-native models when their behavior genuinely differs.
184
+
185
+ ## Development
186
+
187
+ ```console
188
+ uv run ruff check .
189
+ uv run ty check
190
+ uv run pytest -q
191
+ ```
@@ -0,0 +1,89 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "typed-agent-hooks"
7
+ version = "0.1.0"
8
+ description = "Code-first executable hooks and strict schemas for Codex and Claude Code."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = [
14
+ "cyclopts>=4.23.2,<5",
15
+ "pydantic>=2.8,<3",
16
+ "pydantic-core",
17
+ ]
18
+ keywords = [
19
+ "codex",
20
+ "claude-code",
21
+ "hooks",
22
+ "pydantic",
23
+ "schemas",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Typing :: Typed",
33
+ ]
34
+
35
+ [[project.authors]]
36
+ name = "Nima Shoghi"
37
+ email = "nimashoghi@gmail.com"
38
+
39
+ [project.optional-dependencies]
40
+ fastmcp = ["fastmcp>=3.3,<3.4"]
41
+
42
+ [project.urls]
43
+ Repository = "https://github.com/nimashoghi/typed-agent-hooks"
44
+
45
+ [project.scripts]
46
+ tah-fastmcp-forward = "typed_agent_hooks.fastmcp.shim:app"
47
+
48
+ [dependency-groups]
49
+ dev = [
50
+ "pytest>=8",
51
+ "ruff>=0.15",
52
+ "ty>=0.0.50,<0.1",
53
+ "fastmcp>=3.3,<3.4",
54
+ ]
55
+
56
+ [tool.pytest.ini_options]
57
+ pythonpath = ["src"]
58
+ testpaths = ["tests"]
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ src = [
63
+ "src",
64
+ "tests",
65
+ ]
66
+
67
+ [tool.ruff.lint]
68
+ select = [
69
+ "E",
70
+ "F",
71
+ "I",
72
+ "UP",
73
+ "B",
74
+ "SIM",
75
+ ]
76
+
77
+ [tool.ty.environment]
78
+ python-version = "3.10"
79
+ python-platform = "linux"
80
+ root = ["./src"]
81
+
82
+ [tool.ty.src]
83
+ include = [
84
+ "src",
85
+ "tests",
86
+ ]
87
+
88
+ [tool.ty.terminal]
89
+ output-format = "concise"
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "typed-agent-hooks"
7
+ version = "0.1.0"
8
+ description = "Code-first executable hooks and strict schemas for Codex and Claude Code."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Nima Shoghi", email = "nimashoghi@gmail.com" }]
14
+ dependencies = [
15
+ "cyclopts>=4.23.2,<5",
16
+ "pydantic>=2.8,<3",
17
+ "pydantic-core",
18
+ ]
19
+ keywords = ["codex", "claude-code", "hooks", "pydantic", "schemas"]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Typing :: Typed",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ # Server-side FastMCP bridge (HookBridge.attach). The forwarding process does
32
+ # not require this extra.
33
+ fastmcp = ["fastmcp>=3.3,<3.4"]
34
+
35
+ [project.urls]
36
+ Repository = "https://github.com/nimashoghi/typed-agent-hooks"
37
+
38
+ [project.scripts]
39
+ tah-fastmcp-forward = "typed_agent_hooks.fastmcp.shim:app"
40
+
41
+ [dependency-groups]
42
+ dev = ["pytest>=8", "ruff>=0.15", "ty>=0.0.50,<0.1", "fastmcp>=3.3,<3.4"]
43
+
44
+ [tool.pytest.ini_options]
45
+ pythonpath = ["src"]
46
+ testpaths = ["tests"]
47
+
48
+ [tool.ruff]
49
+ line-length = 100
50
+ src = ["src", "tests"]
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "I", "UP", "B", "SIM"]
54
+
55
+ [tool.ty.environment]
56
+ python-version = "3.10"
57
+ # rendezvous targets Linux APIs (geteuid/AF_UNIX/SO_PEERCRED); without the pin
58
+ # ty infers the host OS and flags them on Windows/macOS dev machines.
59
+ python-platform = "linux"
60
+ root = ["./src"]
61
+
62
+ [tool.ty.src]
63
+ include = ["src", "tests"]
64
+
65
+ [tool.ty.terminal]
66
+ output-format = "concise"
@@ -0,0 +1,28 @@
1
+ """Code-first hook APIs for Codex, Claude Code, and shared semantics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import import_module
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ from . import claude_code, codex, shared
10
+ from .collection import Collection
11
+
12
+ __all__ = ["Collection", "claude_code", "codex", "shared"]
13
+ __version__ = "0.1.0"
14
+
15
+
16
+ def __getattr__(name: str) -> Any:
17
+ """Load public surfaces only when a caller selects them."""
18
+
19
+ if name == "Collection":
20
+ from .collection import Collection
21
+
22
+ globals()[name] = Collection
23
+ return Collection
24
+ if name in {"claude_code", "codex", "shared"}:
25
+ module = import_module(f"{__name__}.{name}")
26
+ globals()[name] = module
27
+ return module
28
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,15 @@
1
+ """Claude Code-specific schemas, outputs, and configuration."""
2
+
3
+ from . import config, events, outputs
4
+ from .events import EVENT_NAMES, INPUT_ADAPTER, parse_input
5
+ from .outputs import render_output
6
+
7
+ __all__ = [
8
+ "EVENT_NAMES",
9
+ "INPUT_ADAPTER",
10
+ "config",
11
+ "events",
12
+ "outputs",
13
+ "parse_input",
14
+ "render_output",
15
+ ]