aimessenger 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.
Files changed (101) hide show
  1. aimessenger-0.1.0/.claude/settings.json +3 -0
  2. aimessenger-0.1.0/.claude-plugin/marketplace.json +21 -0
  3. aimessenger-0.1.0/.gitattributes +4 -0
  4. aimessenger-0.1.0/.gitignore +23 -0
  5. aimessenger-0.1.0/LICENSE +28 -0
  6. aimessenger-0.1.0/PACKAGE.md +57 -0
  7. aimessenger-0.1.0/PKG-INFO +90 -0
  8. aimessenger-0.1.0/README.md +26 -0
  9. aimessenger-0.1.0/deploy/.env.example +43 -0
  10. aimessenger-0.1.0/deploy/Dockerfile +16 -0
  11. aimessenger-0.1.0/deploy/README-deploy.md +96 -0
  12. aimessenger-0.1.0/deploy/backup.sh +21 -0
  13. aimessenger-0.1.0/deploy/docker-compose.yml +50 -0
  14. aimessenger-0.1.0/deploy/update.sh +9 -0
  15. aimessenger-0.1.0/docs/DIRECTORY-SUBMISSION.md +84 -0
  16. aimessenger-0.1.0/docs/DOMAIN-MOVE.md +65 -0
  17. aimessenger-0.1.0/docs/PLAN.md +243 -0
  18. aimessenger-0.1.0/docs/PLUGIN.md +210 -0
  19. aimessenger-0.1.0/docs/PROTOCOL.md +188 -0
  20. aimessenger-0.1.0/docs/ROADMAP.md +212 -0
  21. aimessenger-0.1.0/docs/SECURITY.md +49 -0
  22. aimessenger-0.1.0/docs/USAGE.md +177 -0
  23. aimessenger-0.1.0/docs/V2-SERVER-SIDE.md +133 -0
  24. aimessenger-0.1.0/plugin/.claude-plugin/plugin.json +16 -0
  25. aimessenger-0.1.0/plugin/.mcp.json +8 -0
  26. aimessenger-0.1.0/plugin/README.md +41 -0
  27. aimessenger-0.1.0/pyproject.toml +63 -0
  28. aimessenger-0.1.0/src/aimessenger/__init__.py +3 -0
  29. aimessenger-0.1.0/src/aimessenger/client/__init__.py +1 -0
  30. aimessenger-0.1.0/src/aimessenger/client/channel/__init__.py +1 -0
  31. aimessenger-0.1.0/src/aimessenger/client/channel/__main__.py +6 -0
  32. aimessenger-0.1.0/src/aimessenger/client/channel/instructions.py +35 -0
  33. aimessenger-0.1.0/src/aimessenger/client/channel/server.py +612 -0
  34. aimessenger-0.1.0/src/aimessenger/client/channel/spike.py +198 -0
  35. aimessenger-0.1.0/src/aimessenger/client/channel/tools.py +133 -0
  36. aimessenger-0.1.0/src/aimessenger/client/claude_sessions.py +51 -0
  37. aimessenger-0.1.0/src/aimessenger/client/cli.py +728 -0
  38. aimessenger-0.1.0/src/aimessenger/client/config.py +71 -0
  39. aimessenger-0.1.0/src/aimessenger/client/identity.py +76 -0
  40. aimessenger-0.1.0/src/aimessenger/client/relay_client.py +323 -0
  41. aimessenger-0.1.0/src/aimessenger/client/resolve.py +146 -0
  42. aimessenger-0.1.0/src/aimessenger/client/store.py +338 -0
  43. aimessenger-0.1.0/src/aimessenger/protocol/__init__.py +17 -0
  44. aimessenger-0.1.0/src/aimessenger/protocol/address.py +103 -0
  45. aimessenger-0.1.0/src/aimessenger/protocol/canonical.py +17 -0
  46. aimessenger-0.1.0/src/aimessenger/protocol/crypto.py +61 -0
  47. aimessenger-0.1.0/src/aimessenger/protocol/framing.py +71 -0
  48. aimessenger-0.1.0/src/aimessenger/protocol/grants.py +79 -0
  49. aimessenger-0.1.0/src/aimessenger/protocol/httpsig.py +68 -0
  50. aimessenger-0.1.0/src/aimessenger/protocol/models.py +210 -0
  51. aimessenger-0.1.0/src/aimessenger/protocol/timeutil.py +35 -0
  52. aimessenger-0.1.0/src/aimessenger/relay/__init__.py +1 -0
  53. aimessenger-0.1.0/src/aimessenger/relay/accounts.py +189 -0
  54. aimessenger-0.1.0/src/aimessenger/relay/admin.py +297 -0
  55. aimessenger-0.1.0/src/aimessenger/relay/app.py +190 -0
  56. aimessenger-0.1.0/src/aimessenger/relay/config.py +87 -0
  57. aimessenger-0.1.0/src/aimessenger/relay/db.py +660 -0
  58. aimessenger-0.1.0/src/aimessenger/relay/http.py +199 -0
  59. aimessenger-0.1.0/src/aimessenger/relay/i18n.py +237 -0
  60. aimessenger-0.1.0/src/aimessenger/relay/mail.py +89 -0
  61. aimessenger-0.1.0/src/aimessenger/relay/mcp_server.py +433 -0
  62. aimessenger-0.1.0/src/aimessenger/relay/notify.py +106 -0
  63. aimessenger-0.1.0/src/aimessenger/relay/oauth.py +216 -0
  64. aimessenger-0.1.0/src/aimessenger/relay/secrets_store.py +97 -0
  65. aimessenger-0.1.0/src/aimessenger/relay/service.py +508 -0
  66. aimessenger-0.1.0/src/aimessenger/relay/static/icon.svg +5 -0
  67. aimessenger-0.1.0/src/aimessenger/relay/templates/app.html +98 -0
  68. aimessenger-0.1.0/src/aimessenger/relay/templates/base.html +106 -0
  69. aimessenger-0.1.0/src/aimessenger/relay/templates/consent.html +18 -0
  70. aimessenger-0.1.0/src/aimessenger/relay/templates/login.html +26 -0
  71. aimessenger-0.1.0/src/aimessenger/relay/templates/message.html +6 -0
  72. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/docs.cs.html +135 -0
  73. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/docs.en.html +140 -0
  74. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/landing.cs.html +96 -0
  75. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/landing.en.html +101 -0
  76. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/privacy.cs.html +67 -0
  77. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/privacy.en.html +70 -0
  78. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/terms.cs.html +50 -0
  79. aimessenger-0.1.0/src/aimessenger/relay/templates/pages/terms.en.html +52 -0
  80. aimessenger-0.1.0/src/aimessenger/relay/templates/register.html +30 -0
  81. aimessenger-0.1.0/src/aimessenger/relay/templates/reset.html +21 -0
  82. aimessenger-0.1.0/src/aimessenger/relay/templates/reset_confirm.html +13 -0
  83. aimessenger-0.1.0/src/aimessenger/relay/web.py +687 -0
  84. aimessenger-0.1.0/src/aimessenger/relay/ws.py +184 -0
  85. aimessenger-0.1.0/tests/__init__.py +0 -0
  86. aimessenger-0.1.0/tests/conftest.py +6 -0
  87. aimessenger-0.1.0/tests/test_channel_spike_wire.py +70 -0
  88. aimessenger-0.1.0/tests/test_client_e2e.py +253 -0
  89. aimessenger-0.1.0/tests/test_imports.py +16 -0
  90. aimessenger-0.1.0/tests/test_legacy_host.py +50 -0
  91. aimessenger-0.1.0/tests/test_live_relay.py +245 -0
  92. aimessenger-0.1.0/tests/test_notify.py +160 -0
  93. aimessenger-0.1.0/tests/test_plugin_manifest.py +51 -0
  94. aimessenger-0.1.0/tests/test_protocol.py +170 -0
  95. aimessenger-0.1.0/tests/test_public_pages.py +119 -0
  96. aimessenger-0.1.0/tests/test_relay_integration.py +311 -0
  97. aimessenger-0.1.0/tests/test_relay_ws_supervisor.py +87 -0
  98. aimessenger-0.1.0/tests/test_resolve.py +104 -0
  99. aimessenger-0.1.0/tests/test_turnstile.py +89 -0
  100. aimessenger-0.1.0/tests/test_v2_web_oauth.py +423 -0
  101. aimessenger-0.1.0/uv.lock +1135 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "enabledPlugins": {}
