cabildo 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 (42) hide show
  1. cabildo-0.1.0/.gitignore +9 -0
  2. cabildo-0.1.0/.gitlab-ci.yml +38 -0
  3. cabildo-0.1.0/LICENSE +21 -0
  4. cabildo-0.1.0/PKG-INFO +121 -0
  5. cabildo-0.1.0/README.md +96 -0
  6. cabildo-0.1.0/publishing-page.yml +314 -0
  7. cabildo-0.1.0/pyproject.toml +50 -0
  8. cabildo-0.1.0/scripts/install.py +136 -0
  9. cabildo-0.1.0/scripts/pypi-publish +23 -0
  10. cabildo-0.1.0/scripts/release +31 -0
  11. cabildo-0.1.0/src/cabildo/__init__.py +3 -0
  12. cabildo-0.1.0/src/cabildo/actions.py +710 -0
  13. cabildo-0.1.0/src/cabildo/cli.py +353 -0
  14. cabildo-0.1.0/src/cabildo/context.py +84 -0
  15. cabildo-0.1.0/src/cabildo/daemon.py +188 -0
  16. cabildo-0.1.0/src/cabildo/desktop.py +123 -0
  17. cabildo-0.1.0/src/cabildo/guard.py +108 -0
  18. cabildo-0.1.0/src/cabildo/hooks/__init__.py +0 -0
  19. cabildo-0.1.0/src/cabildo/hooks/_common.py +32 -0
  20. cabildo-0.1.0/src/cabildo/hooks/cli.py +40 -0
  21. cabildo-0.1.0/src/cabildo/hooks/post_tool_use.py +34 -0
  22. cabildo-0.1.0/src/cabildo/hooks/pre_tool_use.py +44 -0
  23. cabildo-0.1.0/src/cabildo/hooks/prompt_submit.py +38 -0
  24. cabildo-0.1.0/src/cabildo/hooks/session_end.py +24 -0
  25. cabildo-0.1.0/src/cabildo/hooks/session_start.py +36 -0
  26. cabildo-0.1.0/src/cabildo/hooks/stop.py +42 -0
  27. cabildo-0.1.0/src/cabildo/mcp_server.py +448 -0
  28. cabildo-0.1.0/src/cabildo/notify.py +113 -0
  29. cabildo-0.1.0/src/cabildo/store.py +840 -0
  30. cabildo-0.1.0/src/cabildo/tui.py +311 -0
  31. cabildo-0.1.0/src/cabildo/usage.py +202 -0
  32. cabildo-0.1.0/tests/__init__.py +0 -0
  33. cabildo-0.1.0/tests/conftest.py +32 -0
  34. cabildo-0.1.0/tests/test_claims.py +101 -0
  35. cabildo-0.1.0/tests/test_context.py +24 -0
  36. cabildo-0.1.0/tests/test_hooks_midturn.py +139 -0
  37. cabildo-0.1.0/tests/test_land_verification.py +152 -0
  38. cabildo-0.1.0/tests/test_mcp_tools.py +93 -0
  39. cabildo-0.1.0/tests/test_spawn.py +201 -0
  40. cabildo-0.1.0/tests/test_store.py +129 -0
  41. cabildo-0.1.0/tests/test_tui.py +59 -0
  42. cabildo-0.1.0/uv.lock +794 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+
