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.
- windy_connect-0.1.0/.gitignore +54 -0
- windy_connect-0.1.0/PKG-INFO +140 -0
- windy_connect-0.1.0/README.md +110 -0
- windy_connect-0.1.0/pyproject.toml +78 -0
- windy_connect-0.1.0/src/windy_connect/__init__.py +8 -0
- windy_connect-0.1.0/src/windy_connect/__main__.py +6 -0
- windy_connect-0.1.0/src/windy_connect/_mock_bundle.py +102 -0
- windy_connect-0.1.0/src/windy_connect/bundle.py +116 -0
- windy_connect-0.1.0/src/windy_connect/cli.py +432 -0
- windy_connect-0.1.0/src/windy_connect/detect.py +126 -0
- windy_connect-0.1.0/src/windy_connect/doctor.py +164 -0
- windy_connect-0.1.0/src/windy_connect/orchestrator.py +128 -0
- windy_connect-0.1.0/src/windy_connect/state.py +81 -0
- windy_connect-0.1.0/src/windy_connect/writers/__init__.py +29 -0
- windy_connect-0.1.0/src/windy_connect/writers/base.py +112 -0
- windy_connect-0.1.0/src/windy_connect/writers/claude_code.py +48 -0
- windy_connect-0.1.0/src/windy_connect/writers/generic.py +51 -0
- windy_connect-0.1.0/src/windy_connect/writers/openclaw.py +155 -0
- windy_connect-0.1.0/tests/__init__.py +0 -0
- windy_connect-0.1.0/tests/conftest.py +33 -0
- windy_connect-0.1.0/tests/test_bundle.py +71 -0
- windy_connect-0.1.0/tests/test_doctor.py +171 -0
- windy_connect-0.1.0/tests/test_lifecycle.py +120 -0
- windy_connect-0.1.0/tests/test_state.py +84 -0
- windy_connect-0.1.0/tests/test_writers.py +211 -0
|
@@ -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,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
|