aistack-mcp 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 (99) hide show
  1. aistack_mcp-0.1.0/LICENSE +21 -0
  2. aistack_mcp-0.1.0/PKG-INFO +436 -0
  3. aistack_mcp-0.1.0/README.md +412 -0
  4. aistack_mcp-0.1.0/pyproject.toml +59 -0
  5. aistack_mcp-0.1.0/src/aistack/__init__.py +0 -0
  6. aistack_mcp-0.1.0/src/aistack/api/__init__.py +1 -0
  7. aistack_mcp-0.1.0/src/aistack/api/dashboard.py +407 -0
  8. aistack_mcp-0.1.0/src/aistack/bootstrap/__init__.py +0 -0
  9. aistack_mcp-0.1.0/src/aistack/bootstrap/__main__.py +250 -0
  10. aistack_mcp-0.1.0/src/aistack/bootstrap/configuration/__init__.py +0 -0
  11. aistack_mcp-0.1.0/src/aistack/bootstrap/configuration/settings/__init__.py +0 -0
  12. aistack_mcp-0.1.0/src/aistack/bootstrap/configuration/settings/settings_config.py +178 -0
  13. aistack_mcp-0.1.0/src/aistack/bootstrap/context/__init__.py +0 -0
  14. aistack_mcp-0.1.0/src/aistack/bootstrap/context/application_context.py +311 -0
  15. aistack_mcp-0.1.0/src/aistack/bootstrap/github_setup.py +143 -0
  16. aistack_mcp-0.1.0/src/aistack/bootstrap/self_update.py +91 -0
  17. aistack_mcp-0.1.0/src/aistack/bundled/__init__.py +39 -0
  18. aistack_mcp-0.1.0/src/aistack/bundled/dashboard.html +2746 -0
  19. aistack_mcp-0.1.0/src/aistack/bundled/local/setup.md +104 -0
  20. aistack_mcp-0.1.0/src/aistack/bundled/local/sync-aistack/SKILL.md +61 -0
  21. aistack_mcp-0.1.0/src/aistack/bundled/setup-aistack.md +124 -0
  22. aistack_mcp-0.1.0/src/aistack/bundled/skills/sync-aistack/SKILL.md +145 -0
  23. aistack_mcp-0.1.0/src/aistack/commons/__init__.py +0 -0
  24. aistack_mcp-0.1.0/src/aistack/commons/exceptions/__init__.py +41 -0
  25. aistack_mcp-0.1.0/src/aistack/commons/text.py +49 -0
  26. aistack_mcp-0.1.0/src/aistack/commons/urls.py +12 -0
  27. aistack_mcp-0.1.0/src/aistack/db/__init__.py +0 -0
  28. aistack_mcp-0.1.0/src/aistack/db/engine.py +71 -0
  29. aistack_mcp-0.1.0/src/aistack/db/migrations/env.py +17 -0
  30. aistack_mcp-0.1.0/src/aistack/db/migrations/script.py.mako +16 -0
  31. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0001_baseline.py +94 -0
  32. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0002_teams.py +240 -0
  33. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0003_harnesses.py +52 -0
  34. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0004_can_invite.py +15 -0
  35. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0005_no_server_team.py +61 -0
  36. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0006_invites.py +35 -0
  37. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0007_schema_compatibility.py +24 -0
  38. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0008_library_event.py +25 -0
  39. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0009_machine_skill_revision.py +17 -0
  40. aistack_mcp-0.1.0/src/aistack/db/migrations/versions/0010_user_full_name.py +16 -0
  41. aistack_mcp-0.1.0/src/aistack/db/models/__init__.py +48 -0
  42. aistack_mcp-0.1.0/src/aistack/db/models/base.py +9 -0
  43. aistack_mcp-0.1.0/src/aistack/db/models/harness.py +24 -0
  44. aistack_mcp-0.1.0/src/aistack/db/models/invite.py +35 -0
  45. aistack_mcp-0.1.0/src/aistack/db/models/library_event.py +31 -0
  46. aistack_mcp-0.1.0/src/aistack/db/models/machine.py +40 -0
  47. aistack_mcp-0.1.0/src/aistack/db/models/machine_harness.py +36 -0
  48. aistack_mcp-0.1.0/src/aistack/db/models/machine_skill.py +45 -0
  49. aistack_mcp-0.1.0/src/aistack/db/models/operating_system.py +13 -0
  50. aistack_mcp-0.1.0/src/aistack/db/models/schema_compatibility.py +28 -0
  51. aistack_mcp-0.1.0/src/aistack/db/models/skill.py +48 -0
  52. aistack_mcp-0.1.0/src/aistack/db/models/skill_file.py +34 -0
  53. aistack_mcp-0.1.0/src/aistack/db/models/skill_version.py +45 -0
  54. aistack_mcp-0.1.0/src/aistack/db/models/team.py +140 -0
  55. aistack_mcp-0.1.0/src/aistack/db/models/timestamps.py +21 -0
  56. aistack_mcp-0.1.0/src/aistack/db/models/user.py +34 -0
  57. aistack_mcp-0.1.0/src/aistack/db/schema.py +182 -0
  58. aistack_mcp-0.1.0/src/aistack/mcp/__init__.py +0 -0
  59. aistack_mcp-0.1.0/src/aistack/mcp/auth/__init__.py +0 -0
  60. aistack_mcp-0.1.0/src/aistack/mcp/auth/aistack_token_verifier.py +92 -0
  61. aistack_mcp-0.1.0/src/aistack/mcp/auth/policy.py +53 -0
  62. aistack_mcp-0.1.0/src/aistack/mcp/auth/principal.py +27 -0
  63. aistack_mcp-0.1.0/src/aistack/mcp/auth/scopes.py +9 -0
  64. aistack_mcp-0.1.0/src/aistack/mcp/local.py +14 -0
  65. aistack_mcp-0.1.0/src/aistack/mcp/local_dashboard.py +52 -0
  66. aistack_mcp-0.1.0/src/aistack/mcp/mcp.py +86 -0
  67. aistack_mcp-0.1.0/src/aistack/mcp/mcp_error_handler.py +41 -0
  68. aistack_mcp-0.1.0/src/aistack/mcp/routes/__init__.py +0 -0
  69. aistack_mcp-0.1.0/src/aistack/mcp/routes/health.py +35 -0
  70. aistack_mcp-0.1.0/src/aistack/mcp/routes/invite.py +77 -0
  71. aistack_mcp-0.1.0/src/aistack/mcp/routes/route_error_handler.py +63 -0
  72. aistack_mcp-0.1.0/src/aistack/mcp/routes/setup.py +38 -0
  73. aistack_mcp-0.1.0/src/aistack/mcp/routes/transfer.py +116 -0
  74. aistack_mcp-0.1.0/src/aistack/mcp/server_url.py +42 -0
  75. aistack_mcp-0.1.0/src/aistack/mcp/tools/__init__.py +0 -0
  76. aistack_mcp-0.1.0/src/aistack/mcp/tools/local.py +307 -0
  77. aistack_mcp-0.1.0/src/aistack/mcp/tools/onboarding.py +173 -0
  78. aistack_mcp-0.1.0/src/aistack/mcp/tools/skills.py +350 -0
  79. aistack_mcp-0.1.0/src/aistack/mcp/tools/sync.py +163 -0
  80. aistack_mcp-0.1.0/src/aistack/mcp/tools/teams.py +183 -0
  81. aistack_mcp-0.1.0/src/aistack/services/__init__.py +0 -0
  82. aistack_mcp-0.1.0/src/aistack/services/content_hash.py +50 -0
  83. aistack_mcp-0.1.0/src/aistack/services/dashboard_service.py +234 -0
  84. aistack_mcp-0.1.0/src/aistack/services/harnesses.py +72 -0
  85. aistack_mcp-0.1.0/src/aistack/services/library_log.py +231 -0
  86. aistack_mcp-0.1.0/src/aistack/services/library_repo.py +262 -0
  87. aistack_mcp-0.1.0/src/aistack/services/library_sharing.py +64 -0
  88. aistack_mcp-0.1.0/src/aistack/services/local_identity.py +43 -0
  89. aistack_mcp-0.1.0/src/aistack/services/local_skills.py +197 -0
  90. aistack_mcp-0.1.0/src/aistack/services/onboarding_service.py +223 -0
  91. aistack_mcp-0.1.0/src/aistack/services/reconciliation.py +118 -0
  92. aistack_mcp-0.1.0/src/aistack/services/skill_archive.py +266 -0
  93. aistack_mcp-0.1.0/src/aistack/services/skill_service.py +568 -0
  94. aistack_mcp-0.1.0/src/aistack/services/skill_validation.py +454 -0
  95. aistack_mcp-0.1.0/src/aistack/services/sync_service.py +357 -0
  96. aistack_mcp-0.1.0/src/aistack/services/team_service.py +518 -0
  97. aistack_mcp-0.1.0/src/aistack/services/token_service.py +31 -0
  98. aistack_mcp-0.1.0/src/aistack/services/transfer_ticket.py +185 -0
  99. aistack_mcp-0.1.0/src/aistack/services/visibility.py +178 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dimitris Christopoulos
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,436 @@
1
+ Metadata-Version: 2.4
2
+ Name: aistack-mcp
3
+ Version: 0.1.0
4
+ Summary: A self-hosted home for your AI coding skills, shared with your team over MCP.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Author: dchristopoulos
8
+ Author-email: dimitrisgr56@gmail.com
9
+ Requires-Python: >=3.13,<4.0
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Programming Language :: Python :: 3.15
14
+ Requires-Dist: PyJWT (>=2.13.0,<3.0.0)
15
+ Requires-Dist: PyYAML (>=6.0.3,<7.0.0)
16
+ Requires-Dist: SQLAlchemy (>=2.0.52,<3.0.0)
17
+ Requires-Dist: alembic (>=1.16,<2.0)
18
+ Requires-Dist: fastmcp (==3.4.7)
19
+ Requires-Dist: psycopg[binary] (>=3.3.4,<4.0.0)
20
+ Requires-Dist: pydantic-settings (>=2.13.1,<3.0.0)
21
+ Project-URL: Homepage, https://github.com/dchristopoulos/AIStack
22
+ Description-Content-Type: text/markdown
23
+
24
+ # AIStack
25
+
26
+ [![tests](https://github.com/dchristopoulos/AIStack/actions/workflows/tests.yaml/badge.svg)](https://github.com/dchristopoulos/AIStack/actions/workflows/tests.yaml)
27
+
28
+ A self-hosted home for your AI coding skills. Every skill on your computers is backed up to a private space of your own and kept current on each machine. From there you share skills with a team, or with a group inside one, and take the ones your teammates shared.
29
+
30
+ You see and manage all of it on a web dashboard. Your coding agent is connected to the same account over MCP, so the everyday actions are also a sentence in chat.
31
+
32
+ Open source and free, with no hosted service: it runs on your own computers, and you choose where your Library lives.
33
+
34
+ ## Where your Library lives
35
+
36
+ | Choice | What it costs you | For |
37
+ |---|---|---|
38
+ | **Only this computer** | Nothing to set up. No other computer reaches it | Trying it out, or one computer |
39
+ | **A private GitHub repository** | Sign in to GitHub once in the browser on each computer; AIStack creates the repository and keeps it up to date. Nothing to keep switched on, nothing pauses, and every change is in the repository's history ([ADR-0025](docs/adr/0025-a-github-library-is-a-log-each-computer-replays.md)) | Your laptop and your desktop |
40
+ | **A free PostgreSQL database** ([Neon](https://neon.tech) recommended; Supabase works) | A free account and one address pasted on each computer. Nothing to keep switched on. Supabase pauses after about a week unused | Your laptop and your desktop |
41
+ | **A computer that stays on**, running the AIStack server | Free on a computer you own, but it must stay on (or a few dollars a month on a host) | Teams, and the only choice that works for one |
42
+
43
+ For the first three, your coding agent starts AIStack itself and nothing runs in between ([ADR-0020](docs/adr/0020-local-mode-and-where-the-library-lives.md)). Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then paste this into Claude Code or Codex:
44
+
45
+ ```text
46
+ Set up AIStack: run `uvx aistack-mcp setup` and follow what it prints.
47
+ ```
48
+
49
+ It asks which choice you want, signs you in to GitHub and creates the private repository, or walks you through creating the Neon database, connects your agent, and installs the `sync-aistack` skill. On another computer, paste the same and choose the same repository or database. If a laptop is lost, revoke GitHub CLI under Settings → Applications on github.com and sign in again on the others with `aistack-mcp github`, or reset the database password and paste the new address on the others ([ADR-0023](docs/adr/0023-every-computer-keeps-the-database-address.md)). "Open my aistack dashboard" opens your Library in the browser, on that computer only, as it does on a server.
50
+
51
+ For a team, run a server and connect each computer to it: see [Connecting a computer to a server](#connecting-a-computer-to-a-server).
52
+
53
+ To move your own Library onto a team's server later, run `aistack-mcp export` on a computer that has it. It writes every skill in the Library, at its latest revision, to `./aistack-library`, with each skill's files checked against the hash recorded when they were pushed. Then connect that computer to the server, copy the skill folders you want into `~/.claude/skills`, and say "sync aistack": sync backs up whatever the new Library lacks, without asking. Revision history and which computer had which skill stay behind.
54
+
55
+ ## Try it in two minutes
56
+
57
+ No coding agent and no Docker needed to look around. This starts a server on your machine and fills it with a small demo team:
58
+
59
+ ```bash
60
+ git clone https://github.com/dchristopoulos/AIStack && cd AIStack
61
+ poetry install
62
+ AISTACK_INVITE_CODE=try-it-out-locally-1234 poetry run aistack serve &
63
+ poetry run python scripts/demo_seed.py http://127.0.0.1:8000 try-it-out-locally-1234
64
+ ```
65
+
66
+ It prints a sign-in link for each of three people: an admin, a member with an update waiting, and a member who is kept out of a private group. Open one. Everything the script created went through the same tools and routes a real user's agent and browser use.
67
+
68
+ ## What it looks like
69
+
70
+ ![The Library: every skill you keep, where it came from, and which computers have it](docs/images/dashboard-library.png)
71
+
72
+ | One computer's coding agent | A team, with nested groups |
73
+ |---|---|
74
+ | ![What Claude Code on one computer has, with Add skills and Copy from](docs/images/dashboard-computer.png) | ![A team's skills by group](docs/images/dashboard-team.png) |
75
+ | **Managing groups** | **An update a teammate pushed, offered and not applied** |
76
+ | ![Groups: open or private, and who is in them](docs/images/dashboard-groups.png) | ![An update waiting in the Library, in dark mode](docs/images/dashboard-update-dark.png) |
77
+
78
+ ## The problem
79
+
80
+ A team writes a good code-review skill. It gets copied onto five laptops, and within a month there are five versions. A developer with a MacBook and a desktop maintains the same setup twice. Nobody knows what their teammates' agents are running, and nobody wants a colleague's edit to an agent's instructions landing on their machine unannounced.
81
+
82
+ ## How it is organized
83
+
84
+ Three places hold skills. Each is more widely seen than the one before.
85
+
86
+ | Place | Who sees it | How a skill gets there | What installs from it |
87
+ |---|---|---|---|
88
+ | **Library** | Only you | Sync backs up what is on your computers, without asking | Your machines, and nothing else |
89
+ | **A team** | Its members | A member shares one of their own skills | Nothing directly: you take a copy into the Library |
90
+ | **A group** in a team, such as `backend` or `backend/aistack` | Open: whoever sees the group above it. Private: the people added to it | A member of it, or of a group above it, shares into it | The same as a team |
91
+
92
+ **The Library** is yours alone and mirrors your computers. It is the only place a machine ever installs from, so what runs on your laptop is always your own copy.
93
+
94
+ **Teams** are created by any user, who becomes the team's admin, and joined with the team's invite code. A team's invite link also creates the account of someone new to the server. Sharing gives the team its own copy of a skill, with its own history, and every revision says who shared it.
95
+
96
+ **Groups** sit inside a team like channels, and nest. A private group is seen only by the people added to it. Two roles exist, admin and member; "backend developer" is not a role, it is being in the `backend` group.
97
+
98
+ Two rules follow from "a machine installs only from the Library":
99
+
100
+ - **A teammate's change is an offer.** When a team's copy is newer than the one you took, sync tells you who changed it and asks. It never applies the change. A skill is instructions to an agent, and somebody else's edit does not reach your machine until you say yes.
101
+ - **Leaving a team takes nothing off your computer.** Neither does being removed from one, or an admin deleting the team's copy. What you took is yours.
102
+
103
+ The reasoning is in [ADR-0015](docs/adr/0015-teams-and-groups.md).
104
+
105
+ ## How it works
106
+
107
+ ```mermaid
108
+ flowchart LR
109
+ subgraph people["A person"]
110
+ browser["Browser"]
111
+ agent["Coding agent<br/>on each machine"]
112
+ end
113
+ subgraph server["AIStack server · one container, one port"]
114
+ dash["Dashboard<br/>/app and /api"]
115
+ tools["13 MCP tools<br/>metadata only"]
116
+ routes["2 transfer routes<br/>signed, 5-minute URLs"]
117
+ mine[("Library<br/>private, per user")]
118
+ teams[("Teams and groups<br/>shared copies")]
119
+ desired[("Desired state<br/>per machine")]
120
+ end
121
+
122
+ browser -- "sign-in link from chat" --> dash
123
+ agent -- "MCP · machine token" --> tools
124
+ agent -- "tar + curl" --> routes
125
+ dash --- mine
126
+ dash --- teams
127
+ tools --- mine
128
+ tools --- desired
129
+ routes --- mine
130
+ mine -- "share" --> teams
131
+ teams -- "take" --> mine
132
+ ```
133
+
134
+ **The dashboard is the product; MCP is the connector.** Both are the same process and call the same services. The dashboard is one HTML page with no build step, and there are no passwords: you ask your agent to open it and get a link that signs your browser in ([ADR-0017](docs/adr/0017-dashboard-in-the-same-process.md)).
135
+
136
+ **Skills are copied to disk in full and run natively.** After an install there is no runtime dependency on AIStack: if the server is down, every skill still works ([ADR-0001](docs/adr/0001-full-sync-over-runtime-proxy.md)).
137
+
138
+ **The server holds desired state per machine.** Each machine has its own token, so the server knows who you are and which computer you are on. A MacBook and a work laptop can enable different skills, and sync reconciles each against its own list ([ADR-0004](docs/adr/0004-server-is-source-of-truth-per-machine.md)).
139
+
140
+ **Metadata travels over MCP; bytes do not.** A tool argument is text the model generates token by token, which is slow and not exact for a file. So `push_skill` and `get_skill` return a short-lived signed URL, and the agent moves a gzipped tar with `curl` ([ADR-0010](docs/adr/0010-signed-transfer-urls-for-skill-bytes.md)).
141
+
142
+ ## Sync never loses your work
143
+
144
+ Sync compares one machine with the Library. It acts without asking only when nothing can be lost and nothing is shared.
145
+
146
+ | What sync finds | What happens |
147
+ |---|---|
148
+ | Enabled here, not on disk | Installed |
149
+ | Enabled here, an older unmodified revision | Updated, and you are told who pushed the new one |
150
+ | On disk, not yet in the Library | Backed up to the Library. It is private, so nothing is shared |
151
+ | Enabled here, edited locally | You are asked: restore, back up your version, or leave it |
152
+ | You removed it earlier, and the folder is still there | You are asked: bring it back, or delete the folder |
153
+ | In the Library and on disk, never chosen on this machine | You are asked whether to manage it here |
154
+ | A team has a newer revision of a skill you took | Offered, with who pushed it. You decide each time |
155
+
156
+ Sync itself only reports, and the agent acts through other tools; the one thing it records is when each folder last synced. It never deletes a file, never changes what a machine has enabled, and never applies a teammate's change. Every answer that would overwrite your files or discard edits says so first. The policy is one table in [`reconciliation.py`](src/aistack/services/reconciliation.py), with tests that state it as invariants over every row ([ADR-0006](docs/adr/0006-explicit-disable-sync-never-deletes.md)).
157
+
158
+ ## Connecting a computer to a server
159
+
160
+ Paste this into Claude Code or Codex, with your server's address and an invite code: the one its operator gave you, or a team's, which also puts you in that team:
161
+
162
+ ```text
163
+ Set up AIStack: fetch https://aistack.example.com/setup and follow it.
164
+ Invite code: <the invite code>. Call me <your name>.
165
+ ```
166
+
167
+ The agent registers this computer, installs the `sync-aistack` skill, and connects itself to the server. Restart it once when it says so. That restart is the only one: the agent reads its server list at startup, and skills installed afterwards load live.
168
+
169
+ One connected agent is enough for the whole computer. Sync keeps Claude Code's skills folder and the shared `~/.agents/skills` folder in line, and that shared folder is what Codex, Gemini CLI, Kimi Code, Cursor, Copilot CLI, opencode, and Amp read. The first time, it asks once whether both folders should hold the same skills ([ADR-0021](docs/adr/0021-one-agent-syncs-every-folder.md)). Setup also offers to sync at the start of every session: a `SessionStart` hook that only reminds the agent to run `sync-aistack`, so your computers stay current without anyone asking.
170
+
171
+ For a second computer, say "add my other computer to aistack" on one that is already connected. It returns one command to paste there. No invite code is involved.
172
+
173
+ ## Things to say in chat
174
+
175
+ | Say | What happens |
176
+ |---|---|
177
+ | "sync aistack" | Backs up what is new on this computer, installs and updates what it should have, and asks about the rest. Do it when you start work, like `git pull`, or let the session-start hook ask for it |
178
+ | "open my aistack dashboard" | Gives you a link that opens the dashboard, signed in |
179
+ | "what skills do my teams have?" | Lists your own skills, then each team's, with who pushed each |
180
+ | "install code-review from backend" | Takes the team's copy into the Library and installs it here. Usable in the same session |
181
+ | "share wizard with backend" | Gives the team a copy of your latest revision |
182
+ | "create a team called backend" | Creates it with you as admin. "Invite someone to backend" returns its invite code |
183
+ | "join a team with this code" | Joins the team a teammate's invite code belongs to |
184
+ | "remove wizard from this machine" | Disables it here and deletes the local folder. It stays in the Library and on your other machines |
185
+
186
+ A revision records who pushed it. That is provenance, not authorship: people back up and share third-party skills, and AIStack never claims you wrote something you only uploaded.
187
+
188
+ Everything else is on the dashboard: which of your computers has which skill, groups and who is in them, members and roles, moving or removing a team's skill, unsubscribing from a group's updates, leaving a team, and removing a skill from the Library.
189
+
190
+ ## Running a server
191
+
192
+ ```bash
193
+ docker run -d --name aistack -p 8000:8000 \
194
+ -e AISTACK_INVITE_CODE="$(openssl rand -base64 24)" \
195
+ -v aistack-data:/data ghcr.io/dchristopoulos/aistack:latest
196
+ ```
197
+
198
+ The image is published from every commit on `main` that passed the tests, for Intel and ARM machines; `docker build -t aistack .` builds the same thing from a checkout. One container, one volume, SQLite inside. The dashboard needs no second service and no extra port. Point `DATABASE_URL` at PostgreSQL and the same image uses that instead ([ADR-0002](docs/adr/0002-sqlite-default-postgres-optional.md)). An upgrade is starting the new image: the server migrates its database at startup, in one transaction ([ADR-0016](docs/adr/0016-alembic-migrations-in-one-transaction.md)).
199
+
200
+ To look around without setting anything up, fill a fresh server with a small demo team. It prints a dashboard link for each of three people, and everything it creates goes through the same tools and routes a real user's agent and browser use:
201
+
202
+ ```bash
203
+ poetry run python scripts/demo_seed.py http://127.0.0.1:8000 "<the invite code>"
204
+ ```
205
+
206
+ The screenshots in this README are retaken the same way, from a fresh server, by `scripts/retake_screenshots.py`.
207
+
208
+ [**Operating AIStack**](docs/OPERATING.md) covers configuration, reaching the server from other machines, backups, upgrades, and the operator's tasks: revoke a machine, recover a locked-out user, take someone off the server, rotate the invite code.
209
+
210
+ ## The thirteen tools
211
+
212
+ MCP carries what is worth saying in a sentence. Anything that would need many typed parameters is a dashboard action instead.
213
+
214
+ | | Tool | What it does |
215
+ |---|---|---|
216
+ | **Connect** | `join` | Redeem the server's or a team's invite code, register your first machine, get its token |
217
+ | | `add_machine` | Register another computer and return what to paste there, for Claude Code or Codex |
218
+ | **Library** | `sync` | Compare one skills folder on this machine with the Library and report what to do. Changes no skill and no decision |
219
+ | | `push_skill` | Get a signed URL to back a skill up to. A repeated name becomes a new revision |
220
+ | | `get_skill` | Enable a skill on this machine and get a signed URL for its files. With a team, take that team's copy first |
221
+ | | `list_skills` | Your own skills, then every team skill you may see. This is also the search |
222
+ | | `disable_skill` | Stop managing a skill on this machine |
223
+ | **Teams** | `share_skill` | Copy your latest revision into a team or a group |
224
+ | | `list_teams` | The teams you are in, your role, and their size |
225
+ | | `create_team` | Create a team with you as its admin |
226
+ | | `invite_to_team` | Make an invite link for a team. It lets in one person, creating their account if they are new, and lasts seven days. Cancel one in the dashboard |
227
+ | | `join_team` | Join a team with its invite code |
228
+ | **Dashboard** | `open_dashboard` | Return a link that signs a browser in for you |
229
+
230
+ ## Reading the code
231
+
232
+ About 5,800 lines of Python and 6,900 of tests. A good order:
233
+
234
+ 1. [`docs/DESIGN.md`](docs/DESIGN.md) for the plan and [`CONTEXT.md`](CONTEXT.md) for the vocabulary the code uses.
235
+ 2. [`mcp/tools/`](src/aistack/mcp/tools/): the thirteen tools. Each is thin: read the caller, open one transaction, call a service.
236
+ 3. [`mcp/auth/`](src/aistack/mcp/auth/): two principals, one rule, enforced in one middleware so a new tool is covered by default.
237
+ 4. [`services/visibility.py`](src/aistack/services/visibility.py): who may see what, in one module. Group access is plain code over two queries, and something you may not see answers exactly like something that does not exist.
238
+ 5. [`services/skill_service.py`](src/aistack/services/skill_service.py) for push, share, and take, and [`services/team_service.py`](src/aistack/services/team_service.py) for team administration, where every function begins by establishing the caller's place in the team.
239
+ 6. [`services/reconciliation.py`](src/aistack/services/reconciliation.py): the sync policy as a pure table, and [`services/sync_service.py`](src/aistack/services/sync_service.py), which computes its inputs and the team updates on offer.
240
+ 7. The push pipeline, which is the trust boundary: [`transfer_ticket.py`](src/aistack/services/transfer_ticket.py) → [`routes/transfer.py`](src/aistack/mcp/routes/transfer.py) → [`skill_archive.py`](src/aistack/services/skill_archive.py) → [`skill_validation.py`](src/aistack/services/skill_validation.py).
241
+ 8. [`db/schema.py`](src/aistack/db/schema.py) and [`db/migrations/versions/`](src/aistack/db/migrations/versions/): the schema as migrations. `0002_teams.py` is the one that moved a shared vault into teams without hiding anything from anyone.
242
+ 9. [`api/dashboard.py`](src/aistack/api/dashboard.py): the dashboard's sign-in and JSON routes, as thin as the tools, and [`services/dashboard_service.py`](src/aistack/services/dashboard_service.py), which assembles what one user may see.
243
+
244
+ Things in there that took some care:
245
+
246
+ - **Untrusted archives.** An uploaded tar is decompressed under a hard ceiling before `tarfile` sees it, read in memory, and never extracted. Links, devices, and sparse files reject the push. Tests build a gzip bomb and a pax-header bomb and assert both are refused.
247
+ - **Paths every filesystem can hold.** A skill pushed from Linux must install on Windows and macOS, so reserved device names, trailing dots, case-only differences, and NFC/NFD twins are rejected at push.
248
+ - **YAML without surprises.** Frontmatter is parsed with every scalar kept as a string, so `version: 1.10` is not stored as `1.1`, nothing can construct an object, and an alias bomb is stopped by a node budget.
249
+ - **Hidden means indistinguishable.** A team you are not in, a private group, and a skill inside one are answered with the same exception and the same sentence as one that does not exist. A test holds a table of what every tool shows an outsider, and fails when a tool is added without a row.
250
+ - **Migrations that roll back whole.** Every pending migration runs in one transaction on both databases. On SQLite that took turning foreign keys off around the table rebuild and checking them before commit; a test rebuilds a referenced table with rows in it and fails without that.
251
+ - **Races proven, not hoped.** Two pushes reading the same latest revision are held at a `threading.Barrier` in the exact window, so the test fails without the unique constraint. It runs against real PostgreSQL in CI.
252
+ - **One hash, one implementation.** The server computes the content hash at push from the bytes and at sync from the digests the agent sent. Its test asserts a constant derived with `perl`, `shasum`, and `xxd`.
253
+ - **Secrets stay out of logs.** Machine tokens, both kinds of invite code, transfer URLs, and dashboard links never reach a log line or an error. Tests assert it per flow, and CI greps the container's own output.
254
+ - **A dashboard with no passwords.** The sign-in link and the session cookie are signed tickets with different purposes, so neither can stand in for the other, and a request that changes anything must carry a header a cross-site form cannot send.
255
+ - **Two databases, one behavior.** Every service and HTTP test runs against SQLite and PostgreSQL. Concurrent writers wait instead of failing on SQLite, which took an explicit `BEGIN IMMEDIATE` ([ADR-0014](docs/adr/0014-sqlite-transactions-begin-immediate.md)).
256
+
257
+ ### Tests
258
+
259
+ ```bash
260
+ poetry install
261
+ poetry run pytest
262
+ ```
263
+
264
+ The PostgreSQL half skips without a database. To run all of it:
265
+
266
+ ```bash
267
+ docker compose -f compose.test.yaml up -d
268
+ TEST_DATABASE_URL=postgresql+psycopg://aistack:aistack@localhost:5433/aistack_test poetry run pytest
269
+ ```
270
+
271
+ The GitHub half skips too: a Library in a private GitHub repository is otherwise tested against a local stand-in. To run it against real GitHub, with your `gh` login and a private repository kept for this alone (every run adds to its history):
272
+
273
+ ```bash
274
+ AISTACK_E2E_GITHUB_REPO=<owner>/aistack-library-e2e poetry run pytest tests/e2e
275
+ ```
276
+
277
+ CI also builds the container and drives it from outside as two users: one pushes and shares, the other takes the skill and gets the same bytes, across a restart and on both databases.
278
+
279
+ ### How the whole thing was verified
280
+
281
+ What the repository itself shows:
282
+
283
+ - The test suite covers each service directly and each flow over real HTTP with real bearer tokens: join, push, install, sync, teams and invite codes, share and take, team updates, and the dashboard from sign-in to an update taken there.
284
+ - The upgrade from a single shared vault is tested on a database built at the old schema: afterwards everyone sees what they saw before, and a failed migration leaves the database as it was.
285
+ - The dashboard page is driven in a real browser against the real server: sign-in from a link, taking an update, sharing into a nested group, creating and joining a team, what a member is not shown, and a phone-width layout. The same tests run in Chromium, Firefox, and WebKit.
286
+ - The container job runs the built image, not the source tree.
287
+
288
+ What it does not show, stated plainly:
289
+
290
+ - The first release, before teams, was also run end to end by fresh agents that were given only what a real client gets: the tool descriptions and the `/setup` page. That exercise has not been repeated for sharing, taking, team updates, or the dashboard. Those rest on the tests above.
291
+ - A signed-in Claude Code session was run once by hand against a local server (2026-10-14): it listed a team's skills over MCP, installed one with the tool's own `curl` and `tar` steps, executable bit intact, and used it in the same session without a restart. The first attempt to use the skill straight after installing it answered "Unknown skill"; a moment later it was found. No automated test repeats this.
292
+ - The session-start hook was checked in a headless Claude Code session (2026-10-14): its line reached the model's context at startup.
293
+ - A whole sync was run once from a signed-in Claude Code session (2026-10-14, Claude Code 2.1.241, headless, against a local server). The computer's skills folder held 25 real skills and the server was arranged so one sync met every part of the report. Told only "sync aistack", and given its two answers in advance, the session hashed the folder, called `sync`, and then:
294
+
295
+ | The report said | What the session did |
296
+ |---|---|
297
+ | 24 skills already current | Nothing |
298
+ | 1 to install, pushed from another computer and switched on from the dashboard | Installed it, executable bit intact |
299
+ | 1 to update, an untouched older revision | Replaced it with revision 2 |
300
+ | 2 on disk and not yet in the Library | Backed both up without asking |
301
+ | 1 edited locally | Left it alone, as answered, and said the edits exist only on that computer |
302
+ | 1 update offered by a team, pushed by a teammate | Took it, as answered, and named who pushed it |
303
+
304
+ A second sync straight afterwards reported nothing to do except the edited skill, which it asks about again. It took 14 turns, about three minutes, and $0.95. One thing went wrong on the way: the session wrote its install commands with shell variables in an `rm`, Claude Code refused that as unsafe, and the session rewrote them with plain paths and carried on. The folders the run added were removed afterwards and the skills folder was checked to be file for file what it had been. This is one run with one model, and no automated test repeats it.
305
+ - A sync was run once from Codex (2026-10-15, Codex CLI 0.162 from the ChatGPT app, `codex exec`, against a local server). It was connected the way setup connects it, with an `[mcp_servers.aistack]` block in `~/.codex/config.toml` that `codex mcp get aistack` listed. Its `~/.agents/skills` held `sync-aistack` and one skill the Library did not have, and the Library held one skill switched on for that folder but missing from it. Told only "sync aistack", Codex loaded `sync-aistack`, synced only the agents folder because there was no `~/.claude`, hashed it, called `sync`, backed up the new skill without asking, and installed the missing one byte for byte. Three things got in the way, all from Codex's own safeguards and not from AIStack:
306
+ - Non-interactive `codex exec` refuses an MCP tool that needs approval, so the run set `default_tools_approval_mode = "approve"` for the aistack server on the command line. Setup now writes `approval_mode = "approve"` for `sync`, `get_skill`, and `push_skill` only, so sync runs without a prompt and every other AIStack tool still asks. Codex 0.162 was checked to read those lines; a sync was not run again with them.
307
+ - Codex's `workspace-write` sandbox keeps `.agents` read-only, so the first install failed and Codex said so. The upload still went through. The install succeeded once the run was given write access, which stands in for the user approving the write. Setup leaves that approval in place: declaring `~/.agents/skills` a writable root would let every Codex session, in any repository, change the skills every agent on the computer loads.
308
+ - The `codex` 0.146 on the PATH rejected the model configured in that file, so the run used the CLI the ChatGPT app ships.
309
+
310
+ The skills folder was a scratch home directory, and the config file was restored from a backup afterwards. This is one run with one model, and no automated test repeats it.
311
+ - Local mode was run once by hand (2026-10-15, Claude Code 2.1.241, headless). Two scratch home folders stood in for a laptop and a desktop, each starting the built package with `uvx --from <wheel> aistack-mcp` over stdio, and both pointed at one SQLite Library. Told "sync aistack", the laptop's session backed up its one skill. Asked what was in the Library and to install it, the desktop's session listed it, synced, and installed it byte for byte with its executable bit, in six turns. No server ran at any point. This is one run with one model; `tests/mcp/test_local_mode.py` repeats the same flow without an agent.
312
+ - Local setup was run once by hand (2026-10-15) in a scratch home folder, against the built package and a PostgreSQL address pasted as `postgresql://`. Each step of `aistack setup` worked as written: the start check stayed silent on success and printed one sentence for a wrong password. Claude Code 2.1.241, headless and connected with setup's own command, was then told "sync aistack". It loaded the local `sync-aistack` skill, synced both folders, backed up the one skill on disk, and asked the first-sync question about using the same skills in both folders. Setup's `claude mcp add` itself was not run, because it would have changed the real Claude Code configuration.
313
+ - A sync was run from Codex in local mode (2026-10-15, Codex CLI 0.162 from the ChatGPT app, `codex exec`). It was connected with setup's Codex settings given on the command line, with a scratch home folder and the built package. Told "sync aistack", it loaded the local `sync-aistack` skill and backed up the one skill in `~/.agents/skills`. With that folder deleted, a second sync installed it again, byte for byte. AIStack wrote the folder itself, so Codex's sandbox had nothing to approve. The first attempt found no server: AIStack's first start downloaded its packages for longer than Codex waits, which is why setup runs AIStack once before connecting the agent, and why its Codex settings now allow a minute.
314
+ - Every chat request a person makes was run against a server from source (2026-10-15), once in headless Claude Code 2.1.241 and once in `codex exec` (Codex CLI 0.162 from the ChatGPT app), each in a scratch home folder, connected as a seeded user with a machine token and nothing else. The phrases were "sync aistack" (with a new skill, an edited one, and an older revision on disk), "what skills do my teams have?", "install release-notes from aistack", "share pr-description with aistack", "remove … from this machine", "create a team called platform and invite someone", and "add my other computer". Both agents did each one. What got in the way, and what changed:
315
+ - Claude Code wrote its install steps as a shell function with `rm -rf "$STAGE/$name"`, which it refuses to run, in every session that installed something. It recovered each time, at the cost of a few turns. `get_skill` now says to run the steps per skill with the paths written out, and the same sync then took 14 turns instead of 20.
316
+ - Asked to add a computer where the user runs Codex, Codex got only a `claude mcp add` command, searched the web, and improvised a setup that needs an environment variable set forever. `add_machine` now also returns the `config.toml` block setup writes, which Codex then showed as it was.
317
+ - Codex left out the team when told "install release-notes from aistack", was refused, and listed every skill to find it. The refusal now names the teams the caller can see that share a skill of that name, so the next call succeeds.
318
+ - Both agents compared a team's revision number with the user's own, which count separately. `list_skills` now says so.
319
+
320
+ The sessions ran with their permission checks off, inside a macOS sandbox that denied writes to the real skills folders and agent configuration. This is one run per agent with one model each, and no automated test repeats it.
321
+ - A Library was shared through a real private GitHub repository (2026-10-15, Claude Code 2.1.241 headless, the built package through `uvx`, two scratch home folders as a laptop and a desktop, the owner's `gh` login). `aistack check` on each reported the repository. Told "sync aistack", the laptop's session backed up its skill, which put four event files in the repository. Asked what was in the Library and to install it, the desktop's session listed it and installed it byte for byte with its executable bit, in seven turns. The first attempt found two faults, fixed before this run: a clone that timed out left a broken clone that later passed for a working one, so changes were marked shared without being pushed; and git asked the macOS keychain helper Homebrew configures before `gh`, which hung. AIStack now clones into a temporary folder and moves it into place, and gives git `gh`'s login alone for GitHub.
322
+ - The finished local setup was run on a new computer against that same repository (2026-10-15): AIStack installed with `uv tool install`, started by its full path with a `PATH` holding only `/usr/bin:/bin`, as an agent launched from a desktop app may have. Headless Claude Code, told "sync aistack", synced both folders and offered the two skills the Library had from the other computers, in seven turns. `codex exec`, told "sync aistack" and to accept the offer, installed both into `~/.agents/skills`. Its first attempt found no AIStack tools, because every start then pulled from GitHub before answering and Codex does not wait for a slow MCP server; a start now needs no network after the first one.
323
+
324
+ - Local mode was walked end to end again (2026-10-15), from the built package installed with `uv tool install` into scratch home folders, on two computers for each place a Library can live: this computer only, a new private GitHub repository, and a PostgreSQL database pasted as `postgresql://`. Each computer was connected to headless Claude Code 2.1.241 or to `codex exec` (Codex CLI 0.162 from the ChatGPT app, with a Codex home holding only the login), and told what a person says: "sync aistack", "what's in my library?", "remove … from this computer", "install …", "open my aistack dashboard" (the page then used in a browser), and a skill edited on one computer and synced on the other. Each place worked on both computers. What got in the way, and what changed:
325
+ - Two computers edited one skill before syncing. The second was offered to push its copy with nothing to say the first had pushed since, and pushing it made the first computer's edit disappear from both. Sync now reports the revision a changed copy was edited from, and the second computer's session then said that pushing would replace revision 4 from the other computer.
326
+ - Codex asked before `list_skills`, which the first sync on a computer calls to offer the Library, so `codex exec` could not make the offer. Setup now approves it, and the same run then offered the three skills the other computers had.
327
+ - A push made with GitHub out of reach was reported as backed up. `push_skill` now says when a change has not reached GitHub, and Codex told the user their other computers get it once a sync reaches GitHub.
328
+ - `aistack check` on a new, empty repository warned that it could not reach it, then said it was ready.
329
+ - Asked to move a Library from the computer to GitHub, Claude Code improvised `claude mcp add` without `--scope user`. The sync skill now describes the move. Asked again, the session laid out export, setup, removing the old connection, and copying back, and those steps, run by hand, moved the computer's skills into the GitHub Library.
330
+ - The dashboard beside the terminal kept showing a skill Codex had just removed. It now refreshes when its window comes back to the front.
331
+ - Two Windows problems were found by reading setup and fixed without a Windows computer: PowerShell 5.1's `>` writes the sync skill as UTF-16, and a Windows path in the Codex block breaks TOML. `aistack sync-skill <folder>` now writes the file itself.
332
+
333
+ Left as it is: a skill holding a binary file or more than 100 files is refused with a sentence saying why, and is listed under `backup` again on every sync. Setup's own `claude mcp add` and Codex config file were not used; each agent got the same settings on its command line. These are single runs with one model per agent, and the fixes each have a test.
334
+
335
+ ## How this compares
336
+
337
+ Checked in October 2026.
338
+
339
+ | | AIStack | Claude Code's claude.ai sync | Plugin marketplaces | A git repo of skills |
340
+ |---|---|---|---|---|
341
+ | Share a skill | A sentence in chat, or a click | Upload on claude.ai | A commit | A commit |
342
+ | Per-machine choice of skills | Yes | No | No | No |
343
+ | Handles local edits | Asks, never overwrites | Overwrites | Replaces on update | Merge conflicts |
344
+ | Needs | Your own server | An Anthropic account | Git credentials on every machine | Git everywhere |
345
+ | Works beyond Claude Code | Planned next | No | No | Manually |
346
+
347
+ For one developer on one harness, Claude Code's own sync is the simpler choice, and AIStack does not try to beat it. AIStack is for people who want their own server, a private space per person with teams and groups on top, per-machine control, and one source of truth across more than one coding agent, which is why a second harness is the first thing on the roadmap.
348
+
349
+ ## Status and roadmap
350
+
351
+ Built: skills for Claude Code, Codex, and every agent that reads `~/.agents/skills`, a private space per user, teams with invite codes, nested groups that are open or private, share and take, team updates as offers, schema migrations, and the dashboard.
352
+
353
+ Not built, in the order they are likely to arrive:
354
+
355
+ 1. **Every agent on a computer on the dashboard**: a page per skills folder, and agents in the Add to… dialog.
356
+ 2. **Rules**, the second resource type, projected into `CLAUDE.md`, `AGENTS.md`, and `.cursor/rules/`.
357
+ 3. **Background sync.** Today nothing happens until you say "sync aistack".
358
+ 4. **More on the dashboard**: server administration as buttons, version pinning, deleting a single revision.
359
+ 5. Hooks, agents, an MCP gateway, and profiles.
360
+
361
+ Deliberately left out ([ADR-0015](docs/adr/0015-teams-and-groups.md)): automatic updates from a team, a permission matrix of roles and groups, and merging. When your copy and the team's have both changed, taking the team's replaces your latest, which stays in your history; nothing is merged line by line.
362
+
363
+ Known limits today:
364
+
365
+ - Two skills folders: Claude Code's and the shared `~/.agents/skills`. Project folders are not managed.
366
+ - "Add my other computer" returns a Claude Code command. A second computer where Codex is the only agent is not connected that way yet.
367
+ - No version pinning: a machine always gets the latest revision of your own copy.
368
+ - One machine belongs to one server.
369
+ - Restarting the server signs everyone out of the dashboard, because the signing key lives in memory. Machines stay connected.
370
+ - Server administration (revoking a machine, recovering a locked-out user) is a documented script, not a button.
371
+ - A skill is text files only, at most 100 of them, 256 KiB each and 1 MiB together, up to 8 folders deep. Empty folders are not stored and `.env` files are refused.
372
+
373
+ [`docs/DESIGN.md`](docs/DESIGN.md) §4 lists everything left out and why.
374
+
375
+ ## Privacy
376
+
377
+ AIStack collects nothing. There is no telemetry, no analytics, no account with us, and no hosted service: it runs on computers you choose, and what it stores stays there.
378
+
379
+ **What it connects to.** A server makes no outbound connections. The dashboard loads nothing from any other site; its content security policy forbids it. In local mode, AIStack reaches only:
380
+
381
+ - PyPI, once a day, to check for a newer release of itself;
382
+ - your own GitHub repository, through your own `gh` sign-in, when your Library lives there;
383
+ - your own database, when your Library lives in one.
384
+
385
+ **What a server stores.**
386
+ - **People:** each person's username and, if they give one, the full name their teammates read.
387
+ - **Computers:** each person's computers, by name and operating system, with a hash of each computer's token, never the token.
388
+ - **Skills:** the skills people back up and share, with every revision and who pushed it.
389
+ - **Placements:** which skill is switched on in which folder of which computer, and when each folder last synced.
390
+ - **Teams:** teams, groups and who is in them, and invites, kept as a hash of their code with who made and used them.
391
+
392
+ The log records names and ids, and is built never to record an IP address, a token, an invite code, or a link. A local Library holds the same about one person. In a GitHub Library, its private repository holds it, including the skills' files.
393
+
394
+ **Who is responsible.** Whoever runs a server holds its users' data. A team that runs one for its people decides what to tell them. An operator can take someone off a server ([docs/OPERATING.md](docs/OPERATING.md)), and that ends their access. Their username stays on the revisions they pushed, because a team needs to know whose instructions its agents run. Copies teammates took are theirs.
395
+
396
+ Security reports go through [SECURITY.md](SECURITY.md).
397
+
398
+ ## Documentation
399
+
400
+ Decisions are written down before code, with the reasoning kept.
401
+
402
+ - [Design document](docs/DESIGN.md): scope, tool contracts, flows, the sync contract, push validation, teams and groups, the dashboard, schema, roadmap
403
+ - [Operating AIStack](docs/OPERATING.md): deployment, upgrades, and the operator's tasks
404
+ - [Domain glossary](CONTEXT.md): the vocabulary the code and docs share
405
+ - [AGENTS.md](AGENTS.md): the conventions a coding agent follows when working in this repository
406
+ - [Security policy](SECURITY.md): how to report a vulnerability, and what is in scope
407
+ - Decision records:
408
+ - [0001 Full local sync instead of runtime proxy skills](docs/adr/0001-full-sync-over-runtime-proxy.md)
409
+ - [0002 SQLite by default, Postgres via DATABASE_URL](docs/adr/0002-sqlite-default-postgres-optional.md)
410
+ - [0003 Personal API tokens with invite-code join, no login system](docs/adr/0003-token-auth-with-invite-code-join.md)
411
+ - [0004 Server-side desired state per machine (reverses stateless sync)](docs/adr/0004-server-is-source-of-truth-per-machine.md)
412
+ - [0005 Synchronous SQLAlchemy, not async](docs/adr/0005-synchronous-sqlalchemy.md)
413
+ - [0006 Explicit disable; sync never deletes; ask only when data can be lost](docs/adr/0006-explicit-disable-sync-never-deletes.md)
414
+ - [0007 Skill-specific tables; the generic resource model is deferred](docs/adr/0007-skill-specific-tables-generic-model-deferred.md)
415
+ - [0008 One admin, enforced by a partial unique index](docs/adr/0008-single-admin-partial-index.md)
416
+ - [0009 Length-prefixed canonical content hash, not JSON](docs/adr/0009-length-prefixed-canonical-content-hash.md)
417
+ - [0010 Skill bytes travel over signed transfer URLs, not through the model](docs/adr/0010-signed-transfer-urls-for-skill-bytes.md)
418
+ - [0011 The server alone computes the canonical content hash](docs/adr/0011-server-alone-computes-the-content-hash.md)
419
+ - [0012 Sync returns its questions as data; the agent asks them in chat](docs/adr/0012-sync-returns-questions-as-data.md)
420
+ - [0013 The MCP endpoint is stateless](docs/adr/0013-stateless-streamable-http.md)
421
+ - [0014 SQLite transactions take the write lock at BEGIN](docs/adr/0014-sqlite-transactions-begin-immediate.md)
422
+ - [0015 A personal space per user, teams to share into, and groups inside a team](docs/adr/0015-teams-and-groups.md)
423
+ - [0016 Schema changes are Alembic migrations, applied at startup in one transaction](docs/adr/0016-alembic-migrations-in-one-transaction.md)
424
+ - [0017 The dashboard is served by the server, and a link from chat signs you in](docs/adr/0017-dashboard-in-the-same-process.md)
425
+ - [0018 One account for every coding agent on a computer](docs/adr/0018-one-account-for-every-coding-agent.md)
426
+ - [0019 Invites are the way in](docs/adr/0019-invites-are-the-way-in.md)
427
+ - [0021 One connected agent syncs every skills folder on its computer](docs/adr/0021-one-agent-syncs-every-folder.md)
428
+
429
+ ## Stack
430
+
431
+ Python 3.13, FastMCP (and the Starlette application underneath it), SQLAlchemy, Alembic, PyJWT, Poetry. SQLite by default, PostgreSQL through `DATABASE_URL`. No front-end framework and no build step.
432
+
433
+ ## License
434
+
435
+ MIT. See [LICENSE](LICENSE).
436
+