agentbridge-cli 0.1.11__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 (65) hide show
  1. agentbridge_cli-0.1.11/.codex/config.toml +1 -0
  2. agentbridge_cli-0.1.11/.codex/skills/build-release/SKILL.md +98 -0
  3. agentbridge_cli-0.1.11/.codex/skills/build-release/agents/openai.yaml +4 -0
  4. agentbridge_cli-0.1.11/.codex/skills/build-release/scripts/release_build.py +403 -0
  5. agentbridge_cli-0.1.11/.env.example +20 -0
  6. agentbridge_cli-0.1.11/.github/dependabot.yml +23 -0
  7. agentbridge_cli-0.1.11/.github/workflows/ci.yml +36 -0
  8. agentbridge_cli-0.1.11/.github/workflows/dependency-review.yml +51 -0
  9. agentbridge_cli-0.1.11/.github/workflows/release.yml +228 -0
  10. agentbridge_cli-0.1.11/.gitignore +57 -0
  11. agentbridge_cli-0.1.11/AGENTS.md +53 -0
  12. agentbridge_cli-0.1.11/CLAUDE.md +1 -0
  13. agentbridge_cli-0.1.11/LICENSE +21 -0
  14. agentbridge_cli-0.1.11/Makefile +5 -0
  15. agentbridge_cli-0.1.11/PKG-INFO +157 -0
  16. agentbridge_cli-0.1.11/README.md +127 -0
  17. agentbridge_cli-0.1.11/TODO.txt +1 -0
  18. agentbridge_cli-0.1.11/agentbridge/__init__.py +8 -0
  19. agentbridge_cli-0.1.11/agentbridge/_build_info.py +1 -0
  20. agentbridge_cli-0.1.11/agentbridge/config.py +85 -0
  21. agentbridge_cli-0.1.11/agentbridge/dashboard.py +494 -0
  22. agentbridge_cli-0.1.11/agentbridge/models.py +334 -0
  23. agentbridge_cli-0.1.11/agentbridge/pool.py +338 -0
  24. agentbridge_cli-0.1.11/agentbridge/server.py +2881 -0
  25. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/base.html +160 -0
  26. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/chat.html +1361 -0
  27. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/detail.html +142 -0
  28. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/page.html +900 -0
  29. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/pool.html +9 -0
  30. agentbridge_cli-0.1.11/agentbridge/templates/dashboard/requests.html +67 -0
  31. agentbridge_cli-0.1.11/architecture.png +0 -0
  32. agentbridge_cli-0.1.11/docs/plans/2026-02-14-dashboard-design.md +104 -0
  33. agentbridge_cli-0.1.11/docs/plans/2026-02-14-dashboard-implementation.md +1036 -0
  34. agentbridge_cli-0.1.11/hatch_build.py +24 -0
  35. agentbridge_cli-0.1.11/image-assets/icon/icon-1024.png +0 -0
  36. agentbridge_cli-0.1.11/image-assets/logo/logo-1024.png +0 -0
  37. agentbridge_cli-0.1.11/image-assets/manifest.json +77 -0
  38. agentbridge_cli-0.1.11/image-assets/sources/icon-source-keyed.png +0 -0
  39. agentbridge_cli-0.1.11/image-assets/sources/icon-source.png +0 -0
  40. agentbridge_cli-0.1.11/image-assets/sources/logo-source-keyed.png +0 -0
  41. agentbridge_cli-0.1.11/image-assets/sources/logo-source.png +0 -0
  42. agentbridge_cli-0.1.11/image-assets/sources/social-background-source.png +0 -0
  43. agentbridge_cli-0.1.11/image-assets/sources/social-source.png +0 -0
  44. agentbridge_cli-0.1.11/image-assets/web-seo/android-chrome-192.png +0 -0
  45. agentbridge_cli-0.1.11/image-assets/web-seo/android-chrome-512.png +0 -0
  46. agentbridge_cli-0.1.11/image-assets/web-seo/apple-touch-icon.png +0 -0
  47. agentbridge_cli-0.1.11/image-assets/web-seo/favicon/favicon-16.png +0 -0
  48. agentbridge_cli-0.1.11/image-assets/web-seo/favicon/favicon-32.png +0 -0
  49. agentbridge_cli-0.1.11/image-assets/web-seo/favicon/favicon-48.png +0 -0
  50. agentbridge_cli-0.1.11/image-assets/web-seo/favicon/favicon.ico +0 -0
  51. agentbridge_cli-0.1.11/image-assets/web-seo/og-image-1200x630.png +0 -0
  52. agentbridge_cli-0.1.11/image-assets/web-seo/site.webmanifest +21 -0
  53. agentbridge_cli-0.1.11/logo.png +0 -0
  54. agentbridge_cli-0.1.11/pyproject.toml +66 -0
  55. agentbridge_cli-0.1.11/scripts/dashboard_load_test.py +188 -0
  56. agentbridge_cli-0.1.11/tests/fixtures/ocr_test_document.png +0 -0
  57. agentbridge_cli-0.1.11/tests/test_config.py +41 -0
  58. agentbridge_cli-0.1.11/tests/test_dashboard.py +598 -0
  59. agentbridge_cli-0.1.11/tests/test_image_utils.py +471 -0
  60. agentbridge_cli-0.1.11/tests/test_model_mapping.py +303 -0
  61. agentbridge_cli-0.1.11/tests/test_models.py +584 -0
  62. agentbridge_cli-0.1.11/tests/test_pool.py +601 -0
  63. agentbridge_cli-0.1.11/tests/test_server.py +1101 -0
  64. agentbridge_cli-0.1.11/tests/test_session_logger.py +328 -0
  65. agentbridge_cli-0.1.11/uv.lock +1151 -0