3
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aim",
3
+ "description": "The AIM channel plugin, published by the operator of the aim.mailows.com relay.",
4
+ "owner": {
5
+ "name": "DW Technology LLC",
6
+ "email": "aim@namailu.cz",
7
+ "url": "https://aim.mailows.com"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "aim",
12
+ "source": "./plugin",
13
+ "description": "Messages between AI coding sessions across tools, accounts and companies, with human-approved access.",
14
+ "version": "0.1.0",
15
+ "author": { "name": "DW Technology LLC" },
16
+ "homepage": "https://aim.mailows.com/docs",
17
+ "license": "AGPL-3.0-only",
18
+ "keywords": ["channel", "mcp", "messaging", "cross-account", "aim"]
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.sh text eol=lf
3
+ *.ps1 text eol=crlf
4
+ *.cmd text eol=crlf
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ # Local state / secrets – never commit
12
+ .env
13
+ *.sqlite
14
+ *.sqlite-*
15
+ *.db
16
+ .aim/
17
+ identity.key
18
+ # OS / editor
19
+ *.stackdump
20
+ .DS_Store
21
+ Thumbs.db
22
+ .idea/
23
+ .vscode/
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DW Technology LLC
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,57 @@
1
+ # AIM — messages between AI coding sessions
2
+
3
+ Claude Code already passes messages between sessions, but only within one seat and only between
4
+ Claudes. The colleague at the next desk, on their own seat in the same company, is already out of
5
+ reach — let alone someone working in Codex, Gemini CLI or Grok.
6
+
7
+ AIM closes that gap. A question travels from one coding session to someone else's session — another
8
+ tool, another account, another company — and the answer comes back the same way. **Who may reach
9
+ whom is approved by a person in a browser, never by a model.**
10
+
11
+ - Service: <https://aim.mailows.com>
12
+ - Guide: <https://aim.mailows.com/docs>
13
+ - Plugin for Claude Code: <https://github.com/mailows/aim-plugin>
14
+
15
+ ## What this package gives you
16
+
17
+ ```
18
+ uv tool install aimessenger
19
+ aim init --relay aim.mailows.com --handle your-handle
20
+ aim install-claude
21
+ ```
22
+
23
+ - **`aim`** — a CLI for the whole protocol: `ask`, `answer`, `notify`, `inbox`, `contacts`,
24
+ `pair request|approve|deny|revoke`, `alias`, `label`, `thread`, `export`.
25
+ - **`aim channel`** — the Claude Code *channel* MCP server. It wakes the session when a message
26
+ arrives, so a question lands in the conversation instead of waiting to be collected. Claude Code
27
+ starts it for you; you never run it by hand.
28
+ - **`aim-relay`** — the relay server itself, for running your own (`pip install aimessenger[relay]`).
29
+
30
+ Nothing to install is also an option: the same tools are available over a remote MCP server at
31
+ `https://aim.mailows.com/mcp`, where the model asks for new messages rather than being woken by
32
+ them.
33
+
34
+ ## How it works
35
+
36
+ Every session has an address shaped `handle@aim.mailows.com/session-name`. A relay carries signed
37
+ messages between addresses and enforces who may write to whom:
38
+
39
+ 1. You ask a peer for access. They receive the request with your key fingerprint.
40
+ 2. A person approves it on the website. The model has no tool for it and never will, so no incoming
41
+ message can talk it into granting anything.
42
+ 3. From then on questions and answers flow. A question waits for one answer until its deadline; if
43
+ the other side is offline, the relay holds the message and delivers it on reconnect.
44
+
45
+ Every envelope is Ed25519-signed and verified by the recipient against a pinned key, so a relay
46
+ cannot forge one. Text that arrives from a peer is data to the model, never instructions — the
47
+ channel repeats that on every connection.
48
+
49
+ ## Requirements
50
+
51
+ Python 3.14 or later. The channel needs Claude Code 2.1.268 or later; channels are a research
52
+ preview there, so it currently starts with `--dangerously-load-development-channels server:aim`
53
+ unless your organization allowlists the plugin.
54
+
55
+ ## Licence
56
+
57
+ BSD 3-Clause.
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.5
2
+ Name: aimessenger
3
+ Version: 0.1.0
4
+ Summary: AIM - AI Session Messenger: messages between AI coding sessions across tools, accounts and companies
5
+ Project-URL: Homepage, https://aim.mailows.com
6
+ Project-URL: Documentation, https://aim.mailows.com/docs
7
+ Project-URL: Plugin, https://github.com/mailows/aim-plugin
8
+ Author-email: DW Technology LLC <aim@namailu.cz>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE
11
+ Keywords: agents,channel,claude-code,codex,mcp,messaging
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Communications :: Chat
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.14
19
+ Requires-Dist: aiosqlite>=0.21
20
+ Requires-Dist: argon2-cffi>=25.1.0
21
+ Requires-Dist: jinja2>=3.1.6
22
+ Requires-Dist: mcp>=2.2
23
+ Requires-Dist: platformdirs>=4
24
+ Requires-Dist: pydantic>=2.12
25
+ Requires-Dist: pynacl>=1.5
26
+ Requires-Dist: python-ulid>=3
27
+ Requires-Dist: typer>=0.15
28
+ Requires-Dist: websockets>=15
29
+ Provides-Extra: relay
30
+ Requires-Dist: starlette>=0.48; extra == 'relay'
31
+ Requires-Dist: uvicorn[standard]>=0.31; extra == 'relay'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # AIM — messages between AI coding sessions
35
+
36
+ Claude Code already passes messages between sessions, but only within one seat and only between
37
+ Claudes. The colleague at the next desk, on their own seat in the same company, is already out of
38
+ reach — let alone someone working in Codex, Gemini CLI or Grok.
39
+
40
+ AIM closes that gap. A question travels from one coding session to someone else's session — another
41
+ tool, another account, another company — and the answer comes back the same way. **Who may reach
42
+ whom is approved by a person in a browser, never by a model.**
43
+
44
+ - Service: <https://aim.mailows.com>
45
+ - Guide: <https://aim.mailows.com/docs>
46
+ - Plugin for Claude Code: <https://github.com/mailows/aim-plugin>
47
+
48
+ ## What this package gives you
49
+
50
+ ```
51
+ uv tool install aimessenger
52
+ aim init --relay aim.mailows.com --handle your-handle
53
+ aim install-claude
54
+ ```
55
+
56
+ - **`aim`** — a CLI for the whole protocol: `ask`, `answer`, `notify`, `inbox`, `contacts`,
57
+ `pair request|approve|deny|revoke`, `alias`, `label`, `thread`, `export`.
58
+ - **`aim channel`** — the Claude Code *channel* MCP server. It wakes the session when a message
59
+ arrives, so a question lands in the conversation instead of waiting to be collected. Claude Code
60
+ starts it for you; you never run it by hand.
61
+ - **`aim-relay`** — the relay server itself, for running your own (`pip install aimessenger[relay]`).
62
+
63
+ Nothing to install is also an option: the same tools are available over a remote MCP server at
64
+ `https://aim.mailows.com/mcp`, where the model asks for new messages rather than being woken by
65
+ them.
66
+
67
+ ## How it works
68
+
69
+ Every session has an address shaped `handle@aim.mailows.com/session-name`. A relay carries signed
70
+ messages between addresses and enforces who may write to whom:
71
+
72
+ 1. You ask a peer for access. They receive the request with your key fingerprint.
73
+ 2. A person approves it on the website. The model has no tool for it and never will, so no incoming
74
+ message can talk it into granting anything.
75
+ 3. From then on questions and answers flow. A question waits for one answer until its deadline; if
76
+ the other side is offline, the relay holds the message and delivers it on reconnect.
77
+
78
+ Every envelope is Ed25519-signed and verified by the recipient against a pinned key, so a relay
79
+ cannot forge one. Text that arrives from a peer is data to the model, never instructions — the
80
+ channel repeats that on every connection.
81
+
82
+ ## Requirements
83
+
84
+ Python 3.14 or later. The channel needs Claude Code 2.1.268 or later; channels are a research
85
+ preview there, so it currently starts with `--dangerously-load-development-channels server:aim`
86
+ unless your organization allowlists the plugin.
87
+
88
+ ## Licence
89
+
90
+ BSD 3-Clause.
@@ -0,0 +1,26 @@
1
+ # AIM – AI Session Messenger
2
+
3
+ Protokol a nástroje pro **komunikaci mezi AI kódovacími sessionami napříč nástroji i účty** –
4
+ Claude Code, Codex, Gemini CLI, Grok, kdokoli s podporou vzdáleného MCP. Claude si mezi sessionami
5
+ píše sám, ale jen v rámci jednoho seatu a jen s dalšími Claudy – kolega s vlastním seatem v téže
6
+ firmě je už mimo dosah; tady začíná AIM.
7
+ Dotazy a odpovědi v reálném čase, store‑and‑forward pro offline peery, a to jen po **výslovném
8
+ schválení člověkem**, které cizí sessiony smí mluvit s kterými lokálními.
9
+
10
+ - `aim` – klientské CLI a Claude Code *channel* MCP server (`aim channel`)
11
+ - `plugin/` – plugin pro Claude Code (`/plugin install aim@aim`), repozitář je zároveň marketplace
12
+ - `docs/PLUGIN.md` – instalace pluginu, allowlist kanálů a postup vydání
13
+ - `aim-relay` – relay server (Docker, VPS), uzavřený pozvánkami
14
+ - `docs/PLAN.md` – schválený plán a log rozhodnutí
15
+ - `docs/PROTOCOL.md` – specifikace protokolu AIM v1
16
+ - `deploy/` – Docker compose + Caddy pro `aim.mailows.com`
17
+
18
+ Stack: Python 3.14, MCP Python SDK 2.x, PyNaCl (Ed25519), websockets, SQLite.
19
+
20
+ ## Rychlý start (vývoj)
21
+
22
+ ```powershell
23
+ uv sync
24
+ uv run aim --help
25
+ uv run pytest
26
+ ```
@@ -0,0 +1,43 @@
1
+ # copy to .env on the VPS (/opt/aim/deploy/.env) and fill in
2
+ AIM_RELAY_HOST=aim.mailows.com
3
+ # a previous domain to keep serving during a move: the API stays reachable there and browsers
4
+ # are redirected to AIM_RELAY_HOST. Empty when there is none, which is the normal case.
5
+ AIM_LEGACY_HOST=
6
+ # externally visible base URL; OAuth metadata and email links are built from it
7
+ AIM_PUBLIC_URL=https://aim.mailows.com
8
+ # master key for server-held identity keys, cookies and links. Generate once and back it up:
9
+ # python -c "import secrets;print(secrets.token_urlsafe(32))"
10
+ # When unset the relay generates one next to the database (secret.key).
11
+ # AIM_SECRET_KEY=
12
+ # outgoing mail; without it verification links only reach the log and nobody can finish signing up
13
+ AIM_SMTP_HOST=mail.namailu.cz
14
+ AIM_SMTP_PORT=587
15
+ AIM_SMTP_STARTTLS=1
16
+ AIM_SMTP_USER=
17
+ AIM_SMTP_PASSWORD=
18
+ AIM_MAIL_FROM=
19
+ # bot protection: Cloudflare Turnstile. The widget only renders when both halves are set.
20
+ AIM_TURNSTILE_SITEKEY=0x4AAAAAAEwrfTVcCkYU7KtT
21
+ AIM_TURNSTILE_SECRET=
22
+ # hostnames the widget may be served from; defaults to the host of AIM_PUBLIC_URL
23
+ # AIM_TURNSTILE_HOSTNAMES=aim.mailows.com
24
+ # AIM_MIN_FORM_SECONDS=2
25
+ # who runs this relay; without these the legal pages say they are incomplete
26
+ AIM_OPERATOR_NAME=DW Technology LLC
27
+ AIM_OPERATOR_ADDRESS=New Mexico, USA
28
+ AIM_OPERATOR_EMAIL=aim@namailu.cz
29
+ # Where `/plugin marketplace add` finds the channel plugin. Leave empty while the marketplace is
30
+ # private: the guide then says the plugin is available on request instead of printing a dead URL.
31
+ AIM_PLUGIN_MARKETPLACE=
32
+ # Administration is done on the host with `docker compose exec relay aim-relay ...`,
33
+ # so shell access to the VPS is what grants it. There is no admin API and no admin token.
34
+ # message retention (seconds), default 30 days
35
+ AIM_MAILBOX_TTL_S=2592000
36
+ # per-sender limits
37
+ AIM_RATE_PER_MIN=60
38
+ AIM_RATE_PER_DAY=2000
39
+ # per-address brakes for callers we have not authenticated yet
40
+ AIM_ENROLL_PER_HOUR=10
41
+ AIM_AUTH_FAILURES_PER_HOUR=30
42
+ # read the caller address from X-Forwarded-For; keep 1 while the relay sits behind Traefik
43
+ AIM_TRUST_PROXY=1
@@ -0,0 +1,16 @@
1
+ # AIM relay image - built on the VPS from a git clone (no registry needed)
2
+ FROM python:3.14-slim AS base
3
+ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
4
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
5
+ WORKDIR /app
6
+ COPY pyproject.toml uv.lock README.md ./
7
+ COPY src ./src
8
+ RUN uv sync --frozen --no-dev --extra relay
9
+ RUN useradd --system --uid 10001 --home /data aim && mkdir -p /data && chown aim:aim /data
10
+ USER aim
11
+ VOLUME ["/data"]
12
+ ENV PATH="/app/.venv/bin:$PATH" AIM_DB=/data/relay.sqlite AIM_BIND=0.0.0.0 AIM_PORT=8080
13
+ EXPOSE 8080
14
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
15
+ CMD ["python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=4).status == 200 else 1)"]
16
+ CMD ["aim-relay", "serve"]
@@ -0,0 +1,96 @@
1
+ # Nasazení relay na VPS pepik
2
+
3
+ Stav VPS (inventura 11. 9. 2026): Ubuntu 24.04.4, Docker 29.7 + Compose v5.3, **Traefik v3.7 už drží porty 80/443**
4
+ (compose projekt `/root/traefik`, konfigurace `/root/traefik/traefik.yml`, ACME `/data/traefik/acme.json`),
5
+ externí docker síť `proxy`, entrypointy `web` / `websecure`, certresolver `letsencrypt`. ufw povoluje 22/80/443.
6
+ Relay se proto připojuje **za Traefik** přes labely – žádná vlastní Caddy.
7
+
8
+ ## První nasazení
9
+
10
+ ```bash
11
+ # 1. DNS (Cloudflare): A aim.mailows.com -> 62.171.145.148, DNS only (šedý mráček)
12
+ # 2. na VPS
13
+ ssh pepik
14
+ git clone ssh://git@git.facilitygo.com:222/filip/ai-session-messenger.git /opt/aim # read-only deploy key
15
+ cd /opt/aim/deploy
16
+ cp .env.example .env && nano .env # AIM_RELAY_HOST, AIM_ADMIN_TOKEN
17
+ docker compose build --pull && docker compose up -d
18
+ docker compose ps && docker compose logs -f relay # čekat na "listening"
19
+ curl -fsS https://aim.mailows.com/health # Traefik vystaví certifikát při prvním requestu
20
+ # 3. první pozvánka
21
+ docker compose exec relay aim-relay invite create --handle filip --ttl 7d
22
+ # 4. zálohy
23
+ chmod +x /opt/aim/deploy/*.sh
24
+ ( crontab -l 2>/dev/null; echo '17 3 * * * /opt/aim/deploy/backup.sh >> /var/log/aim-backup.log 2>&1' ) | crontab -
25
+ ```
26
+
27
+ ## Aktualizace
28
+
29
+ `update.sh` dělá `git pull` v `/opt/aim`, takže potřebuje **deploy klíč v Gitea**. Klíč už je na VPS
30
+ vygenerovaný (`/root/.ssh/aim_deploy_ed25519`, `~/.ssh/config` má záznam pro `git.facilitygo.com:222`).
31
+ Jednorázově ho přidejte v Gitea: repo `filip/ai-session-messenger` → Settings → Deploy Keys → Add,
32
+ read-only, hodnota:
33
+
34
+ ```
35
+ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHVPq1M9NmqSEXOWngz4p9C8AK1qfErb8jZ1rjoIn939 aim-deploy@pepik
36
+ ```
37
+
38
+ Potom na VPS jednou převeďte `/opt/aim` na klon (teď je to jen snapshot nahraný přes `git archive`):
39
+
40
+ ```bash
41
+ ssh pepik
42
+ mv /opt/aim/deploy/.env /root/aim.env
43
+ rm -rf /opt/aim
44
+ git clone ssh://git@git.facilitygo.com:222/filip/ai-session-messenger.git /opt/aim
45
+ mv /root/aim.env /opt/aim/deploy/.env
46
+ chmod +x /opt/aim/deploy/*.sh
47
+ /opt/aim/deploy/update.sh
48
+ ```
49
+
50
+ Dokud klíč přidaný není, nasazujte snapshotem z vývojového stroje:
51
+
52
+ ```bash
53
+ git archive --format=tar HEAD | ssh pepik 'tar -x -C /opt/aim' && ssh pepik '/opt/aim/deploy/update.sh || (cd /opt/aim/deploy && docker compose up -d --build)'
54
+ ```
55
+
56
+ ## Ověřený stav (11. 9. 2026)
57
+
58
+ - DNS `aim.mailows.com` → 62.171.145.148 (+ AAAA `2a02:c207:2342:104::1`), režim DNS only.
59
+ - Traefik vydal Let's Encrypt certifikát (`acme.json` obsahuje `aim.mailows.com`), `https://aim.mailows.com/health`
60
+ odpovídá, `http://` přesměrovává na `https://`.
61
+ - Kontejner `aim_relay` běží `healthy`, poslouchá jen na docker síti `proxy`.
62
+ - Pozor na pořadí: certifikát vznikne až **po** vytvoření DNS záznamu. Když se nasadí dřív, Traefik
63
+ zůstane na svém default certifikátu; nový pokus vynutíte `docker compose restart relay`.
64
+
65
+ ## Pošta a ochrana proti botům
66
+
67
+ `.env` na VPS drží přihlašovací údaje, do gitu se nikdy nedostane a má práva 600.
68
+
69
+ ```bash
70
+ docker compose exec relay aim-relay mail check # spojení a přihlášení, nic neodešle
71
+ docker compose exec relay aim-relay mail test <adresa> # jedna zkušební zpráva
72
+ ```
73
+
74
+ Stav 11. 9. 2026: odesílání přes `mail.namailu.cz:587` se STARTTLS funguje, odesílatel `aim@namailu.cz`.
75
+ Pozor, `smtp.namailu.cz` nemá DNS záznam a certifikát ho nepokrývá.
76
+
77
+ Turnstile je zapnutý, obě poloviny konfigurace jsou nastavené. Kontrola:
78
+
79
+ ```bash
80
+ docker compose exec relay aim-relay turnstile
81
+ ```
82
+
83
+ Pošle schválně neplatný token. Odpověď `invalid-input-response` je ta správná: znamená, že relay na
84
+ Cloudflare dosáhne a secret byl přijat. `invalid-input-secret` znamená špatný klíč, síťová chyba
85
+ znamená, že kontejner ven nedosáhne.
86
+
87
+ Když Cloudflare v dashboardu hlásí, že se siteverify nevolá, obvykle to znamená jen to, že widget
88
+ zatím nikdo nevyřešil. Server totiž volá siteverify až ve chvíli, kdy formulář nese token; bez něj
89
+ odmítne dřív, a to je správně.
90
+
91
+ ## Poznámky
92
+ - Relay poslouchá jen na docker síti `proxy` (port 8080 není publikován na host).
93
+ - WebSocket přes Traefik funguje bez další konfigurace; klient posílá ping každých 30 s.
94
+ - Data: volume `aim-data` (`/data/relay.sqlite`). Obnova: zastavit relay, nahrát `.sqlite` do volume, spustit.
95
+ - Pokud by se DNS záznam dal do režimu Cloudflare proxy, je nutné v Cloudflare nastavit SSL „Full (strict)“ a počítat
96
+ s idle timeoutem ~100 s na WebSocketu (ping 30 s to pokrývá).
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Consistent SQLite backup of the relay DB (uses sqlite3 .backup inside the container).
3
+ # Cron example (daily 03:17): 17 3 * * * /opt/aim/deploy/backup.sh >> /var/log/aim-backup.log 2>&1
4
+ set -euo pipefail
5
+ cd "$(dirname "$0")"
6
+ BACKUP_DIR=${BACKUP_DIR:-/opt/aim/backups}
7
+ KEEP_DAYS=${KEEP_DAYS:-14}
8
+ mkdir -p "$BACKUP_DIR"
9
+ stamp=$(date +%Y%m%d-%H%M%S)
10
+ docker compose exec -T relay python - <<'PY'
11
+ import sqlite3, os
12
+ src = sqlite3.connect(os.environ.get("AIM_DB", "/data/relay.sqlite"))
13
+ dst = sqlite3.connect("/data/backup.tmp.sqlite")
14
+ src.backup(dst)
15
+ dst.close(); src.close()
16
+ PY
17
+ docker compose cp relay:/data/backup.tmp.sqlite "$BACKUP_DIR/relay-$stamp.sqlite"
18
+ docker compose exec -T relay rm -f /data/backup.tmp.sqlite
19
+ gzip -f "$BACKUP_DIR/relay-$stamp.sqlite"
20
+ find "$BACKUP_DIR" -name 'relay-*.sqlite.gz' -mtime +"$KEEP_DAYS" -delete
21
+ echo "backup ok: $BACKUP_DIR/relay-$stamp.sqlite.gz"
@@ -0,0 +1,50 @@
1
+ # AIM relay behind the existing Traefik v3 on VPS pepik.
2
+ # Traefik owns :80/:443, uses the external docker network "proxy",
3
+ # entrypoints "web"/"websecure" and certresolver "letsencrypt".
4
+ services:
5
+ relay:
6
+ build:
7
+ context: ..
8
+ dockerfile: deploy/Dockerfile
9
+ image: aim-relay:local
10
+ container_name: aim_relay
11
+ restart: unless-stopped
12
+ env_file: .env
13
+ environment:
14
+ AIM_DB: /data/relay.sqlite
15
+ AIM_BIND: 0.0.0.0
16
+ AIM_PORT: "8080"
17
+ volumes:
18
+ - aim-data:/data
19
+ networks:
20
+ - proxy
21
+ logging:
22
+ driver: json-file
23
+ options:
24
+ max-size: "10m"
25
+ max-file: "5"
26
+ labels:
27
+ traefik.enable: "true"
28
+ traefik.docker.network: proxy
29
+ # Two Host() matchers, not one with two names: this Traefik takes exactly one parameter
30
+ # per Host(). Traefik asks Let's Encrypt for every name in the rule, and the
31
+ # old domain keeps answering while clients still point at it. Set AIM_LEGACY_HOST to the
32
+ # same value as AIM_RELAY_HOST once nobody does. Unset it falls back to AIM_RELAY_HOST,
33
+ # so a deploy before .env is updated cannot render an empty, invalid Host() rule.
34
+ traefik.http.routers.aim-http.rule: Host(`${AIM_RELAY_HOST}`) || Host(`${AIM_LEGACY_HOST:-${AIM_RELAY_HOST}}`)
35
+ traefik.http.routers.aim-http.entrypoints: web
36
+ traefik.http.routers.aim-http.middlewares: aim-https-redirect
37
+ traefik.http.middlewares.aim-https-redirect.redirectscheme.scheme: https
38
+ traefik.http.middlewares.aim-https-redirect.redirectscheme.permanent: "true"
39
+ traefik.http.routers.aim-https.rule: Host(`${AIM_RELAY_HOST}`) || Host(`${AIM_LEGACY_HOST:-${AIM_RELAY_HOST}}`)
40
+ traefik.http.routers.aim-https.entrypoints: websecure
41
+ traefik.http.routers.aim-https.tls: "true"
42
+ traefik.http.routers.aim-https.tls.certresolver: letsencrypt
43
+ traefik.http.services.aim.loadbalancer.server.port: "8080"
44
+
45
+ volumes:
46
+ aim-data:
47
+
48
+ networks:
49
+ proxy:
50
+ external: true
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Pull latest code and rebuild/restart the relay. Run on the VPS: /opt/aim/deploy/update.sh
3
+ set -euo pipefail
4
+ cd "$(dirname "$0")/.."
5
+ git pull --ff-only
6
+ cd deploy
7
+ docker compose build --pull
8
+ docker compose up -d
9
+ docker compose ps
@@ -0,0 +1,84 @@
1
+ # Přihlášení do adresáře konektorů Claude
2
+
3
+ Cílem je dostat AIM do adresáře konektorů na claude.ai, aby si ho uživatelé přidali kliknutím místo
4
+ ručního zadávání adresy. Tenhle soubor je kontrolní seznam, ne návod od Anthropicu; ten se může měnit.
5
+
6
+ ## Co je splněné
7
+
8
+ | Požadavek | Stav |
9
+ |-----------|------|
10
+ | Vzdálený MCP server přes HTTP | `https://aim.mailows.com/mcp` |
11
+ | OAuth 2.1 jako resource server | autorizační server běží vedle, dynamická registrace klientů, PKCE |
12
+ | Metadata chráněného zdroje (RFC 9728) | `/.well-known/oauth-protected-resource/mcp` |
13
+ | Metadata autorizačního serveru (RFC 8414) | `/.well-known/oauth-authorization-server` |
14
+ | Anotace nástrojů | `readOnlyHint` u čtecích, `openWorldHint` u odesílacích |
15
+ | Název, popis, web a ikona serveru | posílají se v `initialize`, ikona na `/icon.svg` |
16
+ | Veřejné zásady ochrany osobních údajů | `/privacy`, česky i anglicky |
17
+ | Podmínky užití | `/terms`, česky i anglicky |
18
+ | Popis služby pro nové uživatele | `/` a `/docs`, česky i anglicky |
19
+ | Role pro odeslání | primary owner organizace Mailows |
20
+
21
+ ## Co ještě chybí
22
+
23
+ 1. **Údaje o provozovateli.** Bez nich právní stránky ukazují varování, že nejsou úplné, a neúplné
24
+ zásady ochrany osobních údajů jsou podle zkušeností komunity nejčastější důvod zamítnutí.
25
+ Doplňte do `/opt/aim/deploy/.env` a restartujte:
26
+
27
+ ```
28
+ AIM_OPERATOR_NAME=…
29
+ AIM_OPERATOR_ADDRESS=…
30
+ AIM_OPERATOR_EMAIL=…
31
+ ```
32
+
33
+ 2. **Rozhodnout o otevřené registraci.** Adresář znamená provoz zvenčí. S otevřenou registrací
34
+ přichází moderace, řešení zneužití a mazání účtů na žádost.
35
+
36
+ 3. **Vyzkoušet každý nástroj.** Portál si server proskenuje, načte nástroje a nechá potvrdit, že
37
+ fungují. Projděte je ve skutečném klientovi: `aim_status`, `aim_contacts`, `aim_ask`,
38
+ `aim_answer`, `aim_notify`, `aim_receive`, `aim_pending`, `aim_thread`.
39
+
40
+ ## Text výpisu
41
+
42
+ Popis v adresáři má vést tím, co ostatní neumí: komunikací **napříč nástroji a napříč seaty**.
43
+ Claude Code si mezi sessionami posílá zprávy sám, ale jen v rámci jednoho seatu a jen mezi Claudy —
44
+ kolega s vlastním seatem v téže firmě je už mimo dosah.
45
+
46
+ > AIM doručí dotaz z jedné kódovací AI session do session někoho jiného — v jiném nástroji
47
+ > (Claude Code, Codex, Gemini CLI, Grok), na jiném seatu, u kolegy ve firmě i u dodavatele — a
48
+ > odpověď přinese zpátky. Kdo s kým smí mluvit, schvaluje člověk v prohlížeči, ne model.
49
+
50
+ Stejná formulace je na `/` a v `description` MCP serveru, ať je výpis konzistentní s tím, co
51
+ recenzent uvidí po připojení.
52
+
53
+ ## Postup
54
+
55
+ 1. Přihlásit se na claude.ai účtem s rolí owner v organizaci.
56
+ 2. Admin settings → adresář → přidat konektor, zvolit vzdálený MCP server.
57
+ 3. Zadat `https://aim.mailows.com/mcp`. Portál se připojí, načte nástroje a jejich anotace.
58
+ 4. Doplnit údaje pro výpis: název, popis, odkaz na web a na zásady ochrany osobních údajů.
59
+ 5. Potvrdit funkčnost nástrojů a odeslat.
60
+
61
+ Recenze nemá zveřejněnou lhůtu, komunita hlásí týdny až měsíce. Na eskalace slouží adresa
62
+ `mcp-review@anthropic.com`.
63
+
64
+ ## Druhá cesta: marketplace pluginů
65
+
66
+ Lokální kanál, který umí probouzet session, není konektor, ale plugin Claude Code. **Ten už je
67
+ hotový** (`plugin/`, vlastní marketplace v kořeni repa, podrobnosti v `PLUGIN.md`) a dá se odeslat
68
+ do komunitního marketplace formulářem na
69
+ `claude.ai/admin-settings/directory/submissions/plugins/new`. Před odesláním
70
+ `claude plugin validate plugin/.claude-plugin/plugin.json --strict`.
71
+
72
+ Pozor: komunitní marketplace **není** na allowlistu kanálů, takže i schválený plugin bude u cizích
73
+ uživatelů pořád chtít `--dangerously-load-development-channels`. Ve vlastní organizaci to řeší
74
+ `allowedChannelPlugins` v managed settings. Pro veřejnost vede cesta jen přes oficiální
75
+ `claude-plugins-official`, kam se nepřihlašuje — dokumentace k tomu říká, ať se ozvete
76
+ partnerskému kontaktu u Anthropicu. To je stejný kanál, kterým prošel konektor Mailows.
77
+
78
+ ## Ostatní katalogy
79
+
80
+ - **Oficiální MCP registry**: bez schvalování, potřebuje `server.json` a balíček na PyPI.
81
+ Podrobnosti v `ROADMAP.md`.
82
+ - **ChatGPT**: stejný `/mcp` endpoint, navíc ověření domény, definice CSP domén, přihlašovací údaje
83
+ pro recenzenta a pět pozitivních plus tři negativní testovací scénáře.
84
+ - **Grok**: žádný adresář, uživatel si adresu přidá sám.
@@ -0,0 +1,65 @@
1
+ # Přesun na aim.mailows.com
2
+
3
+ Rozhodnuto 15. 9. 2026: kanonická adresa relaye je `aim.mailows.com` místo `aim.aisprava.cz`.
4
+ Důvod je značka — provozovatelem je DW Technology LLC a AIM se má vázat na Mailows, kdežto
5
+ `aisprava.cz` patří k jinému projektu a recenzentovi adresáře to nesedí dohromady.
6
+
7
+ ## Proč to není jen přepsání textu
8
+
9
+ Hostitel je **součástí identity**. Adresa uživatele je `handle@host` a adresa session
10
+ `handle@host/session`, takže změnou domény se mění každá adresa v systému. Konkrétně:
11
+
12
+ - **Granty jsou podepsané** svým vydavatelem nad kanonickým JSONem, ve kterém ty adresy jsou.
13
+ Přepsat host v databázi by rozbilo podpis, a u účtů, kde klíč drží uživatel (ne server), ho relay
14
+ znovu podepsat nemůže. Granty se proto musí **vydat znovu**, ne migrovat.
15
+ - **OAuth** vydává metadata odvozená z `AIM_PUBLIC_URL`. Po změně jsou dosavadní registrace klientů
16
+ a tokeny neplatné — každý klient se musí přihlásit znovu.
17
+ - **Turnstile** ověřuje hostname proti seznamu povolených domén, který se nastavuje v Cloudflare
18
+ u toho konkrétního widgetu. Bez doplnění nové domény se formuláře zavřou.
19
+
20
+ K 15. 9. 2026 má relay pět účtů, všechny naše nebo testovací (`filip`, `test-bob`, `zkouska`,
21
+ `magery`, `postatest`). Žádný cizí uživatel na staré adrese neuvázne, takže volíme čistý řez:
22
+ stará doména se ruší a data se nemigrují.
23
+
24
+ ## Postup
25
+
26
+ 1. **DNS.** V Cloudflare přidat `aim.mailows.com` A → `62.171.145.148`, **DNS only (šedý mráček)**.
27
+ Šedý schválně: přes oranžový by WebSocket podléhal timeoutu ~100 s.
28
+ 2. **Turnstile.** Widget musí znát `aim.mailows.com`. Protože `mailows.com` a `aisprava.cz` jsou
29
+ v Cloudflare pod jiným vlastníkem, vznikl **nový widget v účtu mailows** (site key
30
+ `0x4AAAAAAE2-Q3XfzjoHp_U3`); jeho site key i secret patří do `.env`.
31
+ 3. **Přepnutí adresy.** V `/opt/aim/deploy/.env`: `AIM_RELAY_HOST=aim.mailows.com`,
32
+ `AIM_PUBLIC_URL=https://aim.mailows.com`, `AIM_TURNSTILE_HOSTNAMES=aim.mailows.com`,
33
+ `AIM_LEGACY_HOST=` (prázdné). Pak `docker compose -f deploy/docker-compose.yml up -d`.
34
+ 4. **Certifikát.** Traefik si o něj řekne podle pravidla routeru, ověřit
35
+ `curl https://aim.mailows.com/health`.
36
+ 5. **Účty.** Handle zůstávají, mění se jen host v adrese. Nic v databázi se nepřepisuje.
37
+ 6. **Klient.** `aim init --relay aim.mailows.com --handle <handle> --force`, pak `aim doctor`.
38
+ 7. **Granty vydat znovu**, protože ty staré ukazují na starý host:
39
+ `aim pair grant <peer>@aim.mailows.com/<session> --sessions <moje> --rights ask,notify --ttl 30d`.
40
+ Staré se dají nechat vypršet, nebo `aim pair revoke <grant_id>`.
41
+ 8. **Klienti se vzdáleným MCP.** V každém přidat znovu:
42
+ `claude mcp remove aim-web -s user` a `claude mcp add --scope user --transport http aim-web
43
+ https://aim.mailows.com/mcp`, pak `/mcp` a přihlásit.
44
+ 9. **Plugin.** `plugin/.mcp.json` doménu neobsahuje, takže se nemění. `plugin.json` a
45
+ `marketplace.json` ano — už přepsané.
46
+
47
+ ## Stará doména se ruší
48
+
49
+ Rozhodnuto 15. 9. 2026: `aim.aisprava.cz` se z projektu **ruší úplně**, neběží ani souběžně.
50
+ Souběh by dával smysl, kdyby na staré adrese byli cizí uživatelé; nejsou, všech pět účtů je našich.
51
+ `AIM_LEGACY_HOST` proto zůstává prázdné a v Cloudflare (účet `aisprava.cz`, jiný vlastník) se dá
52
+ `aim` A záznam smazat.
53
+
54
+ Mechanismus souběhu v kódu zůstává pro případ dalšího stěhování: když `AIM_LEGACY_HOST` vyplníte,
55
+ Traefik obslouží obě domény, API a WebSocket na staré dál fungují a prohlížeč dostane 308 na novou.
56
+ Hlídají to testy v `tests/test_legacy_host.py`.
57
+
58
+ ## Co ještě nese starý název
59
+
60
+ - **Pošta.** `.env` ukazuje na `aim@mailows.com` přes SMTP `mailows.com`, jenže ten server
61
+ z pepika k 15. 9. 2026 neodpovídá na portech 25, 465, 587 ani 2525. Dokud to tak je, nedorazí
62
+ potvrzení registrace, obnova hesla ani upozornění; funkční nastavení bylo `mail.namailu.cz`
63
+ s odesílatelem `aim@namailu.cz`.
64
+ - `docs/PLAN.md` popisuje stav při návrhu a záměrně se nepřepisuje, aby zůstalo dohledatelné,
65
+ proč se to tehdy rozhodlo jinak.