8
+ # conversation logs stay local
9
+ logs/
@@ -0,0 +1,38 @@
1
+ # cabildo CI/CD -- test, then on the default branch publish to PyPI via OIDC
2
+ # Trusted Publishing when pyproject's version isn't on PyPI yet (no stored API
3
+ # token). Same publishing mechanism as pytest-parallex and musil; plain uv
4
+ # instead of moon/proto because this repo has no task graph (yet).
5
+
6
+ default:
7
+ image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
8
+
9
+ variables:
10
+ UV_LINK_MODE: copy
11
+
12
+ # Avoid duplicate (branch + MR) pipelines.
13
+ workflow:
14
+ rules:
15
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
16
+ - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
17
+ when: never
18
+ - if: $CI_COMMIT_BRANCH
19
+
20
+ ci:
21
+ # OIDC Trusted Publishing: GitLab mints a short-lived id_token; scripts/pypi-publish
22
+ # exchanges it for a one-shot PyPI token. The `pypi` environment matches the PyPI
23
+ # publisher config (it is also recorded on non-publishing runs -- cosmetic only).
24
+ id_tokens:
25
+ PYPI_ID_TOKEN:
26
+ aud: pypi
27
+ environment:
28
+ name: pypi
29
+ url: https://pypi.org/project/cabildo/
30
+ before_script:
31
+ # slim image is bare; the land() tests drive real git against a bare remote.
32
+ - apt-get update && apt-get install -y --no-install-recommends git ca-certificates
33
+ script:
34
+ - uv run --extra dev pytest -q
35
+ - |
36
+ if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
37
+ sh scripts/release
38
+ fi
cabildo-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jorge Eduardo Cardona Gaviria
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.
cabildo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: cabildo
3
+ Version: 0.1.0
4
+ Summary: IPC bus + chat room for coordinating parallel Claude Code agents: messaging, presence, and advisory locks with hook-level enforcement
5
+ Author-email: Jorge Eduardo Cardona Gaviria <jorgeecardona@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: agents,claude-code,coordination,ipc,mcp,multi-agent
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Communications :: Chat
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: mcp>=1.2
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # cabildo
27
+
28
+ A shared **IPC bus / chat room** for coordinating multiple Claude Code agents
29
+ running at the same time. Agents can message each other (direct or by topic),
30
+ discover who else is working, and — crucially — take **advisory locks** to
31
+ coordinate shared actions like committing and pushing.
32
+
33
+ It's a normal Python package: one **stdio MCP server** (the tools agents call)
34
+ plus a set of **hook scripts** (delivery, liveness, and lock enforcement). All state lives in
35
+ a single SQLite DB shared by every session.
36
+
37
+ ## How identity works (the tricky bit)
38
+
39
+ Claude Code does **not** give an MCP server the `session_id`. But hooks *do* get
40
+ it, and both the hooks and the MCP subprocess are descendants of the same
41
+ `claude` process. So:
42
+
43
+ - The `SessionStart` hook writes `session_id ⇄ claude_pid` into the DB.
44
+ - The MCP server resolves *its own* owning `claude_pid` by walking `/proc` and
45
+ looks up the session_id bound to it.
46
+
47
+ 1 session ⇄ 1 MCP process, so this is unambiguous and fully automatic — the
48
+ model never has to carry an ID around.
49
+
50
+ ## Data model (`~/.cabildo/chat.db`, WAL)
51
+
52
+ | table | role |
53
+ |---|---|
54
+ | `sessions` | one row per agent: `handle`, `cwd`, `project`, `claude_pid`, `last_seen_at` |
55
+ | `topics` | channels; auto per-project channel `#proj:<name>` + global `#general` |
56
+ | `subscriptions` | who follows which topic |
57
+ | `messages` | `from` → `topic` **or** `to_session` (DM), `body`, `urgent` |
58
+ | `reads` | explicit read receipts — messages are **never** auto-read |
59
+ | `claims` | advisory leases (`resource`, holder, TTL) for coordination |
60
+
61
+ ## MCP tools
62
+
63
+ **Presence:** `whoami`, `set_handle`, `list_agents`
64
+ **Channels:** `list_topics`, `join_topic`, `leave_topic`
65
+ **Messaging:** `send(body, to=?, topic=?, urgent=?)`, `inbox(unread_only=?)`,
66
+ `read(message_ids|topic|all)`, `history(topic|with_agent)`
67
+ **Coordination:** `claim(resource, ttl_seconds, note)`, `release(resource)`,
68
+ `claims()`
69
+
70
+ ### Coordinating a push
71
+
72
+ ```
73
+ claims() # anyone holding it?
74
+ claim("push:myrepo", note="rebase+push") # ok:false tells you who holds it
75
+ # ... do the commit & push ...
76
+ release("push:myrepo")
77
+ ```
78
+
79
+ Locks are **advisory** (agents cooperate by convention) and auto-expire via TTL,
80
+ so a crashed agent never deadlocks the bus. `SessionEnd` also drops a session's
81
+ locks.
82
+
83
+ ## Hook surface
84
+
85
+ Hooks are the only way into a running session's context, and the tool-call
86
+ loop is the only clock: an agent that is thinking or writing text cannot be
87
+ reached until its next tool call or turn boundary. cabildo uses six events:
88
+
89
+ | event | job |
90
+ |---|---|
91
+ | `SessionStart` | register on the bus, bind `session_id ⇄ claude_pid`, inject a banner (who you are, peers, locks, unread) |
92
+ | `UserPromptSubmit` | refresh liveness, inject a one-line `📬 N unread …` notice (silent when nothing) |
93
+ | `PostToolUse` (`*`) | **mid-turn delivery**: inject unread URGENT mail at the next tool-call boundary. Each message injects once (tracked in `deliveries`; delivery ≠ read). Ordinary mail never interrupts a turn. Also keeps `last_seen` fresh during long autonomous turns. |
94
+ | `PreToolUse` (`Bash\|Edit\|Write\|NotebookEdit`) | **claims enforcement**: deny `git push` while another session holds `push:<project>`, and deny edits to paths another session holds `file:<path>` on (globs work: `file:src/*`). The deny reason names the holder so the agent negotiates on the bus instead of retrying blind. Decision logic in `cabildo/guard.py`. |
95
+ | `Stop` | refuse to go idle with unread URGENT mail — delivery is pull-based, so an idle agent is unreachable; the hook stops the stopping instead |
96
+ | `SessionEnd` | deregister, drop the session's locks |
97
+
98
+ Urgency is the escalation axis: normal mail waits for the next user turn;
99
+ urgent mail interrupts at the next tool call and holds the turn open at Stop.
100
+ Locks stay advisory between cooperating agents but the PreToolUse guard gives
101
+ them teeth at the two places where clobbering actually happens (push, file
102
+ writes).
103
+
104
+ Deliberately unused: `PreCompact`/`PostCompact`, `SubagentStart/Stop`,
105
+ `Notification`, `TeammateIdle` — nothing on the bus needs them yet, and every
106
+ extra per-event hook is latency on somebody's session.
107
+
108
+ ## Install
109
+
110
+ ```bash
111
+ python scripts/install.py
112
+ ```
113
+
114
+ This creates a venv, installs the package, registers the MCP server at
115
+ **user scope** (all projects), and appends the hooks to
116
+ `~/.claude/settings.json` (existing hooks are preserved). See the script for the
117
+ exact commands if you'd rather do it by hand.
118
+
119
+ ## Config
120
+
121
+ - `CABILDO_HOME` — where the DB lives (default `~/.cabildo`).
@@ -0,0 +1,96 @@
1
+ # cabildo
2
+
3
+ A shared **IPC bus / chat room** for coordinating multiple Claude Code agents
4
+ running at the same time. Agents can message each other (direct or by topic),
5
+ discover who else is working, and — crucially — take **advisory locks** to
6
+ coordinate shared actions like committing and pushing.
7
+
8
+ It's a normal Python package: one **stdio MCP server** (the tools agents call)
9
+ plus a set of **hook scripts** (delivery, liveness, and lock enforcement). All state lives in
10
+ a single SQLite DB shared by every session.
11
+
12
+ ## How identity works (the tricky bit)
13
+
14
+ Claude Code does **not** give an MCP server the `session_id`. But hooks *do* get
15
+ it, and both the hooks and the MCP subprocess are descendants of the same
16
+ `claude` process. So:
17
+
18
+ - The `SessionStart` hook writes `session_id ⇄ claude_pid` into the DB.
19
+ - The MCP server resolves *its own* owning `claude_pid` by walking `/proc` and
20
+ looks up the session_id bound to it.
21
+
22
+ 1 session ⇄ 1 MCP process, so this is unambiguous and fully automatic — the
23
+ model never has to carry an ID around.
24
+
25
+ ## Data model (`~/.cabildo/chat.db`, WAL)
26
+
27
+ | table | role |
28
+ |---|---|
29
+ | `sessions` | one row per agent: `handle`, `cwd`, `project`, `claude_pid`, `last_seen_at` |
30
+ | `topics` | channels; auto per-project channel `#proj:<name>` + global `#general` |
31
+ | `subscriptions` | who follows which topic |
32
+ | `messages` | `from` → `topic` **or** `to_session` (DM), `body`, `urgent` |
33
+ | `reads` | explicit read receipts — messages are **never** auto-read |
34
+ | `claims` | advisory leases (`resource`, holder, TTL) for coordination |
35
+
36
+ ## MCP tools
37
+
38
+ **Presence:** `whoami`, `set_handle`, `list_agents`
39
+ **Channels:** `list_topics`, `join_topic`, `leave_topic`
40
+ **Messaging:** `send(body, to=?, topic=?, urgent=?)`, `inbox(unread_only=?)`,
41
+ `read(message_ids|topic|all)`, `history(topic|with_agent)`
42
+ **Coordination:** `claim(resource, ttl_seconds, note)`, `release(resource)`,
43
+ `claims()`
44
+
45
+ ### Coordinating a push
46
+
47
+ ```
48
+ claims() # anyone holding it?
49
+ claim("push:myrepo", note="rebase+push") # ok:false tells you who holds it
50
+ # ... do the commit & push ...
51
+ release("push:myrepo")
52
+ ```
53
+
54
+ Locks are **advisory** (agents cooperate by convention) and auto-expire via TTL,
55
+ so a crashed agent never deadlocks the bus. `SessionEnd` also drops a session's
56
+ locks.
57
+
58
+ ## Hook surface
59
+
60
+ Hooks are the only way into a running session's context, and the tool-call
61
+ loop is the only clock: an agent that is thinking or writing text cannot be
62
+ reached until its next tool call or turn boundary. cabildo uses six events:
63
+
64
+ | event | job |
65
+ |---|---|
66
+ | `SessionStart` | register on the bus, bind `session_id ⇄ claude_pid`, inject a banner (who you are, peers, locks, unread) |
67
+ | `UserPromptSubmit` | refresh liveness, inject a one-line `📬 N unread …` notice (silent when nothing) |
68
+ | `PostToolUse` (`*`) | **mid-turn delivery**: inject unread URGENT mail at the next tool-call boundary. Each message injects once (tracked in `deliveries`; delivery ≠ read). Ordinary mail never interrupts a turn. Also keeps `last_seen` fresh during long autonomous turns. |
69
+ | `PreToolUse` (`Bash\|Edit\|Write\|NotebookEdit`) | **claims enforcement**: deny `git push` while another session holds `push:<project>`, and deny edits to paths another session holds `file:<path>` on (globs work: `file:src/*`). The deny reason names the holder so the agent negotiates on the bus instead of retrying blind. Decision logic in `cabildo/guard.py`. |
70
+ | `Stop` | refuse to go idle with unread URGENT mail — delivery is pull-based, so an idle agent is unreachable; the hook stops the stopping instead |
71
+ | `SessionEnd` | deregister, drop the session's locks |
72
+
73
+ Urgency is the escalation axis: normal mail waits for the next user turn;
74
+ urgent mail interrupts at the next tool call and holds the turn open at Stop.
75
+ Locks stay advisory between cooperating agents but the PreToolUse guard gives
76
+ them teeth at the two places where clobbering actually happens (push, file
77
+ writes).
78
+
79
+ Deliberately unused: `PreCompact`/`PostCompact`, `SubagentStart/Stop`,
80
+ `Notification`, `TeammateIdle` — nothing on the bus needs them yet, and every
81
+ extra per-event hook is latency on somebody's session.
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ python scripts/install.py
87
+ ```
88
+
89
+ This creates a venv, installs the package, registers the MCP server at
90
+ **user scope** (all projects), and appends the hooks to
91
+ `~/.claude/settings.json` (existing hooks are preserved). See the script for the
92
+ exact commands if you'd rather do it by hand.
93
+
94
+ ## Config
95
+
96
+ - `CABILDO_HOME` — where the DB lives (default `~/.cabildo`).
@@ -0,0 +1,314 @@
1
+ - generic [ref=f7e1]:
2
+ - link "Skip to main content" [ref=f7e2] [cursor=pointer]:
3
+ - /url: "#content"
4
+ - alert [ref=f7e258]:
5
+ - generic [ref=f7e259]: Error
6
+ - text: The trusted publisher could not be registered
7
+ - button "Close" [ref=f7e262] [cursor=pointer]
8
+ - banner [ref=f7e3]:
9
+ - generic [ref=f7e5]:
10
+ - generic [ref=f7e6]:
11
+ - link [ref=f7e8] [cursor=pointer]:
12
+ - /url: /
13
+ - img "PyPI" [ref=f7e9]
14
+ - search [ref=f7e10]:
15
+ - generic [ref=f7e11]: Search PyPI
16
+ - textbox "Search PyPI" [ref=f7e12]:
17
+ - /placeholder: Type '/' to search projects
18
+ - button "Search" [ref=f7e13]
19
+ - navigation "Main navigation" [ref=f7e265]:
20
+ - button "View menu" [ref=f7e266] [cursor=pointer]:
21
+ - img "User Image" [ref=f7e267]
22
+ - text: jorgeecardona
23
+ - main [ref=f7e30]:
24
+ - generic [ref=f7e31]:
25
+ - navigation "Breadcrumbs" [ref=f7e32]:
26
+ - list [ref=f7e33]:
27
+ - listitem [ref=f7e34]: Your account
28
+ - generic [ref=f7e35]:
29
+ - navigation "Account navigation" [ref=f7e38]:
30
+ - list [ref=f7e39]:
31
+ - listitem [ref=f7e40]:
32
+ - link "Your projects" [ref=f7e41] [cursor=pointer]:
33
+ - /url: /manage/projects/
34
+ - listitem [ref=f7e43]:
35
+ - link "Your organizations" [ref=f7e44] [cursor=pointer]:
36
+ - /url: /manage/organizations/
37
+ - listitem [ref=f7e46]:
38
+ - link "Account settings" [ref=f7e47] [cursor=pointer]:
39
+ - /url: /manage/account/
40
+ - listitem [ref=f7e49]:
41
+ - link "Publishing" [ref=f7e50] [cursor=pointer]:
42
+ - /url: /manage/account/publishing/
43
+ - generic [ref=f7e52]:
44
+ - navigation "Account navigation":
45
+ - list:
46
+ - listitem
47
+ - listitem
48
+ - listitem
49
+ - listitem
50
+ - generic [ref=f7e53]:
51
+ - generic [ref=f7e54]:
52
+ - heading "Trusted Publisher Management" [level=1] [ref=f7e55]
53
+ - paragraph [ref=f7e56]: OpenID Connect (OIDC) provides a flexible, credential-free mechanism for delegating publishing authority for a PyPI package to a trusted third party service, like GitHub Actions.
54
+ - paragraph [ref=f7e57]: PyPI users and projects can use trusted publishers to automate their release processes, without needing to use API tokens or passwords.
55
+ - paragraph [ref=f7e58]:
56
+ - text: You can read more about trusted publishers and how to use them
57
+ - link "here" [ref=f7e59] [cursor=pointer]:
58
+ - /url: https://docs.pypi.org/trusted-publishers/
59
+ - text: .
60
+ - heading "Manage publishers" [level=2] [ref=f7e60]
61
+ - heading "Projects with active publishers" [level=3] [ref=f7e61]
62
+ - table [ref=f7e62]:
63
+ - rowgroup [ref=f7e63]:
64
+ - row [ref=f7e64]:
65
+ - columnheader "Project" [ref=f7e65]
66
+ - columnheader [ref=f7e66]
67
+ - rowgroup [ref=f7e67]:
68
+ - row [ref=f7e68]:
69
+ - cell "folderbot" [ref=f7e69]
70
+ - cell [ref=f7e70]:
71
+ - link "Manage" [ref=f7e71] [cursor=pointer]:
72
+ - /url: /manage/project/folderbot/settings/publishing/
73
+ - row [ref=f7e72]:
74
+ - cell "musil" [ref=f7e73]
75
+ - cell [ref=f7e74]:
76
+ - link "Manage" [ref=f7e75] [cursor=pointer]:
77
+ - /url: /manage/project/musil/settings/publishing/
78
+ - row [ref=f7e76]:
79
+ - cell "pytest-parallex" [ref=f7e77]
80
+ - cell [ref=f7e78]:
81
+ - link "Manage" [ref=f7e79] [cursor=pointer]:
82
+ - /url: /manage/project/pytest-parallex/settings/publishing/
83
+ - heading "Pending publishers" [level=3] [ref=f7e80]
84
+ - paragraph [ref=f7e81]: No pending publishers are currently configured. Publishers for projects that don't exist yet can be added below.
85
+ - generic [ref=f7e82]:
86
+ - heading "Add a new pending publisher" [level=2] [ref=f7e83]
87
+ - paragraph [ref=f7e84]: You can use this page to register "pending" trusted publishers.
88
+ - paragraph [ref=f7e85]:
89
+ - text: These publishers behave similarly to trusted publishers registered against specific projects, except that they allow users to
90
+ - strong [ref=f7e86]: create
91
+ - text: the project if it doesn't already exist. Once the project is created, the "pending" publisher becomes an ordinary trusted publisher. You can read more about "pending" and ordinary trusted publishers
92
+ - link "here" [ref=f7e87] [cursor=pointer]:
93
+ - /url: https://docs.pypi.org/trusted-publishers/
94
+ - text: .
95
+ - paragraph [ref=f7e88]:
96
+ - text: Configuring a "pending" publisher for a project name does
97
+ - strong [ref=f7e89]: not
98
+ - text: reserve that name. Until the project is created, any other user may create it, including via their own "pending" publisher.
99
+ - generic [ref=f7e90]:
100
+ - generic [ref=f7e91]:
101
+ - button "GitHub" [ref=f7e92] [cursor=pointer]
102
+ - button "GitLab" [active] [ref=f7e93] [cursor=pointer]
103
+ - button "Google" [ref=f7e94] [cursor=pointer]
104
+ - button "ActiveState" [ref=f7e95] [cursor=pointer]
105
+ - generic [ref=f7e271]:
106
+ - paragraph [ref=f7e272]:
107
+ - text: Read more about GitLab CI/CD OpenID Connect support
108
+ - link "here" [ref=f7e273] [cursor=pointer]:
109
+ - /url: https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html
110
+ - text: .
111
+ - generic [ref=f7e274]:
112
+ - generic [ref=f7e275]:
113
+ - generic [ref=f7e276]: PyPI Project Name (required)
114
+ - textbox "project name" [ref=f7e277]
115
+ - paragraph [ref=f7e278]: The project (on PyPI) that will be created when this publisher is used
116
+ - generic [ref=f7e279]:
117
+ - generic [ref=f7e280]: Namespace (required)
118
+ - textbox "Namespace (required)" [ref=f7e281]:
119
+ - /placeholder: namespace
120
+ - paragraph [ref=f7e282]: The GitLab username or GitLab group/subgroup namespace that the project is under
121
+ - generic [ref=f7e283]:
122
+ - generic [ref=f7e284]: Project name (required)
123
+ - textbox "Project name (required) ActiveState Project name (required)" [ref=f7e285]:
124
+ - /placeholder: project
125
+ - paragraph [ref=f7e286]: The name of the GitLab project that contains the publishing workflow
126
+ - generic [ref=f7e287]:
127
+ - generic [ref=f7e288]: Top-level pipeline file path (required)
128
+ - textbox "Top-level pipeline file path (required)" [ref=f7e289]:
129
+ - /placeholder: .gitlab-ci.yml
130
+ - paragraph [ref=f7e290]: The file path of the top-level pipeline, relative to the project's root. This file should exist in the project configured above (external pipelines are not supported).
131
+ - generic [ref=f7e291]:
132
+ - generic [ref=f7e292]: Environment name (optional)
133
+ - textbox "release" [ref=f7e293]
134
+ - paragraph [ref=f7e294]:
135
+ - text: The name of the
136
+ - link "GitLab CI/CD environment" [ref=f7e295] [cursor=pointer]:
137
+ - /url: https://docs.gitlab.com/ee/ci/environments/
138
+ - text: that the above workflow uses for publishing. This should be configured under the project's settings. While not required, a dedicated publishing environment is
139
+ - strong [ref=f7e296]: strongly
140
+ - text: encouraged,
141
+ - strong [ref=f7e297]: especially
142
+ - text: if your project has maintainers with commit access who shouldn't have PyPI publishing access.
143
+ - button "Add" [ref=f7e299] [cursor=pointer]
144
+ - contentinfo [ref=f7e126]:
145
+ - generic [ref=f7e128]:
146
+ - generic [ref=f7e129]:
147
+ - heading "Help" [level=2] [ref=f7e130]
148
+ - navigation "Help navigation" [ref=f7e131]:
149
+ - list [ref=f7e132]:
150
+ - listitem [ref=f7e133]:
151
+ - link "Installing packages" [ref=f7e134] [cursor=pointer]:
152
+ - /url: https://packaging.python.org/tutorials/installing-packages/
153
+ - listitem [ref=f7e135]:
154
+ - link "Uploading packages" [ref=f7e136] [cursor=pointer]:
155
+ - /url: https://packaging.python.org/tutorials/packaging-projects/
156
+ - listitem [ref=f7e137]:
157
+ - link "User guide" [ref=f7e138] [cursor=pointer]:
158
+ - /url: https://packaging.python.org/
159
+ - listitem [ref=f7e139]:
160
+ - link "Project name retention" [ref=f7e140] [cursor=pointer]:
161
+ - /url: https://www.python.org/dev/peps/pep-0541/
162
+ - listitem [ref=f7e141]:
163
+ - link "FAQs" [ref=f7e142] [cursor=pointer]:
164
+ - /url: /help/
165
+ - generic [ref=f7e143]:
166
+ - heading "About PyPI" [level=2] [ref=f7e144]
167
+ - navigation "About PyPI navigation" [ref=f7e145]:
168
+ - list [ref=f7e146]:
169
+ - listitem [ref=f7e147]:
170
+ - link "PyPI Blog" [ref=f7e148] [cursor=pointer]:
171
+ - /url: https://blog.pypi.org
172
+ - listitem [ref=f7e149]:
173
+ - link "Infrastructure dashboard" [ref=f7e150] [cursor=pointer]:
174
+ - /url: https://dtdg.co/pypi
175
+ - listitem [ref=f7e151]:
176
+ - link "Statistics" [ref=f7e152] [cursor=pointer]:
177
+ - /url: /stats/
178
+ - listitem [ref=f7e153]:
179
+ - link "Logos & trademarks" [ref=f7e154] [cursor=pointer]:
180
+ - /url: /trademarks/
181
+ - listitem [ref=f7e155]:
182
+ - link "Our sponsors" [ref=f7e156] [cursor=pointer]:
183
+ - /url: /sponsors/
184
+ - generic [ref=f7e157]:
185
+ - heading "Contributing to PyPI" [level=2] [ref=f7e158]
186
+ - navigation "How to contribute navigation" [ref=f7e159]:
187
+ - list [ref=f7e160]:
188
+ - listitem [ref=f7e161]:
189
+ - link "Bugs and feedback" [ref=f7e162] [cursor=pointer]:
190
+ - /url: /help/#feedback
191
+ - listitem [ref=f7e163]:
192
+ - link "Contribute on GitHub" [ref=f7e164] [cursor=pointer]:
193
+ - /url: https://github.com/pypi/warehouse
194
+ - listitem [ref=f7e165]:
195
+ - link "Translate PyPI" [ref=f7e166] [cursor=pointer]:
196
+ - /url: https://hosted.weblate.org/projects/pypa/warehouse/
197
+ - listitem [ref=f7e167]:
198
+ - link "Sponsor PyPI" [ref=f7e168] [cursor=pointer]:
199
+ - /url: /sponsors/
200
+ - listitem [ref=f7e169]:
201
+ - link "Development credits" [ref=f7e170] [cursor=pointer]:
202
+ - /url: https://github.com/pypi/warehouse/graphs/contributors
203
+ - generic [ref=f7e171]:
204
+ - heading "Using PyPI" [level=2] [ref=f7e172]
205
+ - navigation "Using PyPI navigation" [ref=f7e173]:
206
+ - list [ref=f7e174]:
207
+ - listitem [ref=f7e175]:
208
+ - link "Terms of Service" [ref=f7e176] [cursor=pointer]:
209
+ - /url: https://policies.python.org/pypi.org/Terms-of-Service/
210
+ - listitem [ref=f7e177]:
211
+ - link "Report security issue" [ref=f7e178] [cursor=pointer]:
212
+ - /url: /security/
213
+ - listitem [ref=f7e179]:
214
+ - link "Code of conduct" [ref=f7e180] [cursor=pointer]:
215
+ - /url: https://policies.python.org/python.org/code-of-conduct/
216
+ - listitem [ref=f7e181]:
217
+ - link "Privacy Notice" [ref=f7e182] [cursor=pointer]:
218
+ - /url: https://policies.python.org/pypi.org/Privacy-Notice/
219
+ - listitem [ref=f7e183]:
220
+ - link "Acceptable Use Policy" [ref=f7e184] [cursor=pointer]:
221
+ - /url: https://policies.python.org/pypi.org/Acceptable-Use-Policy/
222
+ - separator [ref=f7e185]
223
+ - generic [ref=f7e186]:
224
+ - paragraph [ref=f7e187]:
225
+ - text: "Status:"
226
+ - link "All Systems Operational" [ref=f7e270] [cursor=pointer]:
227
+ - /url: https://status.python.org/
228
+ - paragraph [ref=f7e189]:
229
+ - text: Developed and maintained by the Python community, for the Python community.
230
+ - link "Donate today!" [ref=f7e190] [cursor=pointer]:
231
+ - /url: https://donate.pypi.org
232
+ - paragraph [ref=f7e191]:
233
+ - text: "\"PyPI\", \"Python Package Index\", and the blocks logos are registered"
234
+ - link "trademarks" [ref=f7e192] [cursor=pointer]:
235
+ - /url: /trademarks/
236
+ - text: of the
237
+ - link "Python Software Foundation" [ref=f7e193] [cursor=pointer]:
238
+ - /url: https://www.python.org/psf-landing
239
+ - text: .
240
+ - paragraph [ref=f7e194]:
241
+ - text: © 2026
242
+ - link "Python Software Foundation" [ref=f7e195] [cursor=pointer]:
243
+ - /url: https://www.python.org/psf-landing/
244
+ - link "Site map" [ref=f7e196] [cursor=pointer]:
245
+ - /url: /sitemap/
246
+ - paragraph [ref=f7e197]:
247
+ - text: Deployed from
248
+ - link [ref=f7e198] [cursor=pointer]:
249
+ - /url: https://github.com/pypi/warehouse/commit/ce84faeb10ffa2742a6c777275ae32fdece657ef
250
+ - code [ref=f7e199]: ce84fae
251
+ - text: 
252
+ - button "Switch to desktop version" [ref=f7e201] [cursor=pointer]
253
+ - list [ref=f7e204]:
254
+ - listitem [ref=f7e205]:
255
+ - button "English"
256
+ - listitem [ref=f7e206]:
257
+ - button "español" [ref=f7e207]
258
+ - listitem [ref=f7e208]:
259
+ - button "français" [ref=f7e209]
260
+ - listitem [ref=f7e210]:
261
+ - button "日本語" [ref=f7e211]
262
+ - listitem [ref=f7e212]:
263
+ - button "português (Brasil)" [ref=f7e213]
264
+ - listitem [ref=f7e214]:
265
+ - button "українська" [ref=f7e215]
266
+ - listitem [ref=f7e216]:
267
+ - button "Ελληνικά" [ref=f7e217]
268
+ - listitem [ref=f7e218]:
269
+ - button "Deutsch" [ref=f7e219]
270
+ - listitem [ref=f7e220]:
271
+ - button "中文 (简体)" [ref=f7e221]
272
+ - listitem [ref=f7e222]:
273
+ - button "中文 (繁體)" [ref=f7e223]
274
+ - listitem [ref=f7e224]:
275
+ - button "русский" [ref=f7e225]
276
+ - listitem [ref=f7e226]:
277
+ - button "עברית" [ref=f7e227]
278
+ - listitem [ref=f7e228]:
279
+ - button "Esperanto" [ref=f7e229]
280
+ - listitem [ref=f7e230]:
281
+ - button "한국어" [ref=f7e231]
282
+ - generic [ref=f7e232]:
283
+ - link "AWS Cloud computing and Security Sponsor" [ref=f7e233] [cursor=pointer]:
284
+ - /url: https://aws.amazon.com/
285
+ - generic [ref=f7e234]: AWS
286
+ - generic [ref=f7e235]: Cloud computing and Security Sponsor
287
+ - link "Datadog Monitoring" [ref=f7e236] [cursor=pointer]:
288
+ - /url: https://www.datadoghq.com/
289
+ - generic [ref=f7e237]: Datadog
290
+ - generic [ref=f7e238]: Monitoring
291
+ - link "Depot Continuous Integration" [ref=f7e239] [cursor=pointer]:
292
+ - /url: https://depot.dev
293
+ - generic [ref=f7e240]: Depot
294
+ - generic [ref=f7e241]: Continuous Integration
295
+ - link "Fastly CDN" [ref=f7e242] [cursor=pointer]:
296
+ - /url: https://www.fastly.com/
297
+ - generic [ref=f7e243]: Fastly
298
+ - generic [ref=f7e244]: CDN
299
+ - link "Google Download Analytics" [ref=f7e245] [cursor=pointer]:
300
+ - /url: https://careers.google.com/
301
+ - generic [ref=f7e246]: Google
302
+ - generic [ref=f7e247]: Download Analytics
303
+ - link "Pingdom Monitoring" [ref=f7e248] [cursor=pointer]:
304
+ - /url: https://www.pingdom.com/
305
+ - generic [ref=f7e249]: Pingdom
306
+ - generic [ref=f7e250]: Monitoring
307
+ - link "Sentry Error logging" [ref=f7e251] [cursor=pointer]:
308
+ - /url: https://sentry.io/for/python/?utm_source=pypi&utm_medium=paid-community&utm_campaign=python-na-evergreen&utm_content=static-ad-pypi-sponsor-learnmore
309
+ - generic [ref=f7e252]: Sentry
310
+ - generic [ref=f7e253]: Error logging
311
+ - link "StatusPage Status page" [ref=f7e254] [cursor=pointer]:
312
+ - /url: https://statuspage.io
313
+ - generic [ref=f7e255]: StatusPage
314
+ - generic [ref=f7e256]: Status page
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "cabildo"
3
+ version = "0.1.0"
4
+ description = "IPC bus + chat room for coordinating parallel Claude Code agents: messaging, presence, and advisory locks with hook-level enforcement"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [
10
+ { name = "Jorge Eduardo Cardona Gaviria", email = "jorgeecardona@gmail.com" },
11
+ ]
12
+ keywords = ["claude-code", "agents", "mcp", "ipc", "coordination", "multi-agent"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Operating System :: MacOS",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Topic :: Communications :: Chat",
25
+ ]
26
+ dependencies = [
27
+ "mcp>=1.2",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=8.0"]
32
+
33
+ [project.scripts]
34
+ cabildo = "cabildo.cli:main"
35
+ cabildo-mcp = "cabildo.mcp_server:main"
36
+ cabildo-hook = "cabildo.hooks.cli:main"
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+ addopts = "-q"
41
+
42
+ [build-system]
43
+ requires = ["hatchling"]
44
+ build-backend = "hatchling.build"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/cabildo"]
48
+
49
+ [tool.hatch.build.targets.sdist]
50
+ exclude = [".claude", ".gitignore"]