@@ -0,0 +1 @@
1
+ sandbox_mode = "workspace-write"
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: build-release
3
+ description: Build, publish, and verify AgentBridge releases. Use when the user invokes /build-release or asks to cut, launch, tag, publish, monitor, or verify an agentbridge-cli PyPI or GitHub release.
4
+ ---
5
+
6
+ # Build Release
7
+
8
+ Use the repository-owned GitHub Actions workflow and PyPI Trusted Publishing.
9
+ Do not create a local tag or upload with local credentials: pushing a new
10
+ `pyproject.toml` version to `main` triggers `.github/workflows/release.yml`,
11
+ which tests, scans, builds, creates `v<version>`, creates the GitHub Release,
12
+ and publishes `agentbridge-cli`.
13
+
14
+ Use the next unused patch version unless the user requests another valid,
15
+ unused semantic version. Treat an untagged version absent from PyPI as pending.
16
+
17
+ ## Flow
18
+
19
+ 1. Fetch release state and require a clean, synchronized `main` worktree:
20
+
21
+ ```bash
22
+ git fetch origin main --tags
23
+ git status --short --branch
24
+ git log --oneline origin/main..HEAD
25
+ git log --oneline HEAD..origin/main
26
+ ```
27
+
28
+ Stop on unrelated changes, unpublished commits, divergence, or another branch.
29
+ Do not clean, pull, commit unrelated files, or switch branches.
30
+
31
+ 2. Select the release version from the repository root:
32
+
33
+ ```bash
34
+ python3 .codex/skills/build-release/scripts/release_build.py next-version
35
+ ```
36
+
37
+ Update only `[project].version` in `pyproject.toml` with `apply_patch`, then
38
+ refresh the lockfile mechanically:
39
+
40
+ ```bash
41
+ uv lock
42
+ ```
43
+
44
+ 3. Run the deterministic release preflight with the exact version:
45
+
46
+ ```bash
47
+ python3 .codex/skills/build-release/scripts/release_build.py preflight --version <version>
48
+ ```
49
+
50
+ The preflight requires consistent project and lock metadata, an unused PyPI
51
+ version and tag, clean patch formatting, a frozen lock, passing tests on Python
52
+ 3.12 and 3.13, Ruff, a successful wheel and sdist build, artifact audits, and a
53
+ wheel installation smoke test. Stop at the exact failed gate.
54
+
55
+ 4. Review and publish the release commit:
56
+
57
+ ```bash
58
+ git diff --check
59
+ git diff -- pyproject.toml uv.lock
60
+ git status --short
61
+ git add pyproject.toml uv.lock <other-intended-release-files>
62
+ git commit -m "Release <version>"
63
+ release_sha="$(git rev-parse HEAD)"
64
+ git push origin HEAD:main
65
+ ```
66
+
67
+ Never stage unrelated user changes. Do not create the tag locally; the release
68
+ workflow owns it.
69
+
70
+ 5. Find and monitor the release workflow for the pushed commit:
71
+
72
+ ```bash
73
+ gh run list --workflow release.yml --commit "$release_sha" --limit 5 \
74
+ --json databaseId,status,conclusion,event,headSha,url
75
+ gh run watch <run-id> --exit-status
76
+ ```
77
+
78
+ If the commit-filtered result has not appeared, poll briefly. Select the
79
+ `push` run for the exact SHA. A manual dispatch publishes only when its
80
+ `publish` input is explicitly true.
81
+
82
+ 6. After the workflow succeeds, verify exact-version files on PyPI and inspect
83
+ the GitHub Release:
84
+
85
+ ```bash
86
+ python3 .codex/skills/build-release/scripts/release_build.py wait-pypi --version <version>
87
+ gh release view v<version> --json url,tagName,assets
88
+ ```
89
+
90
+ Do not report success until PyPI returns files for the exact version. If the
91
+ workflow fails, inspect `gh run view <run-id> --log-failed` and report the
92
+ failed gate before considering recovery.
93
+
94
+ ## Final Response
95
+
96
+ Lead with the exact PyPI version URL. Report the tag, release workflow URL and
97
+ conclusion, GitHub Release URL, pushed commit, and every distribution filename.
98
+ On failure, report the exact command or job and the next safe recovery action.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Build Release"
3
+ short_description: "Build and publish AgentBridge releases"
4
+ default_prompt: "Use $build-release to validate, publish, and verify an AgentBridge release."
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env python3
2
+ """Select, build, audit, and verify agentbridge-cli releases."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import email.parser
8
+ import hashlib
9
+ import json
10
+ import re
11
+ import subprocess
12
+ import sys
13
+ import tarfile
14
+ import tempfile
15
+ import time
16
+ import tomllib
17
+ import urllib.error
18
+ import urllib.request
19
+ import zipfile
20
+ from pathlib import Path
21
+
22
+ REPO_ROOT = Path(__file__).resolve().parents[4]
23
+ PACKAGE_NAME = "agentbridge-cli"
24
+ IMPORT_NAME = "agentbridge"
25
+ VERSION_PATTERN = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$")
26
+ TEST_PYTHONS = ("3.12", "3.13")
27
+
28
+
29
+ def run(args: list[str], *, capture: bool = False) -> str:
30
+ print("+", " ".join(args), flush=True)
31
+ result = subprocess.run(
32
+ args,
33
+ cwd=REPO_ROOT,
34
+ check=True,
35
+ text=True,
36
+ stdout=subprocess.PIPE if capture else None,
37
+ )
38
+ return result.stdout.strip() if capture else ""
39
+
40
+
41
+ def read_toml(path: Path) -> dict[str, object]:
42
+ with path.open("rb") as stream:
43
+ return tomllib.load(stream)
44
+
45
+
46
+ def parse_version(version: str) -> tuple[int, int, int]:
47
+ match = VERSION_PATTERN.fullmatch(version)
48
+ if match is None:
49
+ raise SystemExit(f"release version must be MAJOR.MINOR.PATCH: {version!r}")
50
+ return tuple(int(part) for part in match.groups()) # type: ignore[return-value]
51
+
52
+
53
+ def project_metadata() -> dict[str, object]:
54
+ project = read_toml(REPO_ROOT / "pyproject.toml").get("project")
55
+ if not isinstance(project, dict):
56
+ raise SystemExit("pyproject.toml is missing [project]")
57
+ return project
58
+
59
+
60
+ def project_version() -> str:
61
+ version = project_metadata().get("version")
62
+ if not isinstance(version, str):
63
+ raise SystemExit("project.version must be a string")
64
+ parse_version(version)
65
+ return version
66
+
67
+
68
+ def lock_version() -> str:
69
+ packages = read_toml(REPO_ROOT / "uv.lock").get("package", [])
70
+ if not isinstance(packages, list):
71
+ raise SystemExit("uv.lock has an invalid package list")
72
+ for package in packages:
73
+ if isinstance(package, dict) and package.get("name") == PACKAGE_NAME:
74
+ version = package.get("version")
75
+ if isinstance(version, str):
76
+ return version
77
+ raise SystemExit(f"uv.lock is missing {PACKAGE_NAME!r}")
78
+
79
+
80
+ def fetch_pypi() -> dict[str, object]:
81
+ url = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
82
+ try:
83
+ with urllib.request.urlopen(url, timeout=20) as response:
84
+ data = json.load(response)
85
+ except urllib.error.HTTPError as exc:
86
+ if exc.code == 404:
87
+ return {}
88
+ raise
89
+ if not isinstance(data, dict):
90
+ raise SystemExit("unexpected PyPI JSON response")
91
+ return data
92
+
93
+
94
+ def pypi_files(version: str) -> list[dict[str, object]]:
95
+ releases = fetch_pypi().get("releases", {})
96
+ if not isinstance(releases, dict):
97
+ raise SystemExit("unexpected PyPI releases payload")
98
+ files = releases.get(version, [])
99
+ if not isinstance(files, list):
100
+ raise SystemExit(f"unexpected PyPI file payload for {version}")
101
+ return [item for item in files if isinstance(item, dict)]
102
+
103
+
104
+ def next_version(_: argparse.Namespace) -> None:
105
+ candidate = project_version()
106
+ releases = fetch_pypi().get("releases", {})
107
+ if not isinstance(releases, dict):
108
+ raise SystemExit("unexpected PyPI releases payload")
109
+ while releases.get(candidate):
110
+ major, minor, patch = parse_version(candidate)
111
+ candidate = f"{major}.{minor}.{patch + 1}"
112
+ print(candidate)
113
+
114
+
115
+ def tag_exists(version: str) -> bool:
116
+ tag = f"v{version}"
117
+ local = subprocess.run(
118
+ ["git", "rev-parse", "-q", "--verify", f"refs/tags/{tag}"],
119
+ cwd=REPO_ROOT,
120
+ stdout=subprocess.DEVNULL,
121
+ stderr=subprocess.DEVNULL,
122
+ )
123
+ remote = subprocess.run(
124
+ ["git", "ls-remote", "--exit-code", "--tags", "origin", f"refs/tags/{tag}"],
125
+ cwd=REPO_ROOT,
126
+ stdout=subprocess.DEVNULL,
127
+ stderr=subprocess.DEVNULL,
128
+ )
129
+ return local.returncode == 0 or remote.returncode == 0
130
+
131
+
132
+ def check_version(version: str) -> None:
133
+ parse_version(version)
134
+ project = project_metadata()
135
+ actual = {
136
+ "project.name": project.get("name"),
137
+ "project.version": project.get("version"),
138
+ "project.requires-python": project.get("requires-python"),
139
+ "uv.lock version": lock_version(),
140
+ }
141
+ expected = {
142
+ "project.name": PACKAGE_NAME,
143
+ "project.version": version,
144
+ "project.requires-python": ">=3.12",
145
+ "uv.lock version": version,
146
+ }
147
+ mismatches = {
148
+ key: value for key, value in actual.items() if value != expected[key]
149
+ }
150
+ if mismatches:
151
+ details = ", ".join(
152
+ f"{key}={value!r}, expected {expected[key]!r}"
153
+ for key, value in mismatches.items()
154
+ )
155
+ raise SystemExit(f"release metadata mismatch: {details}")
156
+ if pypi_files(version):
157
+ raise SystemExit(f"{PACKAGE_NAME}=={version} already exists on PyPI")
158
+ if tag_exists(version):
159
+ raise SystemExit(f"v{version} already exists locally or on origin")
160
+ print(json.dumps(actual, indent=2))
161
+
162
+
163
+ def sha256(path: Path) -> str:
164
+ digest = hashlib.sha256()
165
+ with path.open("rb") as stream:
166
+ for block in iter(lambda: stream.read(1024 * 1024), b""):
167
+ digest.update(block)
168
+ return digest.hexdigest()
169
+
170
+
171
+ def is_private_environment_file(name: str) -> bool:
172
+ basename = Path(name).name
173
+ return basename == ".env" or (
174
+ basename.startswith(".env.") and not basename.endswith(".example")
175
+ )
176
+
177
+
178
+ def audit_wheel(wheel: Path, version: str) -> dict[str, object]:
179
+ with zipfile.ZipFile(wheel) as archive:
180
+ names = archive.namelist()
181
+ metadata_name = next(
182
+ (name for name in names if name.endswith(".dist-info/METADATA")),
183
+ None,
184
+ )
185
+ entry_points_name = next(
186
+ (name for name in names if name.endswith(".dist-info/entry_points.txt")),
187
+ None,
188
+ )
189
+ metadata = (
190
+ email.parser.Parser().parsestr(archive.read(metadata_name).decode())
191
+ if metadata_name is not None
192
+ else None
193
+ )
194
+ entry_points = (
195
+ archive.read(entry_points_name).decode()
196
+ if entry_points_name is not None
197
+ else ""
198
+ )
199
+ required = {
200
+ f"{IMPORT_NAME}/__init__.py",
201
+ f"{IMPORT_NAME}/server.py",
202
+ f"{IMPORT_NAME}/dashboard.py",
203
+ f"{IMPORT_NAME}/models.py",
204
+ f"{IMPORT_NAME}/pool.py",
205
+ f"{IMPORT_NAME}/config.py",
206
+ f"{IMPORT_NAME}/_build_info.py",
207
+ f"{IMPORT_NAME}/templates/dashboard/base.html",
208
+ f"{IMPORT_NAME}/templates/dashboard/chat.html",
209
+ f"{IMPORT_NAME}/templates/dashboard/detail.html",
210
+ f"{IMPORT_NAME}/templates/dashboard/page.html",
211
+ f"{IMPORT_NAME}/templates/dashboard/pool.html",
212
+ f"{IMPORT_NAME}/templates/dashboard/requests.html",
213
+ }
214
+ checks = {
215
+ "filename_version": version in wheel.name,
216
+ "universal_wheel": wheel.name.endswith("-py3-none-any.whl"),
217
+ "metadata_name": metadata is not None and metadata.get("Name") == PACKAGE_NAME,
218
+ "metadata_version": metadata is not None and metadata.get("Version") == version,
219
+ "requires_python": metadata is not None and metadata.get("Requires-Python") == ">=3.12",
220
+ "console_script": "agentbridge = agentbridge.server:main" in entry_points,
221
+ "required_package_files": required.issubset(names),
222
+ "has_license": any(name.endswith(".dist-info/licenses/LICENSE") for name in names),
223
+ "no_cache_files": not any(
224
+ "__pycache__" in Path(name).parts or name.endswith(".pyc")
225
+ for name in names
226
+ ),
227
+ "no_private_environment_files": not any(
228
+ is_private_environment_file(name) for name in names
229
+ ),
230
+ }
231
+ failed = [name for name, passed in checks.items() if not passed]
232
+ result = {"file": wheel.name, "sha256": sha256(wheel), "checks": checks}
233
+ if failed:
234
+ print(json.dumps(result, indent=2), file=sys.stderr)
235
+ raise SystemExit(f"wheel audit failed: {failed}")
236
+ return result
237
+
238
+
239
+ def audit_sdist(sdist: Path, version: str) -> dict[str, object]:
240
+ with tarfile.open(sdist, "r:gz") as archive:
241
+ members = archive.getmembers()
242
+ names = [member.name for member in members]
243
+ pyproject_member = next(
244
+ (member for member in members if member.name.endswith("/pyproject.toml")),
245
+ None,
246
+ )
247
+ if pyproject_member is None:
248
+ raise SystemExit("sdist is missing pyproject.toml")
249
+ stream = archive.extractfile(pyproject_member)
250
+ if stream is None:
251
+ raise SystemExit("could not read pyproject.toml from sdist")
252
+ built_project = tomllib.loads(stream.read().decode()).get("project", {})
253
+ forbidden_parts = {".git", ".venv", "__pycache__", "dist", "logs"}
254
+ checks = {
255
+ "filename_version": version in sdist.name,
256
+ "metadata_name": (
257
+ isinstance(built_project, dict)
258
+ and built_project.get("name") == PACKAGE_NAME
259
+ ),
260
+ "metadata_version": (
261
+ isinstance(built_project, dict)
262
+ and built_project.get("version") == version
263
+ ),
264
+ "has_license": any(name.endswith("/LICENSE") for name in names),
265
+ "has_readme": any(name.endswith("/README.md") for name in names),
266
+ "has_build_hook": any(name.endswith("/hatch_build.py") for name in names),
267
+ "has_package": any(name.endswith(f"/{IMPORT_NAME}/server.py") for name in names),
268
+ "no_build_outputs": not any(
269
+ forbidden_parts.intersection(Path(name).parts) for name in names
270
+ ),
271
+ "no_private_environment_files": not any(
272
+ is_private_environment_file(name) for name in names
273
+ ),
274
+ }
275
+ failed = [name for name, passed in checks.items() if not passed]
276
+ result = {"file": sdist.name, "sha256": sha256(sdist), "checks": checks}
277
+ if failed:
278
+ print(json.dumps(result, indent=2), file=sys.stderr)
279
+ raise SystemExit(f"sdist audit failed: {failed}")
280
+ return result
281
+
282
+
283
+ def smoke_wheel(wheel: Path, version: str) -> None:
284
+ python = run(["uv", "python", "find", "3.12"], capture=True)
285
+ with tempfile.TemporaryDirectory(prefix="agentbridge-smoke-") as directory:
286
+ environment = Path(directory) / "venv"
287
+ run(["uv", "venv", "--python", python, str(environment)])
288
+ installed_python = environment / "bin" / "python"
289
+ run(
290
+ [
291
+ "uv",
292
+ "pip",
293
+ "install",
294
+ "--python",
295
+ str(installed_python),
296
+ "--no-deps",
297
+ str(wheel),
298
+ ]
299
+ )
300
+ code = (
301
+ "from importlib.metadata import version; "
302
+ "import agentbridge; "
303
+ f"assert version('{PACKAGE_NAME}') == '{version}'; "
304
+ f"assert agentbridge.__version__ == '{version}'; "
305
+ "print(agentbridge.__version__)"
306
+ )
307
+ run([str(installed_python), "-c", code])
308
+
309
+
310
+ def preflight(args: argparse.Namespace) -> None:
311
+ version = args.version
312
+ check_version(version)
313
+ run(["git", "diff", "--check"])
314
+ run(["uv", "lock", "--check"])
315
+ for python in TEST_PYTHONS:
316
+ run(
317
+ [
318
+ "uv",
319
+ "run",
320
+ "--frozen",
321
+ "--extra",
322
+ "test",
323
+ "--python",
324
+ python,
325
+ "pytest",
326
+ "-q",
327
+ ]
328
+ )
329
+ run(
330
+ [
331
+ "uv",
332
+ "run",
333
+ "--frozen",
334
+ "--extra",
335
+ "test",
336
+ "--python",
337
+ TEST_PYTHONS[0],
338
+ "ruff",
339
+ "check",
340
+ "agentbridge",
341
+ "tests",
342
+ ".codex/skills/build-release/scripts/release_build.py",
343
+ ]
344
+ )
345
+ with tempfile.TemporaryDirectory(prefix="agentbridge-release-") as directory:
346
+ output = Path(directory) / "dist"
347
+ run(["uv", "build", "--out-dir", str(output)])
348
+ wheels = sorted(output.glob("*.whl"))
349
+ sdists = sorted(output.glob("*.tar.gz"))
350
+ artifacts = [path for path in output.iterdir() if path.name != ".gitignore"]
351
+ if len(wheels) != 1 or len(sdists) != 1 or len(artifacts) != 2:
352
+ raise SystemExit(
353
+ f"expected one wheel and one sdist, found {sorted(path.name for path in artifacts)}"
354
+ )
355
+ results = [audit_wheel(wheels[0], version), audit_sdist(sdists[0], version)]
356
+ smoke_wheel(wheels[0], version)
357
+ print(
358
+ json.dumps(
359
+ {"package": PACKAGE_NAME, "version": version, "artifacts": results},
360
+ indent=2,
361
+ )
362
+ )
363
+
364
+
365
+ def wait_pypi(args: argparse.Namespace) -> None:
366
+ parse_version(args.version)
367
+ for attempt in range(1, args.attempts + 1):
368
+ files = pypi_files(args.version)
369
+ if files:
370
+ print(f"https://pypi.org/project/{PACKAGE_NAME}/{args.version}/")
371
+ for item in files:
372
+ filename = item.get("filename")
373
+ if isinstance(filename, str):
374
+ print(filename)
375
+ return
376
+ print(f"waiting for {PACKAGE_NAME} {args.version} ({attempt}/{args.attempts})", flush=True)
377
+ time.sleep(args.interval)
378
+ raise SystemExit(f"{PACKAGE_NAME} {args.version} did not appear on PyPI")
379
+
380
+
381
+ def main() -> None:
382
+ parser = argparse.ArgumentParser(description=__doc__)
383
+ commands = parser.add_subparsers(dest="command", required=True)
384
+
385
+ next_parser = commands.add_parser("next-version")
386
+ next_parser.set_defaults(func=next_version)
387
+
388
+ preflight_parser = commands.add_parser("preflight")
389
+ preflight_parser.add_argument("--version", required=True)
390
+ preflight_parser.set_defaults(func=preflight)
391
+
392
+ wait_parser = commands.add_parser("wait-pypi")
393
+ wait_parser.add_argument("--version", required=True)
394
+ wait_parser.add_argument("--attempts", type=int, default=60)
395
+ wait_parser.add_argument("--interval", type=float, default=10)
396
+ wait_parser.set_defaults(func=wait_pypi)
397
+
398
+ args = parser.parse_args()
399
+ args.func(args)
400
+
401
+
402
+ if __name__ == "__main__":
403
+ main()
@@ -0,0 +1,20 @@
1
+ # Optional: Set custom port
2
+ # PORT=8082
3
+
4
+ # Optional: Set number of pooled clients
5
+ # POOL_SIZE=3
6
+
7
+ # Optional: Set request timeout in seconds
8
+ # CLAUDE_TIMEOUT=120
9
+
10
+ # Optional: Set Codex request timeout in seconds
11
+ # CODEX_TIMEOUT=600
12
+
13
+ # Optional: Native Codex image generation timeout and raster bounds
14
+ # CODEX_IMAGE_TIMEOUT=600
15
+ # MAX_IMAGE_INPUT_BYTES=67108864
16
+ # MAX_IMAGE_OUTPUT_BYTES=33554432
17
+ # MAX_IMAGE_PIXELS=40000000
18
+
19
+ # Optional: OpenRouter requests can read this from ~/.config/agentbridge/.env
20
+ # OPENROUTER_API_KEY=sk-or-...
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: uv
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ time: "08:00"
9
+ timezone: Europe/Lisbon
10
+ cooldown:
11
+ default-days: 7
12
+ open-pull-requests-limit: 5
13
+
14
+ - package-ecosystem: github-actions
15
+ directory: /
16
+ schedule:
17
+ interval: weekly
18
+ day: monday
19
+ time: "08:00"
20
+ timezone: Europe/Lisbon
21
+ cooldown:
22
+ default-days: 7
23
+ open-pull-requests-limit: 5
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ python-version: ["3.12", "3.13"]
18
+ steps:
19
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
20
+ with:
21
+ persist-credentials: false
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
25
+ with:
26
+ enable-cache: true
27
+ cache-dependency-glob: "uv.lock"
28
+
29
+ - name: Set up Python ${{ matrix.python-version }}
30
+ run: uv python install ${{ matrix.python-version }}
31
+
32
+ - name: Install dependencies
33
+ run: uv sync --all-extras --python ${{ matrix.python-version }}
34
+
35
+ - name: Run tests
36
+ run: uv run --python ${{ matrix.python-version }} pytest tests/ -v --tb=short
@@ -0,0 +1,51 @@
1
+ name: Dependency review
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+ merge_group:
11
+ types:
12
+ - checks_requested
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ dependency-review:
19
+ name: Dependency review
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout repository
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+
25
+ - name: Dependency review (pull request)
26
+ if: ${{ github.event_name == 'pull_request' }}
27
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
28
+ with:
29
+ fail-on-severity: high
30
+ fail-on-scopes: runtime, development, unknown
31
+ retry-on-snapshot-warnings: true
32
+
33
+ - name: Dependency review (push audit)
34
+ if: ${{ github.event_name == 'push' }}
35
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
36
+ with:
37
+ fail-on-severity: high
38
+ fail-on-scopes: runtime, development, unknown
39
+ retry-on-snapshot-warnings: true
40
+ base-ref: ${{ github.event.before }}
41
+ head-ref: ${{ github.sha }}
42
+
43
+ - name: Dependency review (merge group)
44
+ if: ${{ github.event_name == 'merge_group' }}
45
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
46
+ with:
47
+ fail-on-severity: high
48
+ fail-on-scopes: runtime, development, unknown
49
+ retry-on-snapshot-warnings: true
50
+ base-ref: ${{ github.event.merge_group.base_sha }}
51
+ head-ref: ${{ github.event.merge_group.head_sha }}