windy-connect 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual envs
25
+ .venv/
26
+ venv/
27
+ env/
28
+ ENV/
29
+
30
+ # Tooling
31
+ .pytest_cache/
32
+ .mypy_cache/
33
+ .ruff_cache/
34
+ .coverage
35
+ htmlcov/
36
+ .tox/
37
+
38
+ # Editors
39
+ .vscode/
40
+ .idea/
41
+ *.swp
42
+ *.swo
43
+ .DS_Store
44
+
45
+ # Local config / secrets
46
+ .env
47
+ .env.local
48
+ *.local.toml
49
+
50
+ # Build artifacts
51
+ *.log
52
+
53
+ # Local install artifacts
54
+ .venv/
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: windy-connect
3
+ Version: 0.1.0
4
+ Summary: Wire any AI agent into the Windy ecosystem in one command.
5
+ Project-URL: Homepage, https://windyconnect.com
6
+ Project-URL: Source, https://github.com/sneakyfree/windy-connect
7
+ Author: Windy
8
+ License: MIT
9
+ Keywords: agent,ai,credentials,eternitas,imap,matrix,windy
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Communications :: Chat
16
+ Classifier: Topic :: Communications :: Email
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: httpx>=0.27.0
20
+ Requires-Dist: platformdirs>=4.0.0
21
+ Requires-Dist: pydantic>=2.6.0
22
+ Requires-Dist: rich>=13.7.0
23
+ Requires-Dist: typer>=0.12.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Windy Connect
32
+
33
+ **The one command that wires any AI agent into the Windy ecosystem.**
34
+
35
+ ```bash
36
+ curl https://get.windyconnect.com | sh
37
+ windy connect
38
+ ```
39
+
40
+ That's it. Sign in with Google. Your agent now has an email address, a chat identity, free LLM access, and (optionally) Eternitas credentials that let it talk to other agents across the agent web.
41
+
42
+ ---
43
+
44
+ ## What this is
45
+
46
+ Windy Connect is the **agent starter pack issuer** for the Windy ecosystem. One `windy connect` invocation does everything a user would otherwise have to do manually:
47
+
48
+ | Without Windy Connect | With Windy Connect |
49
+ |---|---|
50
+ | Go to Telegram → BotFather → /newbot → copy token → paste in OpenClaw config → restart | `windy connect` |
51
+ | Set up SMTP/IMAP credentials by hand | `windy connect` |
52
+ | Sign up for an LLM provider, generate API key, configure base URL | `windy connect` |
53
+ | Apply to Eternitas for a passport | `windy connect` (answer "yes" to the prompt) |
54
+
55
+ The CLI auto-detects which AI agent frameworks are installed (OpenClaw, Ollama, Claude Code, generic) and writes their respective config files to the right places.
56
+
57
+ ## What gets provisioned
58
+
59
+ Every `windy connect` run provisions four things:
60
+
61
+ 1. **Eternitas Passport (optional but recommended)** — a third-party JWT credential issued by [Eternitas](https://eternitas.ai), the independent State-Department-and-credit-bureau-for-AI-agents. With it, your agent can engage in agent-to-agent communication on Windy Chat, gets higher rate limits, and starts building an integrity score that compounds across every ecosystem that adopts the Eternitas standard.
62
+ 2. **Windy Mail mailbox** — `<agent>@windymail.ai`, with IMAP/SMTP/JMAP credentials your agent can use to send and receive mail.
63
+ 3. **Windy Chat identity** — a Matrix identity (`@<agent>:windychat.ai`) and access token your agent uses to chat with humans (and, with Eternitas, other agents).
64
+ 4. **Windy Mind quota** — free LLM access via the OpenAI-compatible Windy Mind router (BYOM-capable, multiple providers behind one endpoint).
65
+
66
+ All four are delivered as a single **Eternitas Agent Credentials Bundle** ([spec](docs/bundle-spec-v1.md)) that the CLI writes to wherever your agent expects it.
67
+
68
+ ## Two-tier access
69
+
70
+ The Windy ecosystem follows the **two-tier-everywhere** principle (see [ADR-052](docs/adr/adr-052-two-tier-ecosystem-access-2026-05-20.md)):
71
+
72
+ | Tier | Who | What you get |
73
+ |---|---|---|
74
+ | **Tier 1 — Free** | Anyone with a Windy account | Mail mailbox, Mind quota, Chat identity (human-facing) |
75
+ | **Tier 2 — Credentialed** | Anyone with an Eternitas passport | Everything in Tier 1 **plus** agent-to-agent comms on Chat, higher rate limits on Mail/Mind, EI-gated luxury features |
76
+
77
+ `windy connect` asks once which tier you want. The Eternitas tier is the recommended default — it's how the agent web prevents spam at scale.
78
+
79
+ ## Repo layout
80
+
81
+ ```
82
+ windy-connect/
83
+ src/windy_connect/ # Python CLI (published to PyPI as windy-connect)
84
+ backend/ # Cloudflare Worker orchestrator (TypeScript)
85
+ tests/ # pytest suite for the CLI
86
+ docs/
87
+ bundle-spec-v1.md # The Eternitas Agent Credentials Bundle spec
88
+ adr/ # Architectural decision records
89
+ dns-plan.md
90
+ pyproject.toml
91
+ README.md
92
+ ```
93
+
94
+ ## Orchestrator backend
95
+
96
+ The CLI talks to a stateless Cloudflare Worker that mints bundles via Sign-in-with-Google + the device-code OAuth flow (RFC 8628). See [backend/README.md](backend/README.md) for routes, deployment, and what's currently stubbed vs real.
97
+
98
+ Point the CLI at a non-default orchestrator with `WINDY_CONNECT_API_URL=https://your-orchestrator/...` — useful for staging or local `wrangler dev`.
99
+
100
+ ## Status
101
+
102
+ **Pre-alpha — full local lifecycle works against a mock bundle.** Real OAuth orchestrator backend not yet deployed. Try it:
103
+
104
+ ```bash
105
+ pip install -e .
106
+ windy connect --mock # provision + write configs + persist state
107
+ windy status # show what's wired
108
+ windy doctor # run diagnostics against the current connection
109
+ windy disconnect # reverse everything cleanly
110
+ ```
111
+
112
+ State is persisted to `~/.windy/state.json` (mode 0600). Disconnect deletes owned files and strips marker-bounded blocks from shared config files (e.g., Himalaya's `config.toml`) without touching the user's other accounts.
113
+
114
+ Test suite: `pytest` — 48 tests covering bundle parsing, state round-trip, every writer in dry-run + wet mode, doctor diagnostics, and full CLI lifecycle.
115
+
116
+ The bundle spec is v1-draft. ADR-052 is Accepted (canonical home: `~/kit-army-config/docs/adr-052-two-tier-ecosystem-access-2026-05-20.md`).
117
+
118
+ ## What's next
119
+
120
+ See the open issues / project board (once GitHub repo exists).
121
+
122
+ Immediate roadmap:
123
+ 1. **`windy connect` CLI MVP** — interactive flow, auto-detection of OpenClaw + Ollama + Claude Code + generic, writes configs to the right places
124
+ 2. **`/agents/connect` orchestrator backend** — Sign-in-with-Google → mints Eternitas EPT → provisions Mail mailbox → provisions Chat identity → returns the bundle
125
+ 3. **`get.windyconnect.com` installer** — one-line bootstrap that installs the CLI
126
+ 4. **`windyconnect.com/pair` browser flow** — the page the CLI opens for the user to sign in
127
+ 5. **OpenClaw ClawHub skill** — `windy-access` skill on ClawHub for discoverability inside the OpenClaw community
128
+
129
+ ## Naming
130
+
131
+ - **Product name:** Windy Connect
132
+ - **CLI command:** `windy connect` (subcommand of the `windy` binary; future siblings: `windy status`, `windy disconnect`, `windy upgrade`)
133
+ - **Repo:** `windy-connect` (this repo)
134
+ - **Domain:** windyconnect.com (CLI installer + browser pairing page + marketing)
135
+ - **PyPI package:** `windy-connect`
136
+ - **Dashboard tile:** "Windy Connect"
137
+
138
+ ## License
139
+
140
+ TBD (likely MIT for the CLI and skill, proprietary for backend orchestrator).
@@ -0,0 +1,110 @@
1
+ # Windy Connect
2
+
3
+ **The one command that wires any AI agent into the Windy ecosystem.**
4
+
5
+ ```bash
6
+ curl https://get.windyconnect.com | sh
7
+ windy connect
8
+ ```
9
+
10
+ That's it. Sign in with Google. Your agent now has an email address, a chat identity, free LLM access, and (optionally) Eternitas credentials that let it talk to other agents across the agent web.
11
+
12
+ ---
13
+
14
+ ## What this is
15
+
16
+ Windy Connect is the **agent starter pack issuer** for the Windy ecosystem. One `windy connect` invocation does everything a user would otherwise have to do manually:
17
+
18
+ | Without Windy Connect | With Windy Connect |
19
+ |---|---|
20
+ | Go to Telegram → BotFather → /newbot → copy token → paste in OpenClaw config → restart | `windy connect` |
21
+ | Set up SMTP/IMAP credentials by hand | `windy connect` |
22
+ | Sign up for an LLM provider, generate API key, configure base URL | `windy connect` |
23
+ | Apply to Eternitas for a passport | `windy connect` (answer "yes" to the prompt) |
24
+
25
+ The CLI auto-detects which AI agent frameworks are installed (OpenClaw, Ollama, Claude Code, generic) and writes their respective config files to the right places.
26
+
27
+ ## What gets provisioned
28
+
29
+ Every `windy connect` run provisions four things:
30
+
31
+ 1. **Eternitas Passport (optional but recommended)** — a third-party JWT credential issued by [Eternitas](https://eternitas.ai), the independent State-Department-and-credit-bureau-for-AI-agents. With it, your agent can engage in agent-to-agent communication on Windy Chat, gets higher rate limits, and starts building an integrity score that compounds across every ecosystem that adopts the Eternitas standard.
32
+ 2. **Windy Mail mailbox** — `<agent>@windymail.ai`, with IMAP/SMTP/JMAP credentials your agent can use to send and receive mail.
33
+ 3. **Windy Chat identity** — a Matrix identity (`@<agent>:windychat.ai`) and access token your agent uses to chat with humans (and, with Eternitas, other agents).
34
+ 4. **Windy Mind quota** — free LLM access via the OpenAI-compatible Windy Mind router (BYOM-capable, multiple providers behind one endpoint).
35
+
36
+ All four are delivered as a single **Eternitas Agent Credentials Bundle** ([spec](docs/bundle-spec-v1.md)) that the CLI writes to wherever your agent expects it.
37
+
38
+ ## Two-tier access
39
+
40
+ The Windy ecosystem follows the **two-tier-everywhere** principle (see [ADR-052](docs/adr/adr-052-two-tier-ecosystem-access-2026-05-20.md)):
41
+
42
+ | Tier | Who | What you get |
43
+ |---|---|---|
44
+ | **Tier 1 — Free** | Anyone with a Windy account | Mail mailbox, Mind quota, Chat identity (human-facing) |
45
+ | **Tier 2 — Credentialed** | Anyone with an Eternitas passport | Everything in Tier 1 **plus** agent-to-agent comms on Chat, higher rate limits on Mail/Mind, EI-gated luxury features |
46
+
47
+ `windy connect` asks once which tier you want. The Eternitas tier is the recommended default — it's how the agent web prevents spam at scale.
48
+
49
+ ## Repo layout
50
+
51
+ ```
52
+ windy-connect/
53
+ src/windy_connect/ # Python CLI (published to PyPI as windy-connect)
54
+ backend/ # Cloudflare Worker orchestrator (TypeScript)
55
+ tests/ # pytest suite for the CLI
56
+ docs/
57
+ bundle-spec-v1.md # The Eternitas Agent Credentials Bundle spec
58
+ adr/ # Architectural decision records
59
+ dns-plan.md
60
+ pyproject.toml
61
+ README.md
62
+ ```
63
+
64
+ ## Orchestrator backend
65
+
66
+ The CLI talks to a stateless Cloudflare Worker that mints bundles via Sign-in-with-Google + the device-code OAuth flow (RFC 8628). See [backend/README.md](backend/README.md) for routes, deployment, and what's currently stubbed vs real.
67
+
68
+ Point the CLI at a non-default orchestrator with `WINDY_CONNECT_API_URL=https://your-orchestrator/...` — useful for staging or local `wrangler dev`.
69
+
70
+ ## Status
71
+
72
+ **Pre-alpha — full local lifecycle works against a mock bundle.** Real OAuth orchestrator backend not yet deployed. Try it:
73
+
74
+ ```bash
75
+ pip install -e .
76
+ windy connect --mock # provision + write configs + persist state
77
+ windy status # show what's wired
78
+ windy doctor # run diagnostics against the current connection
79
+ windy disconnect # reverse everything cleanly
80
+ ```
81
+
82
+ State is persisted to `~/.windy/state.json` (mode 0600). Disconnect deletes owned files and strips marker-bounded blocks from shared config files (e.g., Himalaya's `config.toml`) without touching the user's other accounts.
83
+
84
+ Test suite: `pytest` — 48 tests covering bundle parsing, state round-trip, every writer in dry-run + wet mode, doctor diagnostics, and full CLI lifecycle.
85
+
86
+ The bundle spec is v1-draft. ADR-052 is Accepted (canonical home: `~/kit-army-config/docs/adr-052-two-tier-ecosystem-access-2026-05-20.md`).
87
+
88
+ ## What's next
89
+
90
+ See the open issues / project board (once GitHub repo exists).
91
+
92
+ Immediate roadmap:
93
+ 1. **`windy connect` CLI MVP** — interactive flow, auto-detection of OpenClaw + Ollama + Claude Code + generic, writes configs to the right places
94
+ 2. **`/agents/connect` orchestrator backend** — Sign-in-with-Google → mints Eternitas EPT → provisions Mail mailbox → provisions Chat identity → returns the bundle
95
+ 3. **`get.windyconnect.com` installer** — one-line bootstrap that installs the CLI
96
+ 4. **`windyconnect.com/pair` browser flow** — the page the CLI opens for the user to sign in
97
+ 5. **OpenClaw ClawHub skill** — `windy-access` skill on ClawHub for discoverability inside the OpenClaw community
98
+
99
+ ## Naming
100
+
101
+ - **Product name:** Windy Connect
102
+ - **CLI command:** `windy connect` (subcommand of the `windy` binary; future siblings: `windy status`, `windy disconnect`, `windy upgrade`)
103
+ - **Repo:** `windy-connect` (this repo)
104
+ - **Domain:** windyconnect.com (CLI installer + browser pairing page + marketing)
105
+ - **PyPI package:** `windy-connect`
106
+ - **Dashboard tile:** "Windy Connect"
107
+
108
+ ## License
109
+
110
+ TBD (likely MIT for the CLI and skill, proprietary for backend orchestrator).
@@ -0,0 +1,78 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "windy-connect"
7
+ version = "0.1.0"
8
+ description = "Wire any AI agent into the Windy ecosystem in one command."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Windy" },
14
+ ]
15
+ keywords = ["agent", "ai", "eternitas", "windy", "credentials", "matrix", "imap"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Topic :: Communications :: Chat",
23
+ "Topic :: Communications :: Email",
24
+ "Topic :: Utilities",
25
+ ]
26
+ dependencies = [
27
+ "typer>=0.12.0",
28
+ "rich>=13.7.0",
29
+ "httpx>=0.27.0",
30
+ "pydantic>=2.6.0",
31
+ "platformdirs>=4.0.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.0.0",
37
+ "pytest-asyncio>=0.23.0",
38
+ "ruff>=0.4.0",
39
+ "mypy>=1.10.0",
40
+ ]
41
+
42
+ [project.scripts]
43
+ windy = "windy_connect.cli:app"
44
+
45
+ [project.urls]
46
+ Homepage = "https://windyconnect.com"
47
+ Source = "https://github.com/sneakyfree/windy-connect"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/windy_connect"]
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ include = [
54
+ "src/windy_connect",
55
+ "tests",
56
+ "README.md",
57
+ "pyproject.toml",
58
+ ]
59
+ exclude = [
60
+ "backend",
61
+ "skills",
62
+ "installer",
63
+ "docs",
64
+ "**/node_modules",
65
+ "**/__pycache__",
66
+ "**/.pytest_cache",
67
+ "**/.ruff_cache",
68
+ "**/.mypy_cache",
69
+ "**/.wrangler",
70
+ "**/.dev.vars",
71
+ "**/dist",
72
+ "**/build",
73
+ "**/*.egg-info",
74
+ ]
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py311"
@@ -0,0 +1,8 @@
1
+ """Windy Connect — the one command that wires any AI agent into the Windy ecosystem."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
4
+
5
+ try:
6
+ __version__ = _pkg_version("windy-connect")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0.dev"
@@ -0,0 +1,6 @@
1
+ """Allow `python -m windy_connect` to invoke the CLI."""
2
+
3
+ from .cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,102 @@
1
+ """Inline mock bundle used by ``windy connect --mock``.
2
+
3
+ Lets us exercise the full CLI flow + writers without a live orchestrator backend.
4
+ Replaced by real orchestrator response once that ships.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import json
11
+ from datetime import UTC, datetime, timedelta
12
+
13
+ from .bundle import (
14
+ Bundle,
15
+ EternitasBlock,
16
+ Issuer,
17
+ JmapEndpoint,
18
+ MailBlock,
19
+ MailEndpoint,
20
+ MatrixChat,
21
+ OpenAICompatibleMind,
22
+ )
23
+
24
+
25
+ def make_mock_bundle(*, tier: str = "credentialed") -> Bundle:
26
+ """Synthesize a realistic bundle for local testing."""
27
+ now = datetime.now(UTC)
28
+ expires = now + timedelta(days=30)
29
+
30
+ eternitas: EternitasBlock | None = None
31
+ if tier == "credentialed":
32
+ ept_payload = (
33
+ base64.urlsafe_b64encode(
34
+ json.dumps(
35
+ {"sub": "ET26-MOCK-0001", "exp": int(expires.timestamp())}
36
+ ).encode()
37
+ )
38
+ .rstrip(b"=")
39
+ .decode()
40
+ )
41
+ eternitas = EternitasBlock(
42
+ ept=f"eyJhbGciOiJFUzI1NiIsImtpZCI6Im1vY2sifQ.{ept_payload}.MOCK",
43
+ passport="ET26-MOCK-0001",
44
+ operator_id="op_mock_0000000000000000000000",
45
+ clearance_level="verified",
46
+ integrity_band="fair",
47
+ jwks_url="https://api.eternitas.ai/.well-known/eternitas-keys",
48
+ revocation_check_url="https://api.eternitas.ai/api/v1/passports/ET26-MOCK-0001/status",
49
+ )
50
+
51
+ return Bundle(
52
+ bundle_version="1.0",
53
+ issuer=Issuer(
54
+ name="windy",
55
+ url="https://windyconnect.com",
56
+ icon="https://windyconnect.com/favicon.png",
57
+ ),
58
+ issued_at=now,
59
+ expires_at=expires,
60
+ refresh_url="https://api.windyconnect.com/v1/bundle/refresh",
61
+ eternitas=eternitas,
62
+ windy_chat=MatrixChat(
63
+ kind="matrix",
64
+ homeserver="https://matrix.windychat.ai",
65
+ matrix_user_id="@mock_agent:windychat.ai",
66
+ access_token="syt_mock_access_token_replace_in_real_flow",
67
+ device_id="WINDY_CONNECT_MOCK_DEVICE",
68
+ default_room_id="!mockroom:windychat.ai",
69
+ ),
70
+ windy_mail=MailBlock(
71
+ address="mock_agent@windymail.ai",
72
+ display_name="Mock Agent",
73
+ imap=MailEndpoint(
74
+ host="imap.windymail.ai",
75
+ port=993,
76
+ tls="implicit",
77
+ username="mock_agent@windymail.ai",
78
+ password="mock-app-password-1234",
79
+ ),
80
+ smtp=MailEndpoint(
81
+ host="smtp.windymail.ai",
82
+ port=587,
83
+ tls="starttls",
84
+ username="mock_agent@windymail.ai",
85
+ password="mock-app-password-1234",
86
+ ),
87
+ jmap=JmapEndpoint(
88
+ endpoint="https://jmap.windymail.ai/jmap",
89
+ account_id="u_mock_account",
90
+ username="mock_agent@windymail.ai",
91
+ password="mock-app-password-1234",
92
+ ),
93
+ ),
94
+ windy_mind=OpenAICompatibleMind(
95
+ kind="openai-compatible",
96
+ base_url="https://api.windymind.ai/v1",
97
+ api_key="wm_mock_api_key_replace_in_real_flow",
98
+ default_model="windy-mind-auto",
99
+ models_endpoint="https://api.windymind.ai/v1/models",
100
+ ),
101
+ tier=tier, # type: ignore[arg-type]
102
+ )
@@ -0,0 +1,116 @@
1
+ """Pydantic models for the Eternitas Agent Credentials Bundle.
2
+
3
+ Mirrors ``docs/bundle-spec-v1.md``. Consumers MUST validate ``bundle_version`` and
4
+ ignore unknown fields per the spec's forward-compatibility rule.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from typing import Literal
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field, HttpUrl
13
+
14
+
15
+ class Issuer(BaseModel):
16
+ model_config = ConfigDict(extra="ignore")
17
+
18
+ name: str = Field(description="Stable slug identifying the ecosystem that issued this bundle.")
19
+ url: HttpUrl
20
+ icon: HttpUrl | None = None
21
+
22
+
23
+ class EternitasBlock(BaseModel):
24
+ model_config = ConfigDict(extra="ignore")
25
+
26
+ ept: str = Field(description="The Eternitas Passport Token (ES256 JWT).")
27
+ passport: str = Field(description="Human-readable passport ID (e.g., ET26-K7BF-42MN).")
28
+ operator_id: str
29
+ clearance_level: Literal["registered", "verified", "cleared", "top_secret", "eternal"]
30
+ integrity_band: Literal["critical", "poor", "fair", "good", "exceptional"]
31
+ jwks_url: HttpUrl
32
+ revocation_check_url: HttpUrl | None = None
33
+
34
+
35
+ class MatrixChat(BaseModel):
36
+ model_config = ConfigDict(extra="ignore")
37
+
38
+ kind: Literal["matrix"]
39
+ homeserver: HttpUrl
40
+ matrix_user_id: str = Field(description="Fully-qualified Matrix ID, e.g., @alice:windychat.ai")
41
+ access_token: str
42
+ device_id: str
43
+ default_room_id: str | None = None
44
+
45
+
46
+ class MailEndpoint(BaseModel):
47
+ model_config = ConfigDict(extra="ignore")
48
+
49
+ host: str
50
+ port: int
51
+ tls: Literal["implicit", "starttls", "none"]
52
+ username: str
53
+ password: str
54
+
55
+
56
+ class JmapEndpoint(BaseModel):
57
+ model_config = ConfigDict(extra="ignore")
58
+
59
+ endpoint: HttpUrl
60
+ account_id: str
61
+ username: str
62
+ password: str
63
+
64
+
65
+ class MailBlock(BaseModel):
66
+ model_config = ConfigDict(extra="ignore")
67
+
68
+ address: str
69
+ display_name: str | None = None
70
+ imap: MailEndpoint | None = None
71
+ smtp: MailEndpoint | None = None
72
+ jmap: JmapEndpoint | None = None
73
+
74
+
75
+ class OpenAICompatibleMind(BaseModel):
76
+ model_config = ConfigDict(extra="ignore")
77
+
78
+ kind: Literal["openai-compatible"]
79
+ base_url: HttpUrl
80
+ api_key: str
81
+ default_model: str | None = None
82
+ models_endpoint: HttpUrl | None = None
83
+
84
+
85
+ class Bundle(BaseModel):
86
+ """The full Eternitas Agent Credentials Bundle.
87
+
88
+ Per spec v1, only ``eternitas`` is mandatory among service blocks; the rest are optional.
89
+ Free-tier bundles MAY omit ``eternitas`` entirely (see ADR-052).
90
+ """
91
+
92
+ model_config = ConfigDict(extra="ignore")
93
+
94
+ bundle_version: str = Field(pattern=r"^\d+\.\d+(\.\d+)?(-\w+)?$")
95
+ issuer: Issuer
96
+ issued_at: datetime
97
+ expires_at: datetime
98
+ refresh_url: HttpUrl | None = None
99
+
100
+ eternitas: EternitasBlock | None = None
101
+
102
+ windy_chat: MatrixChat | None = None
103
+ windy_mail: MailBlock | None = None
104
+ windy_mind: OpenAICompatibleMind | None = None
105
+
106
+ tier: Literal["free", "credentialed"]
107
+
108
+ @property
109
+ def is_supported(self) -> bool:
110
+ """v1 consumers handle v1.x bundles; v2+ requires explicit support."""
111
+ major = self.bundle_version.split(".", 1)[0]
112
+ return major == "1"
113
+
114
+ @property
115
+ def is_expired(self) -> bool:
116
+ return datetime.now(self.expires_at.tzinfo) > self.expires_at