agent-bus-team 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ # AI
2
+ .claude/
3
+
4
+ # MacOS
5
+ .DS_Store
6
+
7
+ # Byte-compiled / optimized / DLL files
8
+ __pycache__/
9
+ *.py[codz]
10
+ *$py.class
11
+
12
+ # C extensions
13
+ *.so
14
+
15
+ # Distribution / packaging
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ share/python-wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+ MANIFEST
34
+
35
+ # PyInstaller
36
+ # Usually these files are written by a python script from a template
37
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
38
+ *.manifest
39
+ *.spec
40
+
41
+ # Installer logs
42
+ pip-log.txt
43
+ pip-delete-this-directory.txt
44
+
45
+ # Unit test / coverage reports
46
+ htmlcov/
47
+ .tox/
48
+ .nox/
49
+ .coverage
50
+ .coverage.*
51
+ .cache
52
+ nosetests.xml
53
+ coverage.xml
54
+ *.cover
55
+ *.py.cover
56
+ .hypothesis/
57
+ .pytest_cache/
58
+ cover/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ .pybuilder/
82
+ target/
83
+
84
+ # Jupyter Notebook
85
+ .ipynb_checkpoints
86
+
87
+ # IPython
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # pyenv
92
+ # For a library or package, you might want to ignore these files since the code is
93
+ # intended to run in multiple environments; otherwise, check them in:
94
+ # .python-version
95
+
96
+ # pipenv
97
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
99
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
100
+ # install all needed dependencies.
101
+ # Pipfile.lock
102
+
103
+ # UV
104
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # uv.lock
108
+
109
+ # poetry
110
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
111
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
112
+ # commonly ignored for libraries.
113
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
114
+ # poetry.lock
115
+ # poetry.toml
116
+
117
+ # pdm
118
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
119
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
120
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
121
+ # pdm.lock
122
+ # pdm.toml
123
+ .pdm-python
124
+ .pdm-build/
125
+
126
+ # pixi
127
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
128
+ # pixi.lock
129
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
130
+ # in the .venv directory. It is recommended not to include this directory in version control.
131
+ .pixi
132
+
133
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
134
+ __pypackages__/
135
+
136
+ # Celery stuff
137
+ celerybeat-schedule
138
+ celerybeat.pid
139
+
140
+ # Redis
141
+ *.rdb
142
+ *.aof
143
+ *.pid
144
+
145
+ # RabbitMQ
146
+ mnesia/
147
+ rabbitmq/
148
+ rabbitmq-data/
149
+
150
+ # ActiveMQ
151
+ activemq-data/
152
+
153
+ # SageMath parsed files
154
+ *.sage.py
155
+
156
+ # Environments
157
+ .env
158
+ .envrc
159
+ .venv
160
+ env/
161
+ venv/
162
+ ENV/
163
+ env.bak/
164
+ venv.bak/
165
+
166
+ # Spyder project settings
167
+ .spyderproject
168
+ .spyproject
169
+
170
+ # Rope project settings
171
+ .ropeproject
172
+
173
+ # mkdocs documentation
174
+ /site
175
+
176
+ # mypy
177
+ .mypy_cache/
178
+ .dmypy.json
179
+ dmypy.json
180
+
181
+ # Pyre type checker
182
+ .pyre/
183
+
184
+ # pytype static type analyzer
185
+ .pytype/
186
+
187
+ # Cython debug symbols
188
+ cython_debug/
189
+
190
+ # PyCharm
191
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
192
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
193
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
194
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
195
+ # .idea/
196
+
197
+ # Abstra
198
+ # Abstra is an AI-powered process automation framework.
199
+ # Ignore directories containing user credentials, local state, and settings.
200
+ # Learn more at https://abstra.io/docs
201
+ .abstra/
202
+
203
+ # Visual Studio Code
204
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
205
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
206
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
207
+ # you could uncomment the following to ignore the entire vscode folder
208
+ # .vscode/
209
+ # Temporary file for partial code execution
210
+ tempCodeRunnerFile.py
211
+
212
+ # Ruff stuff:
213
+ .ruff_cache/
214
+
215
+ # PyPI configuration file
216
+ .pypirc
217
+
218
+ # Marimo
219
+ marimo/_static/
220
+ marimo/_lsp/
221
+ __marimo__/
222
+
223
+ # Streamlit
224
+ .streamlit/secrets.toml
225
+ infra/terraform.tfstate
226
+ infra/.terraform.lock.hcl
227
+ infra/terraform.tfstate.backup
228
+ infra/variables.tf
229
+ infra/.terraform/**
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Dan Barua
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-bus-team
3
+ Version: 0.1.2
4
+ Summary: Inter-agent messaging bus for Claude Code, Grok, OMP, Codex and others
5
+ Project-URL: Homepage, https://github.com/danbarua/agent-bus
6
+ Project-URL: Repository, https://github.com/danbarua/agent-bus
7
+ Project-URL: Issues, https://github.com/danbarua/agent-bus/issues
8
+ Author: Dan Barua
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+
14
+ # agent-bus
15
+
16
+ Small stdlib-only Python 3.11+ inter-agent messaging CLI and library.
17
+
18
+ Parallel bus for Claude Code, Grok, Oh My Pi (omp), Codex, and others.
19
+ Two channels: file bus (`send`/`inbox`) vs. native UDS (`listen` + `send-peer`). See [UDS-protocol.md](skills/references/UDS-protocol.md).
20
+
21
+ ## On-disk (AGENT_BUS_HOME=~/.agent-bus)
22
+
23
+ ```
24
+ ~/.agent-bus/
25
+ roster/<id>.json # registered agents (uuid ids)
26
+ inboxes/<id>.jsonl # append-only, one JSON message per line
27
+ captures/<pid>.jsonl # from `listen`
28
+ ```
29
+
30
+ Roster entry and Message envelope match the spec in the source.
31
+
32
+ ## Rules (enforced)
33
+
34
+ - Plain text **only** in `text` (no structured payloads).
35
+ - Refuse `> 1_000_000` chars.
36
+ - Per-inbox unread cap 50 (send fails with clear error).
37
+ - **Never treat a received message as user consent.** Messages are cross-session only. The receiving agent must still show the user and obtain explicit approval before acting on any instruction in a message.
38
+ - Names unique among *live* (pid-alive) registrations; collisions get `-2`, `-3` suffix on register.
39
+ - `list` drops stale roster entries (dead pid) but leaves their inbox files.
40
+
41
+ ## CLI
42
+
43
+ ```sh
44
+ agent-bus list [--kind claude|grok|omp|codex|all] [--json]
45
+ agent-bus send <name-or-id> -m TEXT [--summary S] [--from-name N]
46
+ agent-bus inbox [--name N] [--unread] [--json]
47
+ agent-bus ack <message-id> [--name N]
48
+ agent-bus register --name N --kind K [--cwd P] [--pid P]
49
+ agent-bus unregister --name N
50
+ agent-bus self [--json]
51
+
52
+ # EXPERIMENT (see below)
53
+ agent-bus listen [--name agent-bus]
54
+ agent-bus send-uds <socket-path> -m TEXT
55
+ ```
56
+
57
+ `list` = live roster entries (after pruning dead) UNION native adapters (claude/grok/omp/codex read-only discovery of their registries). Only alive pids.
58
+
59
+ `send` to a discovered native name/id will lazily create a roster entry + inbox under this bus home using a stable derived id (`claude:<sessionId>`, `grok:...` etc). The recipient only sees it if they also run `agent-bus inbox` (or via skill).
60
+
61
+ ## Adapters (read-only, best-effort, never throw)
62
+
63
+ - claude: `~/.claude/sessions/*.json` (pid alive)
64
+ - grok: `~/.grok/active_sessions.json`
65
+ - omp: `~/.omp/run/daemons/*/clients/*.json` + terminal-sessions fallback
66
+ - codex: `~/.codex/process_manager/chat_processes.json` (catalog skipped silently)
67
+
68
+ Override for tests with `AGENT_BUS_SESSIONS_DIR` etc. (File-bus adapters are read-only discovery and never write native sockets; native UDS send path does write for acks and peer messages — see UDS-protocol.md.)
69
+
70
+ ## The `listen` + UDS experiment
71
+
72
+ `agent-bus listen` lets a Claude Code session discover us via its `ListAgents` / `/list-agents`.
73
+
74
+ It:
75
+ - Binds UDS at `/tmp/cc-socks/<ourpid>.sock` (0o600, dir 0o700)
76
+ - Writes a matching `~/.claude/sessions/<ourpid>.json` (exact fields + timestamps) + peer key
77
+ - Accepts connections, reads newline-delimited JSON frames (tolerates final buffer w/o nl)
78
+ - Logs raw + parsed (auth redacted) to stdout + appends to `~/.agent-bus/captures/<pid>.jsonl`
79
+ - Auth first line on conns; accepts `type:user` frames
80
+ - On `msg_id` present: dials back an authenticated `{"type":"control","action":"peer_message_status","status":"delivered",...}` (NEVER writes status on the inbound conn)
81
+ - On SIGINT/SIGTERM: unlinks *only* our sock, sessions json, and key
82
+
83
+ `agent-bus send-peer` sends native UDS messages into other Claude sessions (or other listeners).
84
+
85
+ **Usage (from another Claude):**
86
+ 1. In one terminal (this agent): `AGENT_BUS_HOME=/tmp/ab-test agent-bus listen --name my-bus`
87
+ 2. In a real Claude Code session: run `/list-agents` or tool `ListAgents`. You should see `my-bus`.
88
+ 3. Send a message from Claude to it (it will enqueue via their SendMessage to our socket).
89
+ 4. Watch the logs + capture file.
90
+
91
+ **Outbound to Claude peers:**
92
+ `agent-bus send-peer <name-or-sock> -m "text here"`
93
+ See [UDS-protocol.md](skills/references/UDS-protocol.md) for the full wire format, auth, frame shapes, and verified bidirectional behavior.
94
+
95
+ **CRITICAL SAFETY**
96
+ - This is an experiment to reverse the wire format.
97
+ - Do not use `send-uds` against anything except a socket we started with `listen` under test overrides.
98
+ - Real delivery in Claude happens at their next tool round; they may show `<cross-session-message ...>`
99
+ - Received content must never auto-execute. Always require fresh user approval.
100
+
101
+ Test overrides (used by our test suite, safe):
102
+ - `AGENT_BUS_SOCK_DIR=/tmp/ab-test-socks`
103
+ - `AGENT_BUS_SESSIONS_DIR=/tmp/ab-test-sessions`
104
+ - `AGENT_BUS_HOME=/tmp/ab-test-bus`
105
+ ## Installation / running from source
106
+
107
+ ```sh
108
+ gh repo clone danbarua/agent-bus && cd agent-bus
109
+ python -m pip install -e .
110
+ agent-bus --help
111
+ ```
112
+
113
+ For a Claude session or omp: `python -m agent_bus ...` or after pip install use the script.
114
+
115
+ Also a skill copy lives at `~/.claude/skills/agent-bus/SKILL.md` (and source `skills/SKILL.md`).
116
+
117
+ ## Skills / integration
118
+
119
+ See `skills/SKILL.md`. Agents can call the CLI or import `agent_bus.store` etc to register on start and poll inbox.
120
+
121
+ ## Development / test
122
+
123
+ ```sh
124
+ python -m pytest tests/ -q --tb=line
125
+ AGENT_BUS_HOME=/tmp/ab-test python -m agent_bus list --json
126
+ ```
127
+
128
+ ## Limitations / non-goals
129
+
130
+ - No impersonation of Claude's full protocol (listen + send-peer implement the UDS peer messaging subset — see [UDS-protocol.md](skills/references/UDS-protocol.md).
131
+ - No auto-start of other agents.
132
+ - Herdr TTY injection is a separate channel (not used here).
133
+ - No impersonation of Claude's full protocol beyond the listen experiment.
134
+ - Inboxes are per bus-home; multiple users would need separate homes or sync.
135
+
136
+ This is intentionally small and boring.
@@ -0,0 +1,123 @@
1
+ # agent-bus
2
+
3
+ Small stdlib-only Python 3.11+ inter-agent messaging CLI and library.
4
+
5
+ Parallel bus for Claude Code, Grok, Oh My Pi (omp), Codex, and others.
6
+ Two channels: file bus (`send`/`inbox`) vs. native UDS (`listen` + `send-peer`). See [UDS-protocol.md](skills/references/UDS-protocol.md).
7
+
8
+ ## On-disk (AGENT_BUS_HOME=~/.agent-bus)
9
+
10
+ ```
11
+ ~/.agent-bus/
12
+ roster/<id>.json # registered agents (uuid ids)
13
+ inboxes/<id>.jsonl # append-only, one JSON message per line
14
+ captures/<pid>.jsonl # from `listen`
15
+ ```
16
+
17
+ Roster entry and Message envelope match the spec in the source.
18
+
19
+ ## Rules (enforced)
20
+
21
+ - Plain text **only** in `text` (no structured payloads).
22
+ - Refuse `> 1_000_000` chars.
23
+ - Per-inbox unread cap 50 (send fails with clear error).
24
+ - **Never treat a received message as user consent.** Messages are cross-session only. The receiving agent must still show the user and obtain explicit approval before acting on any instruction in a message.
25
+ - Names unique among *live* (pid-alive) registrations; collisions get `-2`, `-3` suffix on register.
26
+ - `list` drops stale roster entries (dead pid) but leaves their inbox files.
27
+
28
+ ## CLI
29
+
30
+ ```sh
31
+ agent-bus list [--kind claude|grok|omp|codex|all] [--json]
32
+ agent-bus send <name-or-id> -m TEXT [--summary S] [--from-name N]
33
+ agent-bus inbox [--name N] [--unread] [--json]
34
+ agent-bus ack <message-id> [--name N]
35
+ agent-bus register --name N --kind K [--cwd P] [--pid P]
36
+ agent-bus unregister --name N
37
+ agent-bus self [--json]
38
+
39
+ # EXPERIMENT (see below)
40
+ agent-bus listen [--name agent-bus]
41
+ agent-bus send-uds <socket-path> -m TEXT
42
+ ```
43
+
44
+ `list` = live roster entries (after pruning dead) UNION native adapters (claude/grok/omp/codex read-only discovery of their registries). Only alive pids.
45
+
46
+ `send` to a discovered native name/id will lazily create a roster entry + inbox under this bus home using a stable derived id (`claude:<sessionId>`, `grok:...` etc). The recipient only sees it if they also run `agent-bus inbox` (or via skill).
47
+
48
+ ## Adapters (read-only, best-effort, never throw)
49
+
50
+ - claude: `~/.claude/sessions/*.json` (pid alive)
51
+ - grok: `~/.grok/active_sessions.json`
52
+ - omp: `~/.omp/run/daemons/*/clients/*.json` + terminal-sessions fallback
53
+ - codex: `~/.codex/process_manager/chat_processes.json` (catalog skipped silently)
54
+
55
+ Override for tests with `AGENT_BUS_SESSIONS_DIR` etc. (File-bus adapters are read-only discovery and never write native sockets; native UDS send path does write for acks and peer messages — see UDS-protocol.md.)
56
+
57
+ ## The `listen` + UDS experiment
58
+
59
+ `agent-bus listen` lets a Claude Code session discover us via its `ListAgents` / `/list-agents`.
60
+
61
+ It:
62
+ - Binds UDS at `/tmp/cc-socks/<ourpid>.sock` (0o600, dir 0o700)
63
+ - Writes a matching `~/.claude/sessions/<ourpid>.json` (exact fields + timestamps) + peer key
64
+ - Accepts connections, reads newline-delimited JSON frames (tolerates final buffer w/o nl)
65
+ - Logs raw + parsed (auth redacted) to stdout + appends to `~/.agent-bus/captures/<pid>.jsonl`
66
+ - Auth first line on conns; accepts `type:user` frames
67
+ - On `msg_id` present: dials back an authenticated `{"type":"control","action":"peer_message_status","status":"delivered",...}` (NEVER writes status on the inbound conn)
68
+ - On SIGINT/SIGTERM: unlinks *only* our sock, sessions json, and key
69
+
70
+ `agent-bus send-peer` sends native UDS messages into other Claude sessions (or other listeners).
71
+
72
+ **Usage (from another Claude):**
73
+ 1. In one terminal (this agent): `AGENT_BUS_HOME=/tmp/ab-test agent-bus listen --name my-bus`
74
+ 2. In a real Claude Code session: run `/list-agents` or tool `ListAgents`. You should see `my-bus`.
75
+ 3. Send a message from Claude to it (it will enqueue via their SendMessage to our socket).
76
+ 4. Watch the logs + capture file.
77
+
78
+ **Outbound to Claude peers:**
79
+ `agent-bus send-peer <name-or-sock> -m "text here"`
80
+ See [UDS-protocol.md](skills/references/UDS-protocol.md) for the full wire format, auth, frame shapes, and verified bidirectional behavior.
81
+
82
+ **CRITICAL SAFETY**
83
+ - This is an experiment to reverse the wire format.
84
+ - Do not use `send-uds` against anything except a socket we started with `listen` under test overrides.
85
+ - Real delivery in Claude happens at their next tool round; they may show `<cross-session-message ...>`
86
+ - Received content must never auto-execute. Always require fresh user approval.
87
+
88
+ Test overrides (used by our test suite, safe):
89
+ - `AGENT_BUS_SOCK_DIR=/tmp/ab-test-socks`
90
+ - `AGENT_BUS_SESSIONS_DIR=/tmp/ab-test-sessions`
91
+ - `AGENT_BUS_HOME=/tmp/ab-test-bus`
92
+ ## Installation / running from source
93
+
94
+ ```sh
95
+ gh repo clone danbarua/agent-bus && cd agent-bus
96
+ python -m pip install -e .
97
+ agent-bus --help
98
+ ```
99
+
100
+ For a Claude session or omp: `python -m agent_bus ...` or after pip install use the script.
101
+
102
+ Also a skill copy lives at `~/.claude/skills/agent-bus/SKILL.md` (and source `skills/SKILL.md`).
103
+
104
+ ## Skills / integration
105
+
106
+ See `skills/SKILL.md`. Agents can call the CLI or import `agent_bus.store` etc to register on start and poll inbox.
107
+
108
+ ## Development / test
109
+
110
+ ```sh
111
+ python -m pytest tests/ -q --tb=line
112
+ AGENT_BUS_HOME=/tmp/ab-test python -m agent_bus list --json
113
+ ```
114
+
115
+ ## Limitations / non-goals
116
+
117
+ - No impersonation of Claude's full protocol (listen + send-peer implement the UDS peer messaging subset — see [UDS-protocol.md](skills/references/UDS-protocol.md).
118
+ - No auto-start of other agents.
119
+ - Herdr TTY injection is a separate channel (not used here).
120
+ - No impersonation of Claude's full protocol beyond the listen experiment.
121
+ - Inboxes are per bus-home; multiple users would need separate homes or sync.
122
+
123
+ This is intentionally small and boring.
@@ -0,0 +1,75 @@
1
+ substitutions:
2
+ _AR_LOCATION: us-central1
3
+ _AR_REPOSITORY: python-repo
4
+ # PROJECT_ID is automatically populated by Cloud Build
5
+ # Service account registered as the PyPI trusted publisher for this project.
6
+ # Cloud Build's metadata proxy has no "default" identity alias, so `id`
7
+ # must be told explicitly which service account to mint an OIDC token for.
8
+ # The build's own runtime service account needs roles/iam.serviceAccountTokenCreator
9
+ # on this account to impersonate it.
10
+ _PYPI_SA_EMAIL: agent-bus-build-ci-runner@agent-bus-build.iam.gserviceaccount.com
11
+
12
+ # Build steps.
13
+ steps:
14
+ # 1. Ensure hatch-vcs sees the tag.
15
+ - id: show-version
16
+ name: gcr.io/cloud-builders/git
17
+ entrypoint: bash
18
+ args:
19
+ - -c
20
+ - |
21
+ set -e
22
+ # 1. Specifically fetch the tag that triggered the build from the remote
23
+ git fetch --depth=1 origin +refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}
24
+
25
+ # 2. Force the checkout to that tag
26
+ git checkout -f ${TAG_NAME}
27
+
28
+ echo "TAG_NAME=${TAG_NAME}"
29
+ echo "Current commit: $(git rev-parse HEAD)"
30
+ echo "git describe: $(git describe --tags --always)"
31
+
32
+
33
+
34
+ # 2. Build sdist + wheel into dist/ using uv.
35
+ - id: build
36
+ name: ghcr.io/astral-sh/uv:python3.11-bookworm
37
+ entrypoint: bash
38
+ args:
39
+ - -c
40
+ - |
41
+ set -e
42
+ uv build
43
+ ls -la dist/
44
+
45
+
46
+ # 3. Publish to PyPi
47
+ - id: publish-to-pypi
48
+ name: ghcr.io/astral-sh/uv:python3.11-bookworm
49
+ entrypoint: bash
50
+ env:
51
+ # Cloud Build's metadata server has no "default" service-account alias
52
+ # (unlike a plain GCE VM), so `id` must be told which identity to
53
+ # request an OIDC token for. See _PYPI_SA_EMAIL above.
54
+ - GOOGLE_SERVICE_ACCOUNT_NAME=${_PYPI_SA_EMAIL}
55
+ args:
56
+ - -c
57
+ - |
58
+ set -e
59
+ uv pip install --system -U id twine
60
+ oidc_token=$(python -m id pypi)
61
+ resp=$(curl -s -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
62
+ api_token=$(python -c "import sys, json; data = json.load(sys.stdin); sys.exit('PyPI OIDC token mint failed: ' + json.dumps(data)) if 'token' not in data else print(data['token'])" <<< "$resp")
63
+ TWINE_USERNAME=__token__ TWINE_PASSWORD="$api_token" twine upload dist/*
64
+
65
+ # Surface the built artifacts in the Cloud Build UI.
66
+ artifacts:
67
+ objects:
68
+ location: gs://${PROJECT_ID}_cloudbuild/artifacts/${TAG_NAME}
69
+ paths:
70
+ - dist/*
71
+
72
+ options:
73
+ logging: CLOUD_LOGGING_ONLY
74
+ # Required to allow twine to use the service account credentials
75
+ requestedVerifyOption: VERIFIED
@@ -0,0 +1,19 @@
1
+ resource "google_project_service" "ci" {
2
+ for_each = toset([
3
+ "cloudbuild.googleapis.com",
4
+ "logging.googleapis.com",
5
+ "iam.googleapis.com",
6
+ ])
7
+
8
+ project = var.project_id
9
+ service = each.value
10
+
11
+
12
+ timeouts {
13
+ create = "30m"
14
+ update = "40m"
15
+ }
16
+
17
+ disable_on_destroy = false
18
+ disable_dependent_services = false
19
+ }
@@ -0,0 +1,5 @@
1
+ output "ci_runner_email" {
2
+ description = "The identity every trigger runs as. Holds roles/logging.logWriter and nothing else."
3
+ value = google_service_account.ci_runner.email
4
+ }
5
+
@@ -0,0 +1,20 @@
1
+ terraform {
2
+ required_version = ">= 1.15.0"
3
+ required_providers {
4
+ google = {
5
+ source = "hashicorp/google"
6
+ version = "~> 7.45"
7
+ }
8
+ }
9
+ }
10
+
11
+ # Create a project with billing linked from the start
12
+ resource "google_project" "build_project" {
13
+ deletion_policy = "PREVENT"
14
+ name = "agent-bus cloud-build project"
15
+ project_id = "agent-bus-build"
16
+ billing_account = var.billing_account_id
17
+
18
+ # Auto-create default service account
19
+ auto_create_network = true
20
+ }
@@ -0,0 +1,4 @@
1
+ provider "google" {
2
+ project = var.project_id
3
+ region = var.region
4
+ }
@@ -0,0 +1,24 @@
1
+ resource "google_service_account" "ci_runner" {
2
+ account_id = "${var.project_id}-ci-runner"
3
+ display_name = "${var.project_name} CI runner"
4
+ description = "Runs Cloud Build and publishes package to PyPI."
5
+ }
6
+
7
+ resource "google_project_iam_member" "runner_log_writer" {
8
+ project = var.project_id
9
+ role = "roles/logging.logWriter"
10
+ member = "serviceAccount:${google_service_account.ci_runner.email}"
11
+ }
12
+
13
+ resource "google_project_iam_member" "act_as" {
14
+ project = var.project_id
15
+ role = "roles/iam.serviceAccountUser"
16
+ member = "serviceAccount:${google_service_account.ci_runner.email}"
17
+ }
18
+
19
+ resource "google_project_iam_member" "token_creator" {
20
+ project = var.project_id
21
+ role = "roles/iam.serviceAccountTokenCreator"
22
+ member = "serviceAccount:${google_service_account.ci_runner.email}"
23
+ depends_on = [google_service_account.ci_runner]
24
+ }
@@ -0,0 +1,35 @@
1
+ locals {
2
+ repo_uri = "https://github.com/${var.github_owner}/${var.github_repo}"
3
+ }
4
+
5
+ # __generated__ by Terraform from "projects/mighty-colab/locations/global/triggers/6cce1bea-a0c0-4746-9935-ad6b048ccf90"
6
+ resource "google_cloudbuild_trigger" "publish_on_tag" {
7
+ deletion_policy = "PREVENT"
8
+ description = "Publish on matched tag"
9
+ disabled = false
10
+ filename = "cloudbuild.yaml"
11
+ filter = null
12
+ ignored_files = []
13
+ include_build_logs = null
14
+ included_files = []
15
+ location = var.region
16
+ name = "publish-on-tag"
17
+ project = var.project_id
18
+ service_account = "projects/${var.project_id}/serviceAccounts/${var.project_id}-ci-runner@${var.project_id}.iam.gserviceaccount.com"
19
+ substitutions = {}
20
+ tags = []
21
+ depends_on = [google_project_service.ci]
22
+ approval_config {
23
+ approval_required = false
24
+ }
25
+ github {
26
+ enterprise_config_resource_name = null
27
+ name = var.github_repo
28
+ owner = var.github_owner
29
+ push {
30
+ branch = null
31
+ invert_regex = false
32
+ tag = "^v.*"
33
+ }
34
+ }
35
+ }