clipwright 0.1.1__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,194 @@
1
+ Metadata-Version: 2.3
2
+ Name: clipwright
3
+ Version: 0.1.1
4
+ Summary: MCP server group wrapping FFmpeg/OTIO. Provides primitives to manipulate video editing workflows from AI agents.
5
+ Author: satoh-y-0323
6
+ Author-email: satoh-y-0323 <shoma.papa.0323@gmail.com>
7
+ License: MIT
8
+ Requires-Dist: mcp[cli]>=1.27.2
9
+ Requires-Dist: opentimelineio>=0.18
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Clipwright
15
+
16
+ > For Japanese, see [README.ja.md](README.ja.md).
17
+
18
+ MCP server group wrapping FFmpeg/OTIO. Provides primitives to manipulate video editing workflows from AI agents.
19
+
20
+ ## Prerequisite: FFmpeg
21
+
22
+ Clipwright requires ffprobe (runtime) and ffmpeg (test fixture generation) on PATH. Binaries are not included.
23
+
24
+ ### Installation (Windows / WinGet)
25
+
26
+ ```bash
27
+ winget install Gyan.FFmpeg
28
+ ```
29
+
30
+ **PATH takes effect after shell restart.** When using with Claude Code, restart the app for PATH to become active.
31
+
32
+ If you cannot wait for a restart, specify environment variables directly:
33
+
34
+ ```bash
35
+ # runtime: ffprobe only
36
+ export CLIPWRIGHT_FFPROBE="C:/Users/<user>/AppData/Local/Microsoft/WinGet/Packages/Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe/ffmpeg-8.1.1-full_build/bin/ffprobe.exe"
37
+
38
+ # test: both ffmpeg + ffprobe (for test fixture generation)
39
+ export CLIPWRIGHT_FFMPEG="C:/Users/<user>/AppData/Local/Microsoft/WinGet/Packages/Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe/ffmpeg-8.1.1-full_build/bin/ffmpeg.exe"
40
+ ```
41
+
42
+ ### Environment Variable Usage
43
+
44
+ | Variable | Purpose |
45
+ |----------|---------|
46
+ | `CLIPWRIGHT_FFPROBE` | **Runtime only**. Used by the `clipwright_inspect_media` tool |
47
+ | `CLIPWRIGHT_FFMPEG` | **Test only**. Used by the `sample_media` fixture in `conftest.py` |
48
+
49
+ > Runtime depends only on ffprobe. ffmpeg is used only for test fixture generation (design: [DC-AM-008]).
50
+
51
+ ---
52
+
53
+ ## Development Environment Setup
54
+
55
+ ```bash
56
+ # Install dependencies
57
+ uv sync --dev
58
+
59
+ # Run tests (with coverage)
60
+ uv run pytest --cov=clipwright --cov-report=term-missing
61
+
62
+ # lint / format
63
+ uv run ruff check src tests
64
+ uv run ruff format src tests
65
+
66
+ # Type checking
67
+ uv run mypy src
68
+ ```
69
+
70
+ ### Integration Test Prerequisites
71
+
72
+ To run integration tests (tests that actually invoke ffprobe/ffmpeg), ffmpeg / ffprobe must exist on PATH or the following environment variables must be set:
73
+
74
+ ```bash
75
+ # Specify path to ffprobe (used by runtime and integration tests)
76
+ export CLIPWRIGHT_FFPROBE="/path/to/ffprobe"
77
+
78
+ # Specify path to ffmpeg (used for test fixture generation)
79
+ export CLIPWRIGHT_FFMPEG="/path/to/ffmpeg"
80
+ ```
81
+
82
+ If ffmpeg / ffprobe are already registered in PATH, setting environment variables is not required. If neither is found, integration tests are automatically skipped.
83
+
84
+ ---
85
+
86
+ ## Development Notes: MCP Package
87
+
88
+ ### Adopted Package
89
+
90
+ **Official MCP Python SDK** (`mcp[cli]`) is adopted (ADR-5 confirmed).
91
+
92
+ ```
93
+ mcp[cli]>=1.27.2
94
+ ```
95
+
96
+ Importable via `from mcp.server.fastmcp import FastMCP`. Verified to work on Python 3.11 / Windows.
97
+
98
+ ### Annotation Syntax (Adopted Version)
99
+
100
+ ```python
101
+ from mcp.server.fastmcp import FastMCP
102
+ from mcp.types import ToolAnnotations
103
+
104
+ mcp = FastMCP("clipwright")
105
+
106
+ @mcp.tool(
107
+ annotations=ToolAnnotations(
108
+ readOnlyHint=True,
109
+ destructiveHint=False,
110
+ idempotentHint=True,
111
+ openWorldHint=False,
112
+ )
113
+ )
114
+ def clipwright_inspect_media(path: str) -> dict:
115
+ """Probe a media file and return its information."""
116
+ ...
117
+ ```
118
+
119
+ `ToolAnnotations` fields: `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`
120
+
121
+ ### outputSchema / structured_output
122
+
123
+ When `mcp.tool(structured_output=True)` is specified, Pydantic model return values are reflected in outputSchema as JSON Schema.
124
+
125
+ ```python
126
+ from pydantic import BaseModel
127
+
128
+ class MediaResult(BaseModel):
129
+ ok: bool
130
+ summary: str
131
+
132
+ @mcp.tool(structured_output=True)
133
+ def clipwright_inspect_media(path: str) -> MediaResult:
134
+ ...
135
+ ```
136
+
137
+ ---
138
+
139
+ ## MCP Inspector Communication Procedure
140
+
141
+ How to manually verify the server using MCP Inspector (`@modelcontextprotocol/inspector`).
142
+
143
+ ### Setup (Node.js Required)
144
+
145
+ ```bash
146
+ # Verify Node.js is installed
147
+ node --version
148
+ npx --version
149
+ ```
150
+
151
+ ### Starting the Server and Connecting
152
+
153
+ ```bash
154
+ # Start MCP Inspector and connect the server via stdio
155
+ npx @modelcontextprotocol/inspector uv run python -m clipwright.server
156
+ ```
157
+
158
+ Browser opens automatically at `http://localhost:5173` (or access manually).
159
+
160
+ The tool list (`clipwright_init_project` / `clipwright_inspect_media` / `clipwright_read_timeline` / `clipwright_write_timeline`) appears in Inspector, and you can manually execute each tool.
161
+
162
+ ### Expected Behavior
163
+
164
+ - 4 tools appear in the tool list
165
+ - Passing a non-existent path to `clipwright_inspect_media` returns an error envelope with `ok=false`
166
+ - If ffprobe is not set in PATH / environment variables, a `DEPENDENCY_MISSING` error is returned
167
+
168
+ ---
169
+
170
+ ## Architecture Overview
171
+
172
+ ```
173
+ src/clipwright/
174
+ __init__.py # Version definition
175
+ schemas.py # Shared Pydantic types (contract surface)
176
+ envelope.py # Return value envelope + error formatting
177
+ errors.py # Error codes + ClipwrightError exception
178
+ process.py # Subprocess runner (shell=False / timeout required)
179
+ media.py # ffprobe wrapper
180
+ otio_utils.py # OTIO helpers
181
+ operations.py # Declarative edit operation types + application logic
182
+ project.py # Project directory management
183
+ server.py # FastMCP server (4 tools exposed)
184
+ ```
185
+
186
+ Dependency direction: `schemas / envelope / errors` (contract surface) → `process / media / otio_utils / project` → `operations` → `server`
187
+
188
+ For details, see [docs/clipwright-spec.md](docs/clipwright-spec.md).
189
+
190
+ ---
191
+
192
+ ## License
193
+
194
+ MIT — See [LICENSE](LICENSE) for details.
@@ -0,0 +1,181 @@
1
+ # Clipwright
2
+
3
+ > For Japanese, see [README.ja.md](README.ja.md).
4
+
5
+ MCP server group wrapping FFmpeg/OTIO. Provides primitives to manipulate video editing workflows from AI agents.
6
+
7
+ ## Prerequisite: FFmpeg
8
+
9
+ Clipwright requires ffprobe (runtime) and ffmpeg (test fixture generation) on PATH. Binaries are not included.
10
+
11
+ ### Installation (Windows / WinGet)
12
+
13
+ ```bash
14
+ winget install Gyan.FFmpeg
15
+ ```
16
+
17
+ **PATH takes effect after shell restart.** When using with Claude Code, restart the app for PATH to become active.
18
+
19
+ If you cannot wait for a restart, specify environment variables directly:
20
+
21
+ ```bash
22
+ # runtime: ffprobe only
23
+ export CLIPWRIGHT_FFPROBE="C:/Users/<user>/AppData/Local/Microsoft/WinGet/Packages/Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe/ffmpeg-8.1.1-full_build/bin/ffprobe.exe"
24
+
25
+ # test: both ffmpeg + ffprobe (for test fixture generation)
26
+ export CLIPWRIGHT_FFMPEG="C:/Users/<user>/AppData/Local/Microsoft/WinGet/Packages/Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe/ffmpeg-8.1.1-full_build/bin/ffmpeg.exe"
27
+ ```
28
+
29
+ ### Environment Variable Usage
30
+
31
+ | Variable | Purpose |
32
+ |----------|---------|
33
+ | `CLIPWRIGHT_FFPROBE` | **Runtime only**. Used by the `clipwright_inspect_media` tool |
34
+ | `CLIPWRIGHT_FFMPEG` | **Test only**. Used by the `sample_media` fixture in `conftest.py` |
35
+
36
+ > Runtime depends only on ffprobe. ffmpeg is used only for test fixture generation (design: [DC-AM-008]).
37
+
38
+ ---
39
+
40
+ ## Development Environment Setup
41
+
42
+ ```bash
43
+ # Install dependencies
44
+ uv sync --dev
45
+
46
+ # Run tests (with coverage)
47
+ uv run pytest --cov=clipwright --cov-report=term-missing
48
+
49
+ # lint / format
50
+ uv run ruff check src tests
51
+ uv run ruff format src tests
52
+
53
+ # Type checking
54
+ uv run mypy src
55
+ ```
56
+
57
+ ### Integration Test Prerequisites
58
+
59
+ To run integration tests (tests that actually invoke ffprobe/ffmpeg), ffmpeg / ffprobe must exist on PATH or the following environment variables must be set:
60
+
61
+ ```bash
62
+ # Specify path to ffprobe (used by runtime and integration tests)
63
+ export CLIPWRIGHT_FFPROBE="/path/to/ffprobe"
64
+
65
+ # Specify path to ffmpeg (used for test fixture generation)
66
+ export CLIPWRIGHT_FFMPEG="/path/to/ffmpeg"
67
+ ```
68
+
69
+ If ffmpeg / ffprobe are already registered in PATH, setting environment variables is not required. If neither is found, integration tests are automatically skipped.
70
+
71
+ ---
72
+
73
+ ## Development Notes: MCP Package
74
+
75
+ ### Adopted Package
76
+
77
+ **Official MCP Python SDK** (`mcp[cli]`) is adopted (ADR-5 confirmed).
78
+
79
+ ```
80
+ mcp[cli]>=1.27.2
81
+ ```
82
+
83
+ Importable via `from mcp.server.fastmcp import FastMCP`. Verified to work on Python 3.11 / Windows.
84
+
85
+ ### Annotation Syntax (Adopted Version)
86
+
87
+ ```python
88
+ from mcp.server.fastmcp import FastMCP
89
+ from mcp.types import ToolAnnotations
90
+
91
+ mcp = FastMCP("clipwright")
92
+
93
+ @mcp.tool(
94
+ annotations=ToolAnnotations(
95
+ readOnlyHint=True,
96
+ destructiveHint=False,
97
+ idempotentHint=True,
98
+ openWorldHint=False,
99
+ )
100
+ )
101
+ def clipwright_inspect_media(path: str) -> dict:
102
+ """Probe a media file and return its information."""
103
+ ...
104
+ ```
105
+
106
+ `ToolAnnotations` fields: `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`
107
+
108
+ ### outputSchema / structured_output
109
+
110
+ When `mcp.tool(structured_output=True)` is specified, Pydantic model return values are reflected in outputSchema as JSON Schema.
111
+
112
+ ```python
113
+ from pydantic import BaseModel
114
+
115
+ class MediaResult(BaseModel):
116
+ ok: bool
117
+ summary: str
118
+
119
+ @mcp.tool(structured_output=True)
120
+ def clipwright_inspect_media(path: str) -> MediaResult:
121
+ ...
122
+ ```
123
+
124
+ ---
125
+
126
+ ## MCP Inspector Communication Procedure
127
+
128
+ How to manually verify the server using MCP Inspector (`@modelcontextprotocol/inspector`).
129
+
130
+ ### Setup (Node.js Required)
131
+
132
+ ```bash
133
+ # Verify Node.js is installed
134
+ node --version
135
+ npx --version
136
+ ```
137
+
138
+ ### Starting the Server and Connecting
139
+
140
+ ```bash
141
+ # Start MCP Inspector and connect the server via stdio
142
+ npx @modelcontextprotocol/inspector uv run python -m clipwright.server
143
+ ```
144
+
145
+ Browser opens automatically at `http://localhost:5173` (or access manually).
146
+
147
+ The tool list (`clipwright_init_project` / `clipwright_inspect_media` / `clipwright_read_timeline` / `clipwright_write_timeline`) appears in Inspector, and you can manually execute each tool.
148
+
149
+ ### Expected Behavior
150
+
151
+ - 4 tools appear in the tool list
152
+ - Passing a non-existent path to `clipwright_inspect_media` returns an error envelope with `ok=false`
153
+ - If ffprobe is not set in PATH / environment variables, a `DEPENDENCY_MISSING` error is returned
154
+
155
+ ---
156
+
157
+ ## Architecture Overview
158
+
159
+ ```
160
+ src/clipwright/
161
+ __init__.py # Version definition
162
+ schemas.py # Shared Pydantic types (contract surface)
163
+ envelope.py # Return value envelope + error formatting
164
+ errors.py # Error codes + ClipwrightError exception
165
+ process.py # Subprocess runner (shell=False / timeout required)
166
+ media.py # ffprobe wrapper
167
+ otio_utils.py # OTIO helpers
168
+ operations.py # Declarative edit operation types + application logic
169
+ project.py # Project directory management
170
+ server.py # FastMCP server (4 tools exposed)
171
+ ```
172
+
173
+ Dependency direction: `schemas / envelope / errors` (contract surface) → `process / media / otio_utils / project` → `operations` → `server`
174
+
175
+ For details, see [docs/clipwright-spec.md](docs/clipwright-spec.md).
176
+
177
+ ---
178
+
179
+ ## License
180
+
181
+ MIT — See [LICENSE](LICENSE) for details.
@@ -0,0 +1,80 @@
1
+ [project]
2
+ name = "clipwright"
3
+ version = "0.1.1"
4
+ description = "MCP server group wrapping FFmpeg/OTIO. Provides primitives to manipulate video editing workflows from AI agents."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ authors = [
8
+ { name = "satoh-y-0323", email = "shoma.papa.0323@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "mcp[cli]>=1.27.2",
13
+ "opentimelineio>=0.18",
14
+ "pydantic>=2",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.11.19,<0.12.0"]
19
+ build-backend = "uv_build"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "mypy>=2.1.0",
24
+ "pytest>=9.0.3",
25
+ "pytest-cov>=7.1.0",
26
+ "pytest-mock>=3.15.1",
27
+ "ruff>=0.15.16",
28
+ ]
29
+
30
+ # --- Ruff ---
31
+ [tool.ruff]
32
+ target-version = "py311"
33
+ line-length = 88
34
+ # templates/ is a template containing placeholders (__TOOL__ etc). Excluded from lint/format.
35
+ extend-exclude = ["templates"]
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM"]
39
+ ignore = []
40
+
41
+ [tool.ruff.format]
42
+ # Default ruff formatter is OK
43
+
44
+ # --- mypy ---
45
+ [tool.mypy]
46
+ python_version = "3.11"
47
+ strict = true
48
+ warn_return_any = true
49
+ warn_unused_configs = true
50
+ disallow_untyped_defs = true
51
+ disallow_any_generics = true
52
+ # templates/ is a template containing placeholders. Excluded from type checking.
53
+ exclude = ["^templates/"]
54
+
55
+ # opentimelineio has no stubs, ignored with mypy strict
56
+ [[tool.mypy.overrides]]
57
+ module = "opentimelineio.*"
58
+ ignore_missing_imports = true
59
+
60
+ # --- pytest ---
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+ addopts = "--strict-markers -q"
64
+ markers = [
65
+ "integration: integration test requiring actual ffmpeg/ffprobe binaries",
66
+ "slow: test with long execution time",
67
+ ]
68
+
69
+ # --- coverage ---
70
+ [tool.coverage.run]
71
+ source = ["clipwright"]
72
+ omit = ["tests/*"]
73
+
74
+ [tool.coverage.report]
75
+ show_missing = true
76
+ skip_covered = false
77
+
78
+ # --- uv workspace ---
79
+ [tool.uv.workspace]
80
+ members = ["clipwright-bgm", "clipwright-loudness", "clipwright-noise", "clipwright-render", "clipwright-silence", "clipwright-transcribe", "clipwright-wrap"]
@@ -0,0 +1,3 @@
1
+ """clipwright: Core library for the FFmpeg/OTIO MCP server suite."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,25 @@
1
+ """cli_io.py — Shared UTF-8 I/O helper for separate-process CLIs.
2
+
3
+ DRY home for the per-CLI UTF-8 pinning helper (CR L-2 / SR I-1). wrap_cli and
4
+ vad_cli import force_utf8_io() from here instead of carrying a local copy, so the
5
+ behaviour is defined once for every separate-process CLI that depends on core.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+
13
+ def force_utf8_io() -> None:
14
+ """Pin stdin/stdout to UTF-8 so this CLI is correct regardless of host
15
+ locale or inherited PYTHONIOENCODING (cp932 on JP Windows otherwise).
16
+
17
+ MUST be called before the first read from stdin: TextIOWrapper.reconfigure
18
+ raises once buffered reading has begun. Calling it at the very top of main()
19
+ (before sys.stdin.read()) satisfies this. Safe to call even on a CLI that
20
+ never reads stdin (reconfigure before any read is a no-op there).
21
+ """
22
+ for stream in (sys.stdin, sys.stdout):
23
+ reconfigure = getattr(stream, "reconfigure", None)
24
+ if reconfigure is not None:
25
+ reconfigure(encoding="utf-8")
@@ -0,0 +1,64 @@
1
+ """envelope.py — Return value envelope construction helpers.
2
+
3
+ Thin helpers that normalise all tool return values to the standard format (§6.3 / §6.4).
4
+ Returns dict representations of ToolResult / ToolErrorResult, which are directly
5
+ compatible with FastMCP JSON serialisation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+
13
+ def ok_result(
14
+ summary: str,
15
+ *,
16
+ data: dict[str, Any] | None = None,
17
+ artifacts: list[Any] | None = None,
18
+ warnings: list[str] | None = None,
19
+ ) -> dict[str, Any]:
20
+ """Build a success envelope dict (§6.3 ToolResult form).
21
+
22
+ summary must include key points an AI needs to decide the next action
23
+ (counts, durations, maxima, etc.). Do not make it minimal.
24
+
25
+ Args:
26
+ summary: Key points of the result (required).
27
+ data: Supplementary information (optional).
28
+ artifacts: List of references to output files (optional).
29
+ warnings: List of warning messages (optional).
30
+
31
+ Returns:
32
+ A dict of the form { ok: True, summary, data, artifacts, warnings }.
33
+ """
34
+ return {
35
+ "ok": True,
36
+ "summary": summary,
37
+ "data": data if data is not None else {},
38
+ "artifacts": artifacts if artifacts is not None else [],
39
+ "warnings": warnings if warnings is not None else [],
40
+ }
41
+
42
+
43
+ def error_result(code: str, message: str, hint: str) -> dict[str, Any]:
44
+ """Build a failure envelope dict (§6.4 ToolErrorResult form).
45
+
46
+ message describes what happened; hint describes the concrete next step.
47
+ An empty hint violates the error contract (§6).
48
+
49
+ Args:
50
+ code: String representation of an ErrorCode value.
51
+ message: What happened.
52
+ hint: Concrete, actionable next step.
53
+
54
+ Returns:
55
+ A dict of the form { ok: False, error: { code, message, hint } }.
56
+ """
57
+ return {
58
+ "ok": False,
59
+ "error": {
60
+ "code": code,
61
+ "message": message,
62
+ "hint": hint,
63
+ },
64
+ }
@@ -0,0 +1,62 @@
1
+ """errors.py — Error code taxonomy and ClipwrightError exception.
2
+
3
+ The library layer raises ClipwrightError on failure;
4
+ server.py converts it to error_result at the MCP boundary.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import StrEnum
10
+
11
+
12
+ class ErrorCode(StrEnum):
13
+ """Error codes shared across all Clipwright tools (§4 + §13.1 DC-AM-002/DC-AS-003).
14
+
15
+ Inherits str so values are JSON-serializable at API boundaries.
16
+ Each value equals its name string.
17
+ """
18
+
19
+ DEPENDENCY_MISSING = "DEPENDENCY_MISSING"
20
+ """External tool (ffmpeg/ffprobe, etc.) not found."""
21
+ INVALID_INPUT = "INVALID_INPUT"
22
+ """Argument validation failed."""
23
+ FILE_NOT_FOUND = "FILE_NOT_FOUND"
24
+ """No file exists at the given input path."""
25
+ PATH_NOT_ALLOWED = "PATH_NOT_ALLOWED"
26
+ """Path validation failed (e.g., path traversal attempt)."""
27
+ SUBPROCESS_FAILED = "SUBPROCESS_FAILED"
28
+ """External process exited with a non-zero return code."""
29
+ SUBPROCESS_TIMEOUT = "SUBPROCESS_TIMEOUT"
30
+ """External process timed out."""
31
+ PROBE_FAILED = "PROBE_FAILED"
32
+ """Failed to parse ffprobe output."""
33
+ OTIO_ERROR = "OTIO_ERROR"
34
+ """Failed to read, write, or parse an OTIO file."""
35
+ PROJECT_NOT_FOUND = "PROJECT_NOT_FOUND"
36
+ """clipwright.json not found."""
37
+ PROJECT_EXISTS = "PROJECT_EXISTS"
38
+ """An existing project already exists at the target init location."""
39
+ UNSUPPORTED_OPERATION = "UNSUPPORTED_OPERATION"
40
+ """Unknown or unsupported operation type."""
41
+ INTERNAL = "INTERNAL"
42
+ """Unexpected internal error (§13.1 DC-AM-002).
43
+
44
+ Use a generic message; expose stack traces only in hints/logs.
45
+ The hint must include a prompt to report with reproduction steps.
46
+ """
47
+ TRACK_NOT_FOUND = "TRACK_NOT_FOUND"
48
+ """The track index in operations exceeds the total track count (§13.1 DC-AS-003)."""
49
+
50
+
51
+ class ClipwrightError(Exception):
52
+ """Exception raised by the Clipwright library layer.
53
+
54
+ Always carries the three-part set: code / message / hint (§6.4 error contract).
55
+ hint must describe the concrete next action for the user or AI agent.
56
+ """
57
+
58
+ def __init__(self, code: ErrorCode, message: str, hint: str) -> None:
59
+ super().__init__(message)
60
+ self.code = code
61
+ self.message = message
62
+ self.hint = hint