tinta-mcp 0.2.2__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.
- tinta_mcp-0.2.2/.agents/AGENTS.md +60 -0
- tinta_mcp-0.2.2/.agents/rules/architecture.md +62 -0
- tinta_mcp-0.2.2/.agents/skills/mcp-tool-builder/SKILL.md +113 -0
- tinta_mcp-0.2.2/.agents/skills/tinta-designer/SKILL.md +56 -0
- tinta_mcp-0.2.2/.agents/skills/tinta-dev-workflow/SKILL.md +94 -0
- tinta_mcp-0.2.2/.editorconfig +18 -0
- tinta_mcp-0.2.2/.gitattributes +5 -0
- tinta_mcp-0.2.2/.github/actionlint.yaml +4 -0
- tinta_mcp-0.2.2/.github/workflows/ci.yml +194 -0
- tinta_mcp-0.2.2/.github/workflows/release.yml +319 -0
- tinta_mcp-0.2.2/.gitignore +34 -0
- tinta_mcp-0.2.2/.pre-commit-config.yaml +26 -0
- tinta_mcp-0.2.2/.python-version +1 -0
- tinta_mcp-0.2.2/LICENSE +661 -0
- tinta_mcp-0.2.2/PKG-INFO +167 -0
- tinta_mcp-0.2.2/README.md +158 -0
- tinta_mcp-0.2.2/assets/.gitkeep +1 -0
- tinta_mcp-0.2.2/assets/tinta-mcp-banner.png +0 -0
- tinta_mcp-0.2.2/assets/tinta-mcp-banner.svg +368 -0
- tinta_mcp-0.2.2/assets/tinta-mcp-logo.png +0 -0
- tinta_mcp-0.2.2/assets/tinta-mcp-logo.svg +225 -0
- tinta_mcp-0.2.2/docs/releasing.md +89 -0
- tinta_mcp-0.2.2/extension/tinta_mcp.inx +14 -0
- tinta_mcp-0.2.2/extension/tinta_mcp.py +671 -0
- tinta_mcp-0.2.2/extension/tinta_mcp_ops/__init__.py +1 -0
- tinta_mcp-0.2.2/extension/tinta_mcp_ops/common.py +103 -0
- tinta_mcp-0.2.2/extension/tinta_mcp_ops/document_operations.py +291 -0
- tinta_mcp-0.2.2/extension/tinta_mcp_ops/element_mapping.py +139 -0
- tinta_mcp-0.2.2/install_windows.bat +2 -0
- tinta_mcp-0.2.2/pyproject.toml +66 -0
- tinta_mcp-0.2.2/scripts/install_linux.sh +166 -0
- tinta_mcp-0.2.2/scripts/install_windows.bat +7 -0
- tinta_mcp-0.2.2/scripts/install_windows.ps1 +288 -0
- tinta_mcp-0.2.2/scripts/release.py +146 -0
- tinta_mcp-0.2.2/scripts/smoke_installed_package.py +46 -0
- tinta_mcp-0.2.2/scripts/verify_release_artifacts.py +92 -0
- tinta_mcp-0.2.2/tests/conftest.py +185 -0
- tinta_mcp-0.2.2/tests/inkex_cases.py +345 -0
- tinta_mcp-0.2.2/tests/mcp_worker.py +18 -0
- tinta_mcp-0.2.2/tests/test_dbus_smoke.py +253 -0
- tinta_mcp-0.2.2/tests/test_extension_handlers.py +31 -0
- tinta_mcp-0.2.2/tests/test_installer.py +18 -0
- tinta_mcp-0.2.2/tests/test_ipc_protocol.py +178 -0
- tinta_mcp-0.2.2/tests/test_live_safety.py +37 -0
- tinta_mcp-0.2.2/tests/test_preview_contracts.py +97 -0
- tinta_mcp-0.2.2/tests/test_release.py +144 -0
- tinta_mcp-0.2.2/tests/test_release_artifacts.py +60 -0
- tinta_mcp-0.2.2/tests/test_structured_operations.py +340 -0
- tinta_mcp-0.2.2/tests/test_tintamcp.py +187 -0
- tinta_mcp-0.2.2/tinta-designer-skill.zip +0 -0
- tinta_mcp-0.2.2/tintamcp/__init__.py +1 -0
- tinta_mcp-0.2.2/tintamcp/gdbus_util.py +186 -0
- tinta_mcp-0.2.2/tintamcp/installer.py +199 -0
- tinta_mcp-0.2.2/tintamcp/main.py +36 -0
- tinta_mcp-0.2.2/tintamcp/server.py +1189 -0
- tinta_mcp-0.2.2/uv.lock +2200 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Tinta MCP agent guide
|
|
2
|
+
|
|
3
|
+
Tinta is a Model Context Protocol (MCP) server that enables AI assistants to control running Inkscape instances in real time via D-Bus.
|
|
4
|
+
|
|
5
|
+
## Repository overview
|
|
6
|
+
|
|
7
|
+
- `tintamcp/`: Python MCP server package (`fastmcp`). Runs in the user's external Python environment, exposes tools, and translates requests into D-Bus calls.
|
|
8
|
+
- `extension/`: Inkscape extension (`tinta_mcp.py`, `tinta_mcp.inx`, and `tinta_mcp_ops/`). Runs inside Inkscape's internal Python interpreter when triggered by D-Bus.
|
|
9
|
+
- `tests/`: Automated unit tests and live D-Bus smoke tests using `pytest`.
|
|
10
|
+
- `scripts/`: Setup scripts for Windows (`install_windows.ps1`, `install_windows.bat`) and Linux/WSL (`install_linux.sh`).
|
|
11
|
+
- `.agents/`: Project rules, guidelines, and skills for AI assistants and contributors.
|
|
12
|
+
- `.github/workflows/`: CI matrix tests, security audits, headless D-Bus integration tests, and release packaging.
|
|
13
|
+
|
|
14
|
+
## Core rules and invariants
|
|
15
|
+
|
|
16
|
+
1. Runtime separation:
|
|
17
|
+
`tintamcp` must remain pure Python. Never add `inkex`, `lxml`, `pycairo`, or `pygobject` to `[project.dependencies]` in `pyproject.toml`. The `inkex` module is only imported inside `extension/` because it is provided by Inkscape at runtime.
|
|
18
|
+
2. D-Bus identifier:
|
|
19
|
+
The registered D-Bus action name is strictly `org.tinta.mcp`. Do not introduce backwards-compatibility aliases (`inkmcp`).
|
|
20
|
+
3. Cross-platform parity:
|
|
21
|
+
Support both Windows and Linux. On Windows, D-Bus calls use Inkscape's bundled `gdbus.exe`. On Linux, D-Bus calls use the system session bus `gdbus`. IPC lives under `%LOCALAPPDATA%\Temp\tintamcp\<user>` on Windows, or a verified private `$XDG_RUNTIME_DIR/tintamcp` with `/tmp/tintamcp-<uid>` fallback on Linux.
|
|
22
|
+
4. Security and sanitization:
|
|
23
|
+
Validate XML element and attribute names using `is_valid_xml_name()`. Escape attribute values properly before inserting into SVG trees. Python code execution and textual command tools are not supported. Never evaluate client-provided Python with `exec`/`eval` or resolve operation names to arbitrary Python callables. Use fixed extension handlers receiving structured data; operation names and SVG element tags are separate fields.
|
|
24
|
+
5. Code quality gates:
|
|
25
|
+
When modifying Python code (`extension/`, `tintamcp/`, `tests/`) or dependencies (`pyproject.toml`), verify that all quality gates pass:
|
|
26
|
+
```bash
|
|
27
|
+
uv run ruff check extension tintamcp tests
|
|
28
|
+
uv run ruff format --check extension tintamcp tests
|
|
29
|
+
uv run pyright
|
|
30
|
+
uv run pytest
|
|
31
|
+
uv run pip-audit
|
|
32
|
+
```
|
|
33
|
+
Pure documentation edits (such as updating `.md` files) do not require running Python test or lint gates.
|
|
34
|
+
|
|
35
|
+
## Technical prose and documentation style
|
|
36
|
+
|
|
37
|
+
When writing documentation, guides, commit messages, or agent responses:
|
|
38
|
+
- State facts directly without rhetorical staging (*not X but Y*, fake alternatives, or dramatic one-line closers).
|
|
39
|
+
- Use sentence case for headings, avoid decorative emojis, and do not divide sections with horizontal rules (`---`).
|
|
40
|
+
- Do not use em dashes (`—`) or double hyphens (`--`) as universal connectors. Use colons, commas, periods, or parentheses instead.
|
|
41
|
+
- Write list items as natural sentences without forcing a bold title and colon on every bullet.
|
|
42
|
+
- Maintain a neutral, factual tone without marketing filler or AI buzzwords (*robust*, *seamless*, *testament*, *pivotal*, *delve*, *cutting-edge*, *effortless*).
|
|
43
|
+
|
|
44
|
+
## Project rules and references
|
|
45
|
+
|
|
46
|
+
- Architecture and IPC boundaries: [rules/architecture.md](rules/architecture.md)
|
|
47
|
+
|
|
48
|
+
## Project skills
|
|
49
|
+
|
|
50
|
+
- `mcp-tool-builder` ([skills/mcp-tool-builder/SKILL.md](skills/mcp-tool-builder/SKILL.md)): Workflow for adding, modifying, and testing MCP tools for Inkscape.
|
|
51
|
+
- `tinta-designer` ([skills/tinta-designer/SKILL.md](skills/tinta-designer/SKILL.md)): Best practices and technical guidelines for creating and editing vector graphics in Inkscape via Tinta.
|
|
52
|
+
- `tinta-dev-workflow` ([skills/tinta-dev-workflow/SKILL.md](skills/tinta-dev-workflow/SKILL.md)): Development standards, `uv` environment management, Conventional Commits, and CI quality gates.
|
|
53
|
+
|
|
54
|
+
## Releasing a new version
|
|
55
|
+
|
|
56
|
+
1. Bump version in `pyproject.toml` (`version = "X.Y.Z"`).
|
|
57
|
+
2. Verify quality gates pass: `uv run pytest && uv run ruff check extension tintamcp tests && uv run pyright`.
|
|
58
|
+
3. Commit using Conventional Commits: `git commit -am "chore(release): bump version to X.Y.Z"`.
|
|
59
|
+
4. Tag and push: `git tag vX.Y.Z && git push origin main --tags`.
|
|
60
|
+
5. GitHub Actions builds wheels, packages `tinta-extension.zip`, and creates the GitHub Release automatically.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: manual
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Architecture and invariants
|
|
6
|
+
|
|
7
|
+
## Process and runtime boundaries
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[ AI Assistant / Client ]
|
|
11
|
+
│
|
|
12
|
+
â–¼ (MCP protocol over stdio)
|
|
13
|
+
[ tintamcp server ] <-- Pure Python, managed by uv (fastmcp)
|
|
14
|
+
│
|
|
15
|
+
â–¼ (writes request JSON to user IPC folder)
|
|
16
|
+
[ gdbus / session bus ]
|
|
17
|
+
│
|
|
18
|
+
â–¼ (Action activation: org.tinta.mcp)
|
|
19
|
+
[ Inkscape process ]
|
|
20
|
+
│
|
|
21
|
+
â–¼ (runs extension/tinta_mcp.py via internal inkex)
|
|
22
|
+
[ Open SVG document ]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Server process (`tintamcp`): Orchestrates communication, tool definitions, schemas, and parameter validation. Does not touch SVG directly.
|
|
26
|
+
- Extension process (`extension`): Inkscape executes this inside its GTK event loop. It stages DOM mutations, serializes the SVG, then publishes the response JSON.
|
|
27
|
+
|
|
28
|
+
## IPC protocol invariants
|
|
29
|
+
|
|
30
|
+
- The D-Bus action has no parameters. One process lock protects the shared request slot across server processes; a per-connection thread lock also serializes threads.
|
|
31
|
+
- The server atomically publishes `mcp_params.json` and `pending_request.json`; the extension claims the slot by exclusively creating `mcp_processing.json` before it reads the request.
|
|
32
|
+
- Request format:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"protocol_version": 1,
|
|
36
|
+
"session_id": "32 lowercase hexadecimal characters",
|
|
37
|
+
"request_id": "32 lowercase hexadecimal characters",
|
|
38
|
+
"request": {
|
|
39
|
+
"operation": "create-element" | "modify-elements" | "get-info" | "...",
|
|
40
|
+
"attributes": { ... }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
- Response path is derived from `request_id` as `mcp_response_<request_id>.json`; the request cannot choose a path. Responses repeat the version and both identifiers. The extension writes a temporary sibling and atomically renames it into place.
|
|
45
|
+
- The extension stages document mutations on a deep copy and publishes an error without using that copy if a handler fails. `save_raw()` serializes the SVG before it writes the response.
|
|
46
|
+
- Legacy requests with only a top-level `tag`, including `execute-code`, are rejected. There is no arbitrary Python or textual-command execution.
|
|
47
|
+
- Server and extension must be deployed together. Nesting the operation means a phase 1 extension sees no top-level operation and cannot execute a stale-format mutation. The server sends protocol version 1 and rejects missing or mismatched response identifiers, including the operation name.
|
|
48
|
+
- The server polls the derived response path for 5 seconds after D-Bus activation. Timeout, malformed response, or D-Bus failure means the outcome is unknown, not cancelled. The pending request remains on disk; later calls first reconcile its response and refuse to overwrite unresolved files.
|
|
49
|
+
- IPC uses `$XDG_RUNTIME_DIR/tintamcp` only when its owner and permissions are private, otherwise `/tmp/tintamcp-<uid>` with mode 0700. Windows uses the current user's local application data temp directory. Do not add global-temp fallbacks or caller-supplied IPC paths.
|
|
50
|
+
|
|
51
|
+
## Tool pipeline design
|
|
52
|
+
|
|
53
|
+
- Prefer atomic operations where possible.
|
|
54
|
+
- The `create_elements` tool handles creation, canvas resizing, viewport fitting (`fit_canvas`), selection highlighting (`select_created`), and preview rendering (`return_preview`) in a single round-trip.
|
|
55
|
+
- Avoid forcing the LLM to make multiple serial round-trips for common design workflows.
|
|
56
|
+
|
|
57
|
+
## Native GTK export actions
|
|
58
|
+
|
|
59
|
+
- Canvas preview exports use Inkscape GTK actions (`export-type`, `export-filename`, `export-overwrite`, `export-area-page`, `export-do`) triggered via `gdbus`.
|
|
60
|
+
- Never spawn a secondary headless Inkscape process to render previews: native actions take ~0.5s versus ~3.0s for CLI spawns.
|
|
61
|
+
- Every native preview action must be prepared successfully before `export-do`. Use a unique IPC destination, quote GVariant strings, cap the largest PNG dimension, and validate the complete PNG before returning it. A preview failure after an edit is a warning alongside the applied edit result.
|
|
62
|
+
- A successful D-Bus transport call is not an operation result. Extension errors must become MCP tool errors; inspection returns all reported metadata and selected elements without silent truncation.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcp-tool-builder
|
|
3
|
+
description: "Step-by-step guide for creating, modifying, and testing MCP tools for Inkscape in Tinta."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MCP tool builder for Tinta
|
|
7
|
+
|
|
8
|
+
Use this skill when adding a new tool or modifying an existing tool in Tinta.
|
|
9
|
+
|
|
10
|
+
## Dual-runtime architecture
|
|
11
|
+
|
|
12
|
+
Every tool in Tinta bridges two distinct environments:
|
|
13
|
+
|
|
14
|
+
1. Host server (`tintamcp/`): Runs in external Python with FastMCP. Defines the tool signature, validates types, atomically writes a versioned request to the user's IPC directory, and calls D-Bus.
|
|
15
|
+
2. Inkscape extension (`extension/`): Runs inside Inkscape's Python context via `inkex`. Claims and validates the IPC request, stages document edits, serializes the SVG, and atomically writes a correlated response.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
[Client / LLM] ──> FastMCP (@mcp.tool) ──> IPC Request File ──> gdbus ──> [Inkscape Extension] ──> SVG DOM
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Step 1: Define the tool in `tintamcp/server.py`
|
|
22
|
+
|
|
23
|
+
Register the tool on the `mcp` instance using clear, type-annotated parameters and concise docstrings designed for LLM consumption:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
@mcp.tool()
|
|
27
|
+
def my_new_tool(element_id: str, radius: float = 10.0, color: str = "#000000") -> str:
|
|
28
|
+
"""Short description of what the tool accomplishes on the canvas.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
element_id: Unique identifier for the SVG element.
|
|
32
|
+
radius: Radius in user units (default 10.0).
|
|
33
|
+
color: Hex or CSS color string (e.g. '#ff0000').
|
|
34
|
+
"""
|
|
35
|
+
payload = {
|
|
36
|
+
"operation": "my-operation",
|
|
37
|
+
"attributes": {
|
|
38
|
+
"id": element_id,
|
|
39
|
+
"radius": str(radius),
|
|
40
|
+
"color": color,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
return str(dispatch_operation(payload))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Design guidelines for MCP tools
|
|
47
|
+
|
|
48
|
+
- Use snake_case for Python parameters. Tinta automatically normalizes snake_case to kebab-case SVG attributes (`stroke_width` -> `stroke-width`).
|
|
49
|
+
- Provide sensible defaults to reduce round-trips for common actions.
|
|
50
|
+
- Prefer atomic operations. When an operation involves creating elements, fitting canvas, or generating previews, combine them into a single round-trip (see `create_elements`).
|
|
51
|
+
|
|
52
|
+
### Docstring design for LLM consumers
|
|
53
|
+
|
|
54
|
+
Tool docstrings are injected directly into the client LLM system prompt. Follow these guidelines to maximize tool calling accuracy and minimize token overhead:
|
|
55
|
+
- State actions directly using imperative mood in the first line (`"Create an SVG rectangle."`, not `"This tool allows the user to seamlessly create..."`).
|
|
56
|
+
- Avoid marketing filler and buzzwords (*robust*, *seamless*, *powerful*, *effortless*, *cutting-edge*, *comprehensive*).
|
|
57
|
+
- Document units and formats explicitly (pixels, percentages, hex colors) and valid ranges in the `Args:` section.
|
|
58
|
+
- Explain non-obvious defaults concisely so the LLM can plan its workflow in fewer calls.
|
|
59
|
+
|
|
60
|
+
## Step 2: Implement the operation in `extension/`
|
|
61
|
+
|
|
62
|
+
Operations live under `extension/tinta_mcp_ops/`:
|
|
63
|
+
- Shape and element creation: `extension/tinta_mcp_ops/element_mapping.py`
|
|
64
|
+
- Manipulation and attributes: `extension/tinta_mcp_ops/document_operations.py`
|
|
65
|
+
|
|
66
|
+
### Handling the action in `extension/tinta_mcp.py`
|
|
67
|
+
|
|
68
|
+
Register a fixed handler in `DOCUMENT_OPERATIONS`, called by `ElementCreator.handle_operation`. Never generate Python source or expose an interpreter. Operation names are independent of SVG tags. For queries handled by the extension:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
if operation == "my-operation":
|
|
72
|
+
# 1. Access the open document DOM via self.svg
|
|
73
|
+
elem = self.svg.getElementById(attributes.get("id"))
|
|
74
|
+
|
|
75
|
+
# 2. Mutate DOM using inkex elements
|
|
76
|
+
# 3. Return response data; the extension publishes it after SVG serialization.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The extension never accepts a response path from an operation. Requests use protocol version 1 with generated session and request IDs; responses use the corresponding derived path.
|
|
80
|
+
|
|
81
|
+
## Step 3: Test and debug
|
|
82
|
+
|
|
83
|
+
### Manual D-Bus trigger (Windows and Linux)
|
|
84
|
+
|
|
85
|
+
Verify that Inkscape responds over the session bus:
|
|
86
|
+
|
|
87
|
+
Windows (PowerShell):
|
|
88
|
+
```powershell
|
|
89
|
+
$gdbus = "C:\Program Files\Inkscape\bin\gdbus.exe"
|
|
90
|
+
& $gdbus call --session --dest org.inkscape.Inkscape --object-path /org/inkscape/Inkscape --method org.gtk.Actions.List | Select-String "org.tinta.mcp"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Linux / WSL (Bash):
|
|
94
|
+
```bash
|
|
95
|
+
gdbus call --session --dest org.inkscape.Inkscape --object-path /org/inkscape/Inkscape --method org.gtk.Actions.List | grep org.tinta.mcp
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Automated integration test
|
|
99
|
+
|
|
100
|
+
Add a test in [tests/test_dbus_smoke.py](../../../tests/test_dbus_smoke.py):
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
def test_my_new_tool_live():
|
|
104
|
+
result = my_new_tool(element_id="test_item_1", radius=15.0)
|
|
105
|
+
assert "success" in result.lower()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run tests locally:
|
|
109
|
+
```bash
|
|
110
|
+
uv run pytest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use `uv run pytest tests/test_dbus_smoke.py --live-inkscape -v` for live tests. The fixture must own an isolated process/profile/document; never target the user's running session.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tinta-designer
|
|
3
|
+
description: "Best practices, workflows, and technical guidelines for creating and editing vector graphics in Inkscape via Tinta MCP."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tinta designer workflow
|
|
7
|
+
|
|
8
|
+
Use this skill when creating, modifying, or inspecting vector illustrations, diagrams, icons, or layouts in an open Inkscape session.
|
|
9
|
+
|
|
10
|
+
## Core workflow
|
|
11
|
+
|
|
12
|
+
### 1. Inspect existing state
|
|
13
|
+
Before creating or editing graphics, verify the current canvas state:
|
|
14
|
+
- Call `get_document_info` to inspect canvas dimensions, viewBox coordinates, and existing element counts.
|
|
15
|
+
- Call `get_selection` if the user refers to "selected items", "this shape", or wants to modify whatever is currently highlighted in the Inkscape UI.
|
|
16
|
+
- Do not clear the canvas unless the user explicitly asks to start from scratch or clear the document.
|
|
17
|
+
|
|
18
|
+
### 2. Batch operations using `create_elements`
|
|
19
|
+
Avoid making multiple sequential tool calls to insert shapes one by one. Use `create_elements` to submit a structured batch:
|
|
20
|
+
- Combine element definitions, canvas fitting, and preview rendering in a single request.
|
|
21
|
+
- Use `fit_canvas=True` with `fit_padding` when drawing a composition whose final bounding box should define the page boundaries.
|
|
22
|
+
- Set `return_preview=True` to immediately obtain an image preview of the resulting render.
|
|
23
|
+
|
|
24
|
+
### 3. Verify with visual feedback
|
|
25
|
+
Always inspect the visual result before concluding a design task:
|
|
26
|
+
- Use the preview returned by `create_elements(return_preview=True)` or call `export_preview()`.
|
|
27
|
+
- Check for overlapping text, clipping at viewBox boundaries, or unintended layer stacking.
|
|
28
|
+
- If an adjustment is needed, modify elements by ID with `modify_elements` (using `return_preview=True` for instant visual verification) rather than redrawing the entire canvas.
|
|
29
|
+
|
|
30
|
+
## Technical SVG guidelines for Inkscape
|
|
31
|
+
|
|
32
|
+
### Document hierarchy and grouping
|
|
33
|
+
Organize elements into logical groups using `<g id="...">`:
|
|
34
|
+
- Group related shapes together (e.g. `header_group`, `chart_bars`, `icon_background`).
|
|
35
|
+
- Grouped elements appear cleanly in Inkscape's Objects and Layers panel (`Ctrl+Shift+L`), allowing the user to select, hide, or transform components easily.
|
|
36
|
+
- Assign meaningful, unique IDs to major elements so they can be modified later without guessing.
|
|
37
|
+
|
|
38
|
+
### Z-order and stacking
|
|
39
|
+
In SVG, rendering order follows document order:
|
|
40
|
+
- Elements defined earlier in the payload are drawn below later elements.
|
|
41
|
+
- Always declare background rectangles, cards, or base shapes first.
|
|
42
|
+
- Declare decorative details, strokes, icons, and text elements last.
|
|
43
|
+
|
|
44
|
+
### Primitive shapes versus paths
|
|
45
|
+
- Use native primitives (`<rect>`, `<circle>`, `<ellipse>`, `<line>`) whenever possible. In Inkscape, native primitives retain parametric handles (e.g. dragging corner radius on a rectangle).
|
|
46
|
+
- Use `<path>` for custom curves, logos, irregular polygons, or arrows where primitives are insufficient.
|
|
47
|
+
|
|
48
|
+
### Text positioning and baseline alignment
|
|
49
|
+
In SVG, the `y` coordinate of a `<text>` element refers to the typographic baseline, not the top-left bounding box corner:
|
|
50
|
+
- If placing text at the top of a container at `y=0`, the text will render above the visible area.
|
|
51
|
+
- Either offset `y` downwards by at least the font size (e.g. `y=24` for a `20px` font) or set `dominant-baseline="hanging"`.
|
|
52
|
+
- Use cross-platform fallback font stacks such as `system-ui, sans-serif, Arial, Helvetica, DejaVu Sans` so text renders reliably on both Windows and Linux installations.
|
|
53
|
+
|
|
54
|
+
### Viewport and coordinate consistency
|
|
55
|
+
- Work within a consistent coordinate system defined by the `viewBox`.
|
|
56
|
+
- When sizing rectangles or positioning icons, calculate relative offsets from the parent group or anchor position rather than placing arbitrary coordinates across the canvas.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tinta-dev-workflow
|
|
3
|
+
description: "Developer and AI agent workflow for dependency management, code quality gates, and conventional commits in Tinta."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tinta development and quality workflow
|
|
7
|
+
|
|
8
|
+
Use this skill for local environment setup, running quality gates, and crafting commits that pass CI.
|
|
9
|
+
|
|
10
|
+
## Environment management with uv
|
|
11
|
+
|
|
12
|
+
Tinta uses `uv` for fast, reproducible dependency resolution.
|
|
13
|
+
|
|
14
|
+
### Basic commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Sync all dependencies (runtime + dev tools)
|
|
18
|
+
uv sync --all-groups
|
|
19
|
+
|
|
20
|
+
# Add a runtime dependency (must be pure Python, no C/inkex)
|
|
21
|
+
uv add <package-name>
|
|
22
|
+
|
|
23
|
+
# Add a development tool (linter, test helper)
|
|
24
|
+
uv add --group dev <package-name>
|
|
25
|
+
|
|
26
|
+
# Run commands within the managed environment
|
|
27
|
+
uv run pytest
|
|
28
|
+
uv run ruff check extension tintamcp tests
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Windows and WSL shared drive tip
|
|
32
|
+
|
|
33
|
+
When developing across both Windows and WSL on a shared drive:
|
|
34
|
+
- Windows `.venv` uses `.venv/Scripts/python.exe`.
|
|
35
|
+
- Linux `.venv` uses `.venv/bin/python`.
|
|
36
|
+
- Running `uv sync` switches between them in 1-2 seconds because all dependencies are pure Python wheels.
|
|
37
|
+
|
|
38
|
+
## Quality gates checklist
|
|
39
|
+
|
|
40
|
+
Run these checks when modifying Python code (`extension/`, `tintamcp/`, `tests/`) or dependencies (`pyproject.toml`). Pure documentation edits do not require running test or lint gates.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Linting and import sorting
|
|
44
|
+
uv run ruff check extension tintamcp tests
|
|
45
|
+
|
|
46
|
+
# 2. Code formatting
|
|
47
|
+
uv run ruff format --check extension tintamcp tests
|
|
48
|
+
|
|
49
|
+
# 3. Static type analysis
|
|
50
|
+
uv run pyright
|
|
51
|
+
|
|
52
|
+
# 4. Test suite and coverage report
|
|
53
|
+
uv run pytest
|
|
54
|
+
|
|
55
|
+
# 5. Dependency security audit
|
|
56
|
+
uv run pip-audit
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To automatically format files:
|
|
60
|
+
```bash
|
|
61
|
+
uv run ruff format extension tintamcp tests
|
|
62
|
+
uv run ruff check --fix extension tintamcp tests
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Conventional Commits standard
|
|
66
|
+
|
|
67
|
+
All PR titles and commit messages must follow the Conventional Commits specification.
|
|
68
|
+
|
|
69
|
+
### Format
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
<type>(<scope>): <short description in imperative mood>
|
|
73
|
+
|
|
74
|
+
[optional body explaining motivation and context]
|
|
75
|
+
|
|
76
|
+
[optional footer, e.g. BREAKING CHANGE or Closes #123]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Allowed types
|
|
80
|
+
|
|
81
|
+
- `feat`: New feature or MCP tool (e.g. `feat(tools): add canvas_rotate tool`)
|
|
82
|
+
- `fix`: Bug fix in server, extension, or installer (e.g. `fix(ipc): handle spaces in username path`)
|
|
83
|
+
- `docs`: Documentation updates (e.g. `docs: clarify WSL setup in README`)
|
|
84
|
+
- `refactor`: Code restructuring without functional changes (e.g. `refactor(parser): extract xml validation`)
|
|
85
|
+
- `perf`: Performance improvement (e.g. `perf(export): use native GTK export actions`)
|
|
86
|
+
- `test`: Adding or updating tests (e.g. `test(dbus): add smoke test for element deletion`)
|
|
87
|
+
- `ci`: CI/CD workflow changes (e.g. `ci: add security audit gate`)
|
|
88
|
+
- `chore`: Tooling, dependency, or version updates (e.g. `chore(deps): update fastmcp to 4.1.0`)
|
|
89
|
+
|
|
90
|
+
### Rules
|
|
91
|
+
|
|
92
|
+
- Use lower case for type and scope.
|
|
93
|
+
- Use imperative present tense (`add`, not `added` or `adds`).
|
|
94
|
+
- Do not add a period at the end of the subject line.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 4
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
|
|
11
|
+
[*.{bat,cmd,ps1}]
|
|
12
|
+
end_of_line = crlf
|
|
13
|
+
|
|
14
|
+
[*.{json,yml,yaml}]
|
|
15
|
+
indent_size = 2
|
|
16
|
+
|
|
17
|
+
[*.md]
|
|
18
|
+
trim_trailing_whitespace = false
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths-ignore:
|
|
8
|
+
- "**.md"
|
|
9
|
+
- "assets/**"
|
|
10
|
+
- ".gitignore"
|
|
11
|
+
pull_request:
|
|
12
|
+
types:
|
|
13
|
+
- opened
|
|
14
|
+
- edited
|
|
15
|
+
- synchronize
|
|
16
|
+
- reopened
|
|
17
|
+
branches:
|
|
18
|
+
- main
|
|
19
|
+
paths-ignore:
|
|
20
|
+
- "**.md"
|
|
21
|
+
- "assets/**"
|
|
22
|
+
- ".gitignore"
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
|
|
25
|
+
concurrency:
|
|
26
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
27
|
+
cancel-in-progress: true
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
pull-requests: read
|
|
31
|
+
contents: read
|
|
32
|
+
|
|
33
|
+
env:
|
|
34
|
+
PYTHONUTF8: "1"
|
|
35
|
+
PYTHONIOENCODING: utf-8
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
pr-title:
|
|
39
|
+
name: Validate PR Title (Conventional Commits)
|
|
40
|
+
runs-on: ubuntu-26.04
|
|
41
|
+
if: github.event_name == 'pull_request'
|
|
42
|
+
steps:
|
|
43
|
+
- name: Validate PR Title
|
|
44
|
+
uses: amannn/action-semantic-pull-request@v6.1.1
|
|
45
|
+
env:
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
47
|
+
with:
|
|
48
|
+
types: |
|
|
49
|
+
feat
|
|
50
|
+
fix
|
|
51
|
+
docs
|
|
52
|
+
style
|
|
53
|
+
refactor
|
|
54
|
+
perf
|
|
55
|
+
test
|
|
56
|
+
build
|
|
57
|
+
ci
|
|
58
|
+
chore
|
|
59
|
+
revert
|
|
60
|
+
requireScope: false
|
|
61
|
+
|
|
62
|
+
lint:
|
|
63
|
+
name: Lint, Format & Type Check
|
|
64
|
+
runs-on: ubuntu-26.04
|
|
65
|
+
steps:
|
|
66
|
+
- name: Checkout repository
|
|
67
|
+
uses: actions/checkout@v7.0.1
|
|
68
|
+
|
|
69
|
+
- name: Set up uv
|
|
70
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
71
|
+
with:
|
|
72
|
+
enable-cache: true
|
|
73
|
+
|
|
74
|
+
- name: Set up Python
|
|
75
|
+
run: uv python install 3.13
|
|
76
|
+
|
|
77
|
+
- name: Sync dependencies
|
|
78
|
+
run: uv sync --all-groups
|
|
79
|
+
|
|
80
|
+
- name: Check code formatting (ruff format)
|
|
81
|
+
run: uv run ruff format --check extension tintamcp scripts tests
|
|
82
|
+
|
|
83
|
+
- name: Check lints and imports (ruff check)
|
|
84
|
+
run: uv run ruff check extension tintamcp scripts tests
|
|
85
|
+
|
|
86
|
+
- name: Check static types (pyright)
|
|
87
|
+
run: uv run pyright
|
|
88
|
+
|
|
89
|
+
- name: Validate Linux installer syntax
|
|
90
|
+
run: bash -n scripts/install_linux.sh
|
|
91
|
+
|
|
92
|
+
audit:
|
|
93
|
+
name: Dependency Security Audit
|
|
94
|
+
runs-on: ubuntu-26.04
|
|
95
|
+
steps:
|
|
96
|
+
- name: Checkout repository
|
|
97
|
+
uses: actions/checkout@v7.0.1
|
|
98
|
+
|
|
99
|
+
- name: Set up uv
|
|
100
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
101
|
+
with:
|
|
102
|
+
enable-cache: true
|
|
103
|
+
|
|
104
|
+
- name: Set up Python
|
|
105
|
+
run: uv python install 3.13
|
|
106
|
+
|
|
107
|
+
- name: Sync dependencies
|
|
108
|
+
run: uv sync --all-groups
|
|
109
|
+
|
|
110
|
+
- name: Audit dependencies for known CVEs
|
|
111
|
+
run: uv run pip-audit
|
|
112
|
+
|
|
113
|
+
test:
|
|
114
|
+
name: Test (${{ matrix.os }}, Py ${{ matrix.python-version }})
|
|
115
|
+
needs: lint
|
|
116
|
+
runs-on: ${{ matrix.os }}
|
|
117
|
+
strategy:
|
|
118
|
+
fail-fast: false
|
|
119
|
+
matrix:
|
|
120
|
+
os: [ubuntu-26.04, windows-latest]
|
|
121
|
+
python-version: ["3.12", "3.13"]
|
|
122
|
+
steps:
|
|
123
|
+
- name: Checkout repository
|
|
124
|
+
uses: actions/checkout@v7.0.1
|
|
125
|
+
|
|
126
|
+
- name: Set up uv
|
|
127
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
128
|
+
with:
|
|
129
|
+
enable-cache: true
|
|
130
|
+
|
|
131
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
132
|
+
run: uv python install ${{ matrix.python-version }}
|
|
133
|
+
|
|
134
|
+
- name: Sync dependencies
|
|
135
|
+
run: uv sync --all-groups --python ${{ matrix.python-version }}
|
|
136
|
+
|
|
137
|
+
- name: Validate Windows installer syntax
|
|
138
|
+
if: matrix.os == 'windows-latest'
|
|
139
|
+
shell: pwsh
|
|
140
|
+
run: |
|
|
141
|
+
$tokens = $null
|
|
142
|
+
$parseErrors = $null
|
|
143
|
+
[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path scripts/install_windows.ps1), [ref]$tokens, [ref]$parseErrors) | Out-Null
|
|
144
|
+
if ($parseErrors.Count) {
|
|
145
|
+
$parseErrors | ForEach-Object { Write-Error $_.Message }
|
|
146
|
+
exit 1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
- name: Run test suite with coverage
|
|
150
|
+
run: uv run pytest
|
|
151
|
+
|
|
152
|
+
- name: Upload coverage artifact
|
|
153
|
+
if: matrix.os == 'ubuntu-26.04' && matrix.python-version == '3.13'
|
|
154
|
+
uses: actions/upload-artifact@v7.0.2
|
|
155
|
+
with:
|
|
156
|
+
name: coverage-report
|
|
157
|
+
path: coverage.xml
|
|
158
|
+
retention-days: 7
|
|
159
|
+
|
|
160
|
+
dbus-e2e:
|
|
161
|
+
name: Headless D-Bus E2E Test (Linux)
|
|
162
|
+
needs: lint
|
|
163
|
+
runs-on: ubuntu-26.04
|
|
164
|
+
steps:
|
|
165
|
+
- name: Checkout repository
|
|
166
|
+
uses: actions/checkout@v7.0.1
|
|
167
|
+
|
|
168
|
+
- name: Install Inkscape and headless test tools
|
|
169
|
+
run: |
|
|
170
|
+
sudo apt-get update
|
|
171
|
+
sudo apt-get install -y inkscape libglib2.0-bin xvfb dbus-x11
|
|
172
|
+
|
|
173
|
+
- name: Set up uv
|
|
174
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
175
|
+
with:
|
|
176
|
+
enable-cache: true
|
|
177
|
+
|
|
178
|
+
- name: Set up Python
|
|
179
|
+
run: uv python install 3.13
|
|
180
|
+
|
|
181
|
+
- name: Sync dependencies
|
|
182
|
+
run: uv sync --all-groups
|
|
183
|
+
|
|
184
|
+
- name: Run live tests in isolated profiles and documents
|
|
185
|
+
run: timeout 15m xvfb-run -a dbus-run-session uv run pytest tests/test_dbus_smoke.py --live-inkscape -v --junitxml=live-inkscape-results.xml
|
|
186
|
+
|
|
187
|
+
- name: Upload live test results
|
|
188
|
+
if: always()
|
|
189
|
+
uses: actions/upload-artifact@v7.0.2
|
|
190
|
+
with:
|
|
191
|
+
name: live-inkscape-results
|
|
192
|
+
path: live-inkscape-results.xml
|
|
193
|
+
if-no-files-found: ignore
|
|
194
|
+
retention-days: 7
|