heinzel-tailscale 0.9.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.
- heinzel_tailscale-0.9.1/.gitignore +15 -0
- heinzel_tailscale-0.9.1/CHANGELOG.md +8 -0
- heinzel_tailscale-0.9.1/LICENSE +21 -0
- heinzel_tailscale-0.9.1/PKG-INFO +68 -0
- heinzel_tailscale-0.9.1/README.md +54 -0
- heinzel_tailscale-0.9.1/plugin-lock.json +41 -0
- heinzel_tailscale-0.9.1/pyproject.toml +131 -0
- heinzel_tailscale-0.9.1/requirements.lock +1163 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/__init__.py +72 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_cli.py +164 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_localapi.py +189 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_lock.py +53 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_mapping.py +138 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_models.py +60 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_readiness.py +32 -0
- heinzel_tailscale-0.9.1/src/heinzel_tailscale/_service.py +296 -0
- heinzel_tailscale-0.9.1/tests/browser/web_test.py +151 -0
- heinzel_tailscale-0.9.1/tests/conftest.py +21 -0
- heinzel_tailscale-0.9.1/tests/fixtures/local_http.py +13 -0
- heinzel_tailscale-0.9.1/tests/fixtures/tailscale_web.py +161 -0
- heinzel_tailscale-0.9.1/tests/integration/cli_test.py +141 -0
- heinzel_tailscale-0.9.1/tests/integration/host_test.py +552 -0
- heinzel_tailscale-0.9.1/tests/integration/lock_test.py +48 -0
- heinzel_tailscale-0.9.1/tests/integration/origin_policy_test.py +82 -0
- heinzel_tailscale-0.9.1/tests/unit/cli_test.py +74 -0
- heinzel_tailscale-0.9.1/tests/unit/localapi_test.py +166 -0
- heinzel_tailscale-0.9.1/tests/unit/mapping_test.py +131 -0
- heinzel_tailscale-0.9.1/tests/unit/readiness_test.py +66 -0
- heinzel_tailscale-0.9.1/tests/unit/status_test.py +185 -0
- heinzel_tailscale-0.9.1/web/build.py +32 -0
- heinzel_tailscale-0.9.1/web/e2e/tailscale.mjs +183 -0
- heinzel_tailscale-0.9.1/web/package.json +8 -0
- heinzel_tailscale-0.9.1/web/pnpm-lock.yaml +24 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
Move this plugin to its independent repository project for the Heinzel 0.9.1 plugin contract.
|
|
6
|
+
Own its dependencies, tests and artifacts; consume public contracts without concrete provider dependencies.
|
|
7
|
+
|
|
8
|
+
Earlier history is preserved in the source repository named in README.md.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Helmut Hoffer von Ankershoffen
|
|
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,68 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: heinzel-tailscale
|
|
3
|
+
Version: 0.9.1
|
|
4
|
+
Summary: Share the authenticated web app privately through Tailscale Serve: a Heinzelmännchen plugin.
|
|
5
|
+
Project-URL: Source, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/tree/main/plugins/heinzel-tailscale
|
|
6
|
+
Project-URL: Changelog, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/blob/main/plugins/heinzel-tailscale/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: heinzel<0.10,>=0.9.1
|
|
12
|
+
Requires-Dist: httpx>=0.28
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Tailscale private web sharing
|
|
16
|
+
|
|
17
|
+
A first-party adapter for the installed Tailscale client. It keeps the web app's bearer/device authentication and
|
|
18
|
+
loopback listener; tailnet membership does not grant access to the backend owner's operations or terminal.
|
|
19
|
+
|
|
20
|
+
Install the released plugin from Discover Plugins, or run `hz system plugin add tailscale --confirm`. The core PyPI
|
|
21
|
+
package does not bundle this separate plugin distribution, so add it explicitly after installing Heinzel. Tailscale
|
|
22
|
+
itself must already be installed and signed in; hz does not install it, log in, grant HTTPS consent or change OS
|
|
23
|
+
permissions for you.
|
|
24
|
+
On macOS it also detects the CLI bundled in `/Applications/Tailscale.app` and explicitly selects CLI mode, as
|
|
25
|
+
described in [Tailscale's CLI documentation](https://tailscale.com/docs/reference/tailscale-cli?tab=macos).
|
|
26
|
+
|
|
27
|
+
In Settings or the footer's sharing icon, turn **Share over Tailscale** on. The control checks the node and existing
|
|
28
|
+
Serve mappings, merges its exact HTTPS origin into your user configuration and configures its loopback target. The
|
|
29
|
+
running web app applies the updated origin policy immediately; it does not require a backend restart. It shows ON and
|
|
30
|
+
a URL only after the owning authenticated web app verifies its actual port, active origin policy and matching Serve
|
|
31
|
+
configuration. A CLI/stdio MCP request saves the same intent but reports verification pending until that web owner
|
|
32
|
+
verifies it. Status reads never enable sharing.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
hz tailscale status --json
|
|
36
|
+
hz tailscale enable --json
|
|
37
|
+
hz tailscale disable --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The corresponding private operations are available through authenticated API and stdio MCP. They are absent from
|
|
41
|
+
the unauthenticated REST and HTTP MCP heads. In-process calls use the actual bound web port. A separate CLI retains
|
|
42
|
+
a previously observed port override; it cannot discover an arbitrary ephemeral web listener or claim it is ready.
|
|
43
|
+
|
|
44
|
+
**Copy URL** copies a working link with the current `#token=...` session fragment when the authenticated session has
|
|
45
|
+
one. Treat that link like a password: anyone who receives it can use that session until it is revoked or expires. If
|
|
46
|
+
there is no session token, the control copies only the address. A fresh phone browser also needs authentication: use
|
|
47
|
+
the separate **Copy sign-in link** action from the session owner, then pair that browser. Pairing a device never grants
|
|
48
|
+
it authority to pair another device. See [private web sharing](https://github.com/helmut-hoffer-von-ankershoffen/heinzel/blob/main/docs/PRIVATE_WEB_SHARING.md) for authentication
|
|
49
|
+
and revocation.
|
|
50
|
+
|
|
51
|
+
Turning sharing off removes only the still-matching hz root handler and any origin permission this plugin added.
|
|
52
|
+
Other paths, listeners, services and manually configured origins remain. An existing conflicting root, public
|
|
53
|
+
Funnel setting, foreground session or higher-priority origin policy is refused. A changed Serve revision or unknown
|
|
54
|
+
write outcome stops without retry or rollback; inspect current status before taking another action.
|
|
55
|
+
Matching an existing manual handler does not make it owned by hz, so enabling refuses to adopt it. OFF without an
|
|
56
|
+
owned request is non-destructive. Completed removal relinquishes ownership before cleanup; repeating OFF cannot
|
|
57
|
+
delete a later manual recreation. An uncertain create remains unverified and needs inspection before deletion.
|
|
58
|
+
|
|
59
|
+
`tailscale.https_port` chooses an alternate HTTPS port when another app owns the default. Disable a previous request
|
|
60
|
+
before changing its external origin. A Tailscale version must provide the conditional Serve LocalAPI and one of the
|
|
61
|
+
recognized local endpoint formats. Unsupported client installations report an actionable error; no sudo or remote
|
|
62
|
+
endpoint fallback is attempted. The exact node DNS name must be eligible for HTTPS certificates in Tailscale's own
|
|
63
|
+
configuration. Readiness verifies the local deployment; actual phone access still depends on Tailscale login,
|
|
64
|
+
network policy and the remote browser's credential.
|
|
65
|
+
|
|
66
|
+
Development and release commands: [repository guide](../../README.md).
|
|
67
|
+
|
|
68
|
+
Migration provenance: heinzel, before the 0.9 plugin-boundary migration.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Tailscale private web sharing
|
|
2
|
+
|
|
3
|
+
A first-party adapter for the installed Tailscale client. It keeps the web app's bearer/device authentication and
|
|
4
|
+
loopback listener; tailnet membership does not grant access to the backend owner's operations or terminal.
|
|
5
|
+
|
|
6
|
+
Install the released plugin from Discover Plugins, or run `hz system plugin add tailscale --confirm`. The core PyPI
|
|
7
|
+
package does not bundle this separate plugin distribution, so add it explicitly after installing Heinzel. Tailscale
|
|
8
|
+
itself must already be installed and signed in; hz does not install it, log in, grant HTTPS consent or change OS
|
|
9
|
+
permissions for you.
|
|
10
|
+
On macOS it also detects the CLI bundled in `/Applications/Tailscale.app` and explicitly selects CLI mode, as
|
|
11
|
+
described in [Tailscale's CLI documentation](https://tailscale.com/docs/reference/tailscale-cli?tab=macos).
|
|
12
|
+
|
|
13
|
+
In Settings or the footer's sharing icon, turn **Share over Tailscale** on. The control checks the node and existing
|
|
14
|
+
Serve mappings, merges its exact HTTPS origin into your user configuration and configures its loopback target. The
|
|
15
|
+
running web app applies the updated origin policy immediately; it does not require a backend restart. It shows ON and
|
|
16
|
+
a URL only after the owning authenticated web app verifies its actual port, active origin policy and matching Serve
|
|
17
|
+
configuration. A CLI/stdio MCP request saves the same intent but reports verification pending until that web owner
|
|
18
|
+
verifies it. Status reads never enable sharing.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
hz tailscale status --json
|
|
22
|
+
hz tailscale enable --json
|
|
23
|
+
hz tailscale disable --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The corresponding private operations are available through authenticated API and stdio MCP. They are absent from
|
|
27
|
+
the unauthenticated REST and HTTP MCP heads. In-process calls use the actual bound web port. A separate CLI retains
|
|
28
|
+
a previously observed port override; it cannot discover an arbitrary ephemeral web listener or claim it is ready.
|
|
29
|
+
|
|
30
|
+
**Copy URL** copies a working link with the current `#token=...` session fragment when the authenticated session has
|
|
31
|
+
one. Treat that link like a password: anyone who receives it can use that session until it is revoked or expires. If
|
|
32
|
+
there is no session token, the control copies only the address. A fresh phone browser also needs authentication: use
|
|
33
|
+
the separate **Copy sign-in link** action from the session owner, then pair that browser. Pairing a device never grants
|
|
34
|
+
it authority to pair another device. See [private web sharing](https://github.com/helmut-hoffer-von-ankershoffen/heinzel/blob/main/docs/PRIVATE_WEB_SHARING.md) for authentication
|
|
35
|
+
and revocation.
|
|
36
|
+
|
|
37
|
+
Turning sharing off removes only the still-matching hz root handler and any origin permission this plugin added.
|
|
38
|
+
Other paths, listeners, services and manually configured origins remain. An existing conflicting root, public
|
|
39
|
+
Funnel setting, foreground session or higher-priority origin policy is refused. A changed Serve revision or unknown
|
|
40
|
+
write outcome stops without retry or rollback; inspect current status before taking another action.
|
|
41
|
+
Matching an existing manual handler does not make it owned by hz, so enabling refuses to adopt it. OFF without an
|
|
42
|
+
owned request is non-destructive. Completed removal relinquishes ownership before cleanup; repeating OFF cannot
|
|
43
|
+
delete a later manual recreation. An uncertain create remains unverified and needs inspection before deletion.
|
|
44
|
+
|
|
45
|
+
`tailscale.https_port` chooses an alternate HTTPS port when another app owns the default. Disable a previous request
|
|
46
|
+
before changing its external origin. A Tailscale version must provide the conditional Serve LocalAPI and one of the
|
|
47
|
+
recognized local endpoint formats. Unsupported client installations report an actionable error; no sudo or remote
|
|
48
|
+
endpoint fallback is attempted. The exact node DNS name must be eligible for HTTPS certificates in Tailscale's own
|
|
49
|
+
configuration. Readiness verifies the local deployment; actual phone access still depends on Tailscale login,
|
|
50
|
+
network policy and the remote browser's credential.
|
|
51
|
+
|
|
52
|
+
Development and release commands: [repository guide](../../README.md).
|
|
53
|
+
|
|
54
|
+
Migration provenance: heinzel, before the 0.9 plugin-boundary migration.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"sdk": 1,
|
|
4
|
+
"inputs": [
|
|
5
|
+
"coverage>=7.12",
|
|
6
|
+
"editables>=0.5",
|
|
7
|
+
"hatchling>=1.29.0",
|
|
8
|
+
"heinzel>=0.9.1,<0.10",
|
|
9
|
+
"httpx>=0.28",
|
|
10
|
+
"mypy>=1.19.0,<3",
|
|
11
|
+
"pyright[nodejs]==1.1.414",
|
|
12
|
+
"pytest-timeout>=2.4.0,<3",
|
|
13
|
+
"pytest-xdist>=3.8",
|
|
14
|
+
"pytest>=9.0.3,<10",
|
|
15
|
+
"ruff==0.17.2"
|
|
16
|
+
],
|
|
17
|
+
"core": {
|
|
18
|
+
"version": "0.9.1",
|
|
19
|
+
"dependencies": [
|
|
20
|
+
"croniter>=6.2.4",
|
|
21
|
+
"fastapi>=0.161",
|
|
22
|
+
"keyring>=25.6",
|
|
23
|
+
"mcp>=2.3",
|
|
24
|
+
"packaging>=25.0",
|
|
25
|
+
"platformdirs>=4.5",
|
|
26
|
+
"pluggy<2,>=1.6.0",
|
|
27
|
+
"psutil==7.2.2",
|
|
28
|
+
"pydantic-ai-slim==2.61.0",
|
|
29
|
+
"pydantic-ai-slim[anthropic]==2.61.0; extra == \"anthropic\"",
|
|
30
|
+
"pydantic-ai-slim[openai]==2.61.0; extra == \"openai\"",
|
|
31
|
+
"pydantic-settings<3,>=2.11.0",
|
|
32
|
+
"pydantic<3,>=2.12.0",
|
|
33
|
+
"pyyaml<7,>=6.0.2",
|
|
34
|
+
"rich>=14.2.0",
|
|
35
|
+
"tomlkit>=0.13",
|
|
36
|
+
"typer>=0.23.0",
|
|
37
|
+
"uvicorn>=0.54",
|
|
38
|
+
"wsproto>=1.3"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "heinzel-tailscale"
|
|
3
|
+
version = "0.9.1"
|
|
4
|
+
description = "Share the authenticated web app privately through Tailscale Serve: a Heinzelmännchen plugin."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dependencies = ["heinzel>=0.9.1,<0.10", "httpx>=0.28"]
|
|
10
|
+
|
|
11
|
+
[project.urls]
|
|
12
|
+
Source = "https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/tree/main/plugins/heinzel-tailscale"
|
|
13
|
+
Changelog = "https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/blob/main/plugins/heinzel-tailscale/CHANGELOG.md"
|
|
14
|
+
Issues = "https://github.com/helmut-hoffer-von-ankershoffen/heinzel-plugins/issues"
|
|
15
|
+
|
|
16
|
+
[project.entry-points."heinzel.modules"]
|
|
17
|
+
tailscale = "heinzel_tailscale"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["hatchling>=1.29.0"]
|
|
21
|
+
build-backend = "hatchling.build"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
test = ["editables>=0.5", "pytest>=9.0.3,<10", "pytest-timeout>=2.4.0,<3", "pytest-xdist>=3.8", "ruff==0.17.2", "mypy>=1.19.0,<3", "coverage>=7.12", "pyright[nodejs]==1.1.414"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 120
|
|
29
|
+
fix = true
|
|
30
|
+
extend-exclude = ["template"] # rendered and checked by `make template-test`
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["ALL"]
|
|
34
|
+
# pydoclint (DOC): docstring sections must match what the code raises, returns and yields. Preview rules, opted into
|
|
35
|
+
# one by one so no other preview rule sneaks in.
|
|
36
|
+
preview = true
|
|
37
|
+
explicit-preview-rules = true
|
|
38
|
+
extend-select = ["DOC201", "DOC202", "DOC402", "DOC403", "DOC501", "DOC502"]
|
|
39
|
+
ignore = [
|
|
40
|
+
"COM812", # conflicts with the ruff formatter
|
|
41
|
+
"CPY001", # no copyright header in OSS files
|
|
42
|
+
"D203", # incompatible with D211
|
|
43
|
+
"DOC201", # a return value is documented by the type and the summary line; Returns: only when it adds meaning
|
|
44
|
+
"DOC502", # documenting exceptions raised by callees is useful for callers
|
|
45
|
+
"D213", # incompatible with D212
|
|
46
|
+
"FBT001", # boolean positional arguments are idiomatic for Typer options
|
|
47
|
+
"FBT002", # same as FBT001
|
|
48
|
+
"ISC001", # conflicts with the ruff formatter
|
|
49
|
+
"TD003", # todos don't need an issue link
|
|
50
|
+
"FIX002", # todos are allowed
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint.per-file-ignores]
|
|
54
|
+
".claude/hooks/*.py" = [
|
|
55
|
+
"D103", # hook entry points are documented by the module docstring
|
|
56
|
+
"INP001", # hooks are scripts, not a package
|
|
57
|
+
"S603", # subprocess calls with fixed argument lists
|
|
58
|
+
"S607", # tools (uv, git, make) are resolved from PATH on purpose
|
|
59
|
+
"T201", # hooks talk to the agent through stdout/stderr
|
|
60
|
+
]
|
|
61
|
+
"fuzz/*.py" = [
|
|
62
|
+
"INP001", # fuzz targets are scripts, not a package
|
|
63
|
+
]
|
|
64
|
+
"hatch_build.py" = [
|
|
65
|
+
"INP001", # the build hook hatch loads by path, not a package
|
|
66
|
+
]
|
|
67
|
+
"scripts/*.py" = [
|
|
68
|
+
"INP001", # scripts are not a package
|
|
69
|
+
"T201", # scripts report on stdout/stderr
|
|
70
|
+
]
|
|
71
|
+
"**/tests/**/*.py" = [
|
|
72
|
+
"ANN", # fixtures and tests don't need full annotations
|
|
73
|
+
"DOC", # test docstrings describe behaviour, not interfaces
|
|
74
|
+
"ARG", # fixtures are used for their side effects
|
|
75
|
+
"D", # test names are the documentation
|
|
76
|
+
"INP001", # tests are not a package
|
|
77
|
+
"PLC0415", # local imports keep fixtures isolated
|
|
78
|
+
"PLR2004", # magic values are fine in assertions
|
|
79
|
+
"S101", # asserts are what tests do
|
|
80
|
+
"S105", # fake tokens and passwords are test data (credentials, adapters for tracker and document services)
|
|
81
|
+
"S106", # same as S105, passed as arguments
|
|
82
|
+
"S603", # subprocess calls with trusted input
|
|
83
|
+
"S607", # partial executable paths (uv, hz) are resolved from PATH on purpose
|
|
84
|
+
"SLF001", # private access is needed for solitary unit tests
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint.pydocstyle]
|
|
88
|
+
convention = "google"
|
|
89
|
+
|
|
90
|
+
[tool.ruff.format]
|
|
91
|
+
docstring-code-format = true
|
|
92
|
+
|
|
93
|
+
[tool.mypy]
|
|
94
|
+
strict = true
|
|
95
|
+
mypy_path = ["src"]
|
|
96
|
+
files = ["src"]
|
|
97
|
+
plugins = ["pydantic.mypy"]
|
|
98
|
+
|
|
99
|
+
[tool.pydantic-mypy]
|
|
100
|
+
init_forbid_extra = true
|
|
101
|
+
init_typed = true
|
|
102
|
+
warn_required_dynamic_aliases = true
|
|
103
|
+
|
|
104
|
+
[tool.pytest]
|
|
105
|
+
minversion = "9.0"
|
|
106
|
+
strict = true
|
|
107
|
+
testpaths = ["tests"]
|
|
108
|
+
addopts = ["-ra", "--import-mode=importlib"]
|
|
109
|
+
filterwarnings = ["error"]
|
|
110
|
+
markers = ["unit: Offline isolated behavior.", "integration: Offline public-contract integration.", "e2e: Installed artifacts or real external services.", "perf: Explicit timing tests.", "long_running: Extended end-to-end budget."]
|
|
111
|
+
|
|
112
|
+
[tool.heinzel.plugin]
|
|
113
|
+
profiles = ["standard"]
|
|
114
|
+
requires = ["core:plugin-api@1"]
|
|
115
|
+
provides = []
|
|
116
|
+
|
|
117
|
+
[tool.coverage.run]
|
|
118
|
+
source = ["heinzel_tailscale"]
|
|
119
|
+
branch = true
|
|
120
|
+
|
|
121
|
+
[tool.coverage.report]
|
|
122
|
+
fail_under = 90
|
|
123
|
+
show_missing = true
|
|
124
|
+
|
|
125
|
+
[tool.pyright]
|
|
126
|
+
typeCheckingMode = "strict"
|
|
127
|
+
include = ["src"]
|
|
128
|
+
pythonVersion = "3.12"
|
|
129
|
+
|
|
130
|
+
[tool.hatch.build.targets.sdist]
|
|
131
|
+
exclude = [".venv", ".acceptance", ".preview", "dist", "web/node_modules", "**/__pycache__", ".pytest_cache", ".mypy_cache", ".coverage*"]
|