diff --git a/CLAUDE.md b/CLAUDE.md index 2aac70468..eeac1aa4c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,7 @@ Only after doing all of the above should you begin writing code. # Important commands and conventions: - Never run `uv sync`, always run `uv sync --all-packages` instead -- For browser automation, Playwright's Python API is in the root venv (`from playwright.sync_api import sync_playwright`, run via `uv run python`). Chromium installs asynchronously on first container boot (the one-shot `deferred-install` program), so in a fresh workspace confirm it finished -- `supervisorctl status deferred-install` or the marker `/var/lib/minds/deferred-install/done.playwright` -- before launching, or the launch fails with a clear error. It runs as-is under the docker provider's gVisor runtime; if you hit a "No usable sandbox!" error on a runtime without unprivileged user namespaces, pass `chromium.launch(args=["--no-sandbox"])`. See `libs/bootstrap/README.md` for the full deferral contract. +- For browser automation, Playwright's Python API is in the root venv (`from playwright.sync_api import sync_playwright`, run via `uv run python`). The engine is CloakBrowser (a stealth-patched Chromium fork), not Playwright's own managed Chromium -- pass `executable_path="/opt/cloakbrowser/chrome"` explicitly to `chromium.launch(...)`, since Playwright's own browser-cache lookup only auto-discovers builds it downloaded itself. It installs asynchronously on first container boot (the one-shot `deferred-install` program), so in a fresh workspace confirm it finished -- `supervisorctl status deferred-install` or the marker `/var/lib/minds/deferred-install/done.cloakbrowser` -- before launching, or the launch fails with a clear error. It runs as-is under the docker provider's gVisor runtime; if you hit a "No usable sandbox!" error on a runtime without unprivileged user namespaces, pass `chromium.launch(executable_path="/opt/cloakbrowser/chrome", args=["--no-sandbox"])`. See `libs/bootstrap/README.md` for the full deferral contract. # Always remember these guidelines: diff --git a/libs/bootstrap/README.md b/libs/bootstrap/README.md index c1889b537..c07f05e65 100644 --- a/libs/bootstrap/README.md +++ b/libs/bootstrap/README.md @@ -53,8 +53,16 @@ The `deferred-install` program in `supervisord.conf` runs bake into the Docker image but aren't required by any boot-time service. Currently it covers: -- Playwright's Chromium browser + its apt system libraries - (`uv run playwright install --with-deps chromium`). +- Chromium's apt system libraries (`uv run playwright install-deps chromium` + -- libs only, no browser download). +- CloakBrowser, a from-source C++ (Blink/V8) stealth-patched Chromium fork, + fetched from a pinned GitHub release + SHA256-verified per-arch (Linux + x64/arm64), unpacked to `/opt/cloakbrowser/`. This is the one Chromium + binary in the image -- both the agentic browser fleet and any agent's own + direct Playwright calls (`chromium.launch(executable_path=...)`) use it; + Playwright's own managed-Chromium download is not installed. See + `libs/browser/CHROMIUM_ENGINE_DESCRIPTION_AND_FUTURE_GUIDELINES.md` for the + full swap-engine/upgrade-version contract. (The publish-inspiration scan gate's two secret-scanner binaries -- `betterleaks`, `kingfisher` -- are NOT deferred: they are baked into the diff --git a/libs/browser/CHROMIUM_ENGINE_DESCRIPTION_AND_FUTURE_GUIDELINES.md b/libs/browser/CHROMIUM_ENGINE_DESCRIPTION_AND_FUTURE_GUIDELINES.md new file mode 100644 index 000000000..c28a18f41 --- /dev/null +++ b/libs/browser/CHROMIUM_ENGINE_DESCRIPTION_AND_FUTURE_GUIDELINES.md @@ -0,0 +1,103 @@ +# Chromium engine: description and future guidelines + +## What this is, in three sentences + +The browser fleet drives [CloakHQ/CloakBrowser](https://github.com/CloakHQ/CloakBrowser) +(a from-source C++/Blink/V8 stealth-patched Chromium fork), pulled from a +pinned GitHub release under their free "delayed release" tier -- chosen over +`tiliondev/fortress` specifically because Fortress ships no Linux arm64 build +(would break the desktop/Lima path on Apple Silicon), while CloakBrowser does. +The binary lands at `/opt/cloakbrowser/chrome`, fetched and SHA256-verified by +`scripts/deferred_install.sh`'s `_install_cloakbrowser` on first container +boot, and pre-baked into the box image ahead of time on cloud slices (see +`slice_provider.py::_build_cloakbrowser_derived_image` in `mngr`). Exactly +which named things changed to point at it -- and, just as important, which +same-named things belong to a *different* library and did **not** change -- +is spelled out explicitly below; don't guess from the diff. + +## Exactly which variables changed -- and whose they are + +Two different libraries are involved here, Playwright and browser-use, and +they are **not the same thing** even though one property and one keyword +argument happen to share the identical name `executable_path`. Mixing them up +is the easiest way to misread this code. Table, in the order they appear in +`libs/browser/src/browser/session.py`: + +| # | Name | File | Whose namespace | What happened | +|---|---|---|---|---| +| 1 | `_PLAYWRIGHT_MARKER` → `_CLOAKBROWSER_MARKER` | `session.py` | **Ours** -- a plain module-level constant we invented, not part of any library's API | Renamed. A `pathlib.Path` gating `deferred_install_ready()`. Value changed from `Path("/var/lib/minds/deferred-install/done.playwright")` to `Path("/var/lib/minds/deferred-install/done.cloakbrowser")`. | +| 2 | `playwright.chromium.executable_path` | `session.py` | **Playwright's own property**, on Playwright's `BrowserType` object (`playwright.chromium`) | **Deleted, no longer read at all.** This is Playwright's own API -- a read-only attribute that returns wherever Playwright's *own* browser-management system downloaded *its own* managed Chromium binary. It has nothing to do with browser-use. We used to read this to find a Chromium to launch; we don't anymore. | +| 3 | `chromium_path` | `session.py` | **Ours** -- an ordinary local Python variable inside `LiveBrowser.start()`, belongs to neither library | Still exists, but its source changed: it used to be assigned from reading #2 (Playwright's property); it's now assigned from #4 (our own constant). | +| 4 | `_CLOAKBROWSER_EXECUTABLE` | `session.py` | **Ours** -- new module-level constant | New. A plain string, `"/opt/cloakbrowser/chrome"`. This is what #3 is now set to. | +| 5 | `executable_path=` | `session.py` | **browser-use's own keyword argument**, on the `browser_use.BrowserSession(...)` constructor call inside `_build_bu_session` | **Still used, value changed.** This is browser-use's own API -- a constructor parameter that tells browser-use which literal binary file to launch and drive. This is the one that actually matters for what engine runs. It used to receive whatever #2 (Playwright's property) returned; it now receives #3/#4 (our CloakBrowser path). Despite sharing the string `"executable_path"` with #2, #2 and #5 are unrelated attributes on two different objects from two different libraries -- #2 is Playwright reporting its own state, #5 is browser-use accepting a value *we* hand it. | +| 6 | `_CLOAKBROWSER_VERSION`, `_CLOAKBROWSER_INSTALL_DIR`, `_CLOAKBROWSER_RELEASE_URL`, `_CLOAKBROWSER_SHA256_ARM64`, `_CLOAKBROWSER_SHA256_X64` | `scripts/deferred_install.sh` | **Ours** -- bash `readonly` variables | New. Control what gets fetched, verified, and where it's unpacked. | +| 7 | `_CLOAKBROWSER_VERSION`, `_CLOAKBROWSER_SHA256_X64`, `_CLOAKBROWSER_INSTALL_DIR`, `_CLOAKBROWSER_RELEASE_URL` | `mngr`'s `slice_provider.py` | **Ours** -- Python `Final[str]` constants | New. Independently-pinned mirror of #6 for the cloud box pre-bake (x64 only, no arm64 constant there -- cloud slices are x86_64 bare metal). Kept in sync with #6 **by hand**, not by shared code -- see the upgrade steps below. | + +The `playwright` Python **package** itself is still a dependency and still +installed (`pyproject.toml`, unchanged) -- it's still used for the CDP +*observer* connection (`playwright.chromium.connect_over_cdp`, protocol-level, +engine-agnostic) and by any agent's own direct scripted use +(`from playwright.sync_api import sync_playwright`). Only its *browser* +download (row 2, and the `deferred_install.sh` step that used to fetch it) is +gone. + +## Upgrading CloakBrowser to a new version + +1. Browse and pick a tag + that actually has downloadable `cloakbrowser-linux-x64.tar.gz` / + `-linux-arm64.tar.gz` assets attached -- not just `SHA256SUMS` / + `SHA256SUMS.sig`. Their newest major version is routinely Pro-gated (no + public binary at all); the release you want is the newest one *with* real + assets, which may be one or more majors behind their latest tag. +2. Download that release's `SHA256SUMS` file and copy the `x64`/`arm64` hash + lines out of it. +3. In `scripts/deferred_install.sh`, update the three constants together: + `_CLOAKBROWSER_VERSION` (the release tag), `_CLOAKBROWSER_SHA256_X64`, and + `_CLOAKBROWSER_SHA256_ARM64`. +4. In `mngr`'s `slice_provider.py`, update the matching + `_CLOAKBROWSER_VERSION` and `_CLOAKBROWSER_SHA256_X64` (no arm64 constant + there -- see row 7 above). These are a **separate, manually-duplicated + pin** -- forgetting this step means cloud slices keep baking the old + version while desktop/Lima gets the new one. +5. Nothing else needs to change. The binary always unpacks to the same + `/opt/cloakbrowser/chrome` path regardless of version -- verify this stays + true for the new release by actually downloading and `tar tzf`-listing it + (don't assume; CloakBrowser ships a flat archive with the binary literally + named `chrome`, but a future release could restructure that). +6. Re-run `libs/browser/browser_test.py` and, ideally, boot a real workspace + off the change and drive the fleet once before merging. + +## When Fortress ships Linux arm64 (or any other engine swap) + +Fortress (`tiliondev/fortress`) was the first candidate considered here and +was rejected on exactly one blocking fact: no Linux arm64 build, native or +Docker. Their own roadmap lists `linux/arm64 Docker image` as an unshipped +item. **When that changes, it's worth actively re-evaluating**, not just +noting the option -- Fortress's stealth claims (0% CreepJS headless/stealth, +a published gauntlet, monthly Chromium rebase) are stronger than CloakBrowser's +free tier, which trails their paid tier by roughly one to two Chromium majors +at any given time. Check and +their roadmap section for arm64 status. + +If/when a swap is worth doing, same two touch points as any engine swap, no +architecture change required: + +1. **Download and inspect the actual release tarball first.** Do not assume + the binary's name or directory layout from a README. (CloakBrowser turned + out to be a flat archive with the binary literally named `chrome`; + Fortress's own quick-start docs implied a `tilion-fortress/` wrapper + directory with a `tilion` binary inside -- these are not the same shape, + and guessing wrong here fails silently until the fleet tries to launch.) +2. In `scripts/deferred_install.sh`, replace the CloakBrowser-specific bits in + (or rename) `_install_cloakbrowser`: the release URL, asset name(s), and + SHA256 pin(s) -- rows 6 above. +3. In `session.py`, update `_CLOAKBROWSER_EXECUTABLE` (row 4) to the new + binary's real path. +4. Mirror both of the above in `mngr`'s `slice_provider.py` + (`_build_cloakbrowser_derived_image`) for the cloud pre-bake -- row 7. + +That's the entire contract. Any Chromium-family fork that accepts standard +launch flags (`--headless`, `--user-data-dir`, `--remote-debugging-port`, +`--no-sandbox`) and speaks CDP satisfies it -- browser-use's +`BrowserSession(executable_path=...)` (row 5) and the CDP observer connection +don't know or care which engine is behind that path. diff --git a/libs/browser/README.md b/libs/browser/README.md index ad9153548..a851950f1 100644 --- a/libs/browser/README.md +++ b/libs/browser/README.md @@ -8,12 +8,15 @@ agent, identified by its `MNGR_AGENT_ID`, or the human). thread-per-connection) that owns every browser. browser_use, Playwright (async), and the per-browser ownership state machine run on one background asyncio event loop, reached from the Flask threads through a single `run_coroutine_threadsafe` - bridge. Each browser is a headless Chromium driven by `browser_use.BrowserSession`, observed - over the same CDP endpoint to stream a live view (`Page.startScreencast` -> - base64 JPEG frames over a WebSocket) and inject human input. Each browser is - addressed by a random ~2-word english NAME (e.g. `alex-smith`), generated on - demand and never reused; the fleet starts empty and there is no default - browser. + bridge. Each browser is a headless CloakBrowser -- a from-source C++ + (Blink/V8) stealth-patched Chromium fork at `/opt/cloakbrowser/chrome` (see + `scripts/deferred_install.sh`), not Playwright's own managed Chromium -- + driven by `browser_use.BrowserSession` (`executable_path` pinned to that + fixed path), observed over the same CDP endpoint to stream a live view + (`Page.startScreencast` -> base64 JPEG frames over a WebSocket) and inject + human input. Each browser is addressed by a random ~2-word english NAME + (e.g. `alex-smith`), generated on demand and never reused; the fleet starts + empty and there is no default browser. - **Ownership** is one locked, compare-and-set state machine per browser. Agents never preempt each other -- a second agent waits in a FIFO queue (monitor-and-wait). The human can take control from the UI at any time, which diff --git a/libs/browser/browser_test.py b/libs/browser/browser_test.py index e61245af1..71431316a 100644 --- a/libs/browser/browser_test.py +++ b/libs/browser/browser_test.py @@ -76,11 +76,11 @@ def test_deferred_install_ready_gates_on_marker( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: monkeypatch.delenv("BROWSER_SKIP_INSTALL_CHECK", raising=False) - play = tmp_path / "done.playwright" - monkeypatch.setattr(bsession, "_PLAYWRIGHT_MARKER", play) + marker = tmp_path / "done.cloakbrowser" + monkeypatch.setattr(bsession, "_CLOAKBROWSER_MARKER", marker) ready, _ = bsession.deferred_install_ready() assert ready is False - play.write_text("") + marker.write_text("") ready, reason = bsession.deferred_install_ready() assert ready is True assert reason == "ready" diff --git a/libs/browser/src/browser/session.py b/libs/browser/src/browser/session.py index d0ee2d6b2..8dab3c142 100644 --- a/libs/browser/src/browser/session.py +++ b/libs/browser/src/browser/session.py @@ -105,10 +105,16 @@ # tab feels snappier. Slightly more bandwidth than skipping frames. _SCREENCAST_EVERY_NTH_FRAME = 1 -# Deferred-install marker (see scripts/deferred_install.sh). Chromium installs -# asynchronously on first container boot; launching a browser before it exists -# fails, so callers gate on this. No Xvfb: CDP streaming/input are headless. -_PLAYWRIGHT_MARKER = Path("/var/lib/minds/deferred-install/done.playwright") +# Deferred-install marker (see scripts/deferred_install.sh). CloakBrowser +# installs asynchronously on first container boot; launching a browser before +# it exists fails, so callers gate on this. No Xvfb: CDP streaming/input are +# headless. +_CLOAKBROWSER_MARKER = Path("/var/lib/minds/deferred-install/done.cloakbrowser") + +# CloakBrowser's fixed install path (see scripts/deferred_install.sh's +# _install_cloakbrowser). A stealth, C++-patched Chromium fork -- replaces +# vanilla Chromium as the engine for every browser the fleet launches. +_CLOAKBROWSER_EXECUTABLE = "/opt/cloakbrowser/chrome" # Default model. browser-use's own default LLM is ChatBrowserUse (its hosted # model), so to drive with the user's Anthropic key we pass ChatAnthropic @@ -329,7 +335,7 @@ def deferred_install_ready() -> tuple[bool, str]: """Return ``(ready, reason)`` once Chromium is installed.""" if os.environ.get("BROWSER_SKIP_INSTALL_CHECK") == "1": return True, "ready" # host/CI testing without the deferred-install marker - if not _PLAYWRIGHT_MARKER.exists(): + if not _CLOAKBROWSER_MARKER.exists(): return False, "Chromium is still installing in this workspace; try again in a minute." return True, "ready" @@ -542,7 +548,11 @@ async def start( """ self._playwright = playwright self._input_enabled.set() - chromium_path = playwright.chromium.executable_path + # Fixed CloakBrowser path, not playwright.chromium.executable_path -- + # the fleet's engine is CloakBrowser, not Playwright's own managed + # Chromium (which vanilla Playwright calls elsewhere in this image + # still use). See _CLOAKBROWSER_EXECUTABLE. + chromium_path = _CLOAKBROWSER_EXECUTABLE profile_dir = _profile_dir(self.browser_id) profile_dir.mkdir(parents=True, exist_ok=True) _clear_stale_singleton(profile_dir) # a prior hard kill may have orphaned a lock diff --git a/libs/browser/test_browser_integration.py b/libs/browser/test_browser_integration.py index 6c5b8746c..f20d2124b 100644 --- a/libs/browser/test_browser_integration.py +++ b/libs/browser/test_browser_integration.py @@ -415,7 +415,7 @@ def test_http_release_requires_ownership(monkeypatch: pytest.MonkeyPatch) -> Non def test_http_new_browser_blocked_until_chromium_installed(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("BROWSER_SKIP_INSTALL_CHECK", raising=False) - monkeypatch.setattr(bsession, "_PLAYWRIGHT_MARKER", bsession.Path("/nonexistent/marker")) + monkeypatch.setattr(bsession, "_CLOAKBROWSER_MARKER", bsession.Path("/nonexistent/marker")) client = runner.application.test_client() resp = client.post("/browsers") assert resp.status_code == 503 diff --git a/scripts/deferred_install.sh b/scripts/deferred_install.sh index c1e1bf8ec..e4b5a7050 100755 --- a/scripts/deferred_install.sh +++ b/scripts/deferred_install.sh @@ -66,35 +66,102 @@ _recover_interrupted_dpkg() { fi } -_install_playwright() { +_install_playwright_deps() { local marker - marker="$(_marker_for playwright)" + marker="$(_marker_for playwright_deps)" if [ -f "$marker" ]; then - _log "playwright: marker present at $marker, skipping" + _log "playwright_deps: marker present at $marker, skipping" return 0 fi - # `playwright install --with-deps` shells out to apt; recover any - # interrupted dpkg state first so an install the bake interrupted can - # actually complete on retry. + # `install-deps` only apt-installs the shared system libraries a Chromium + # build needs (libnss3, libgbm, etc.) -- it does not download a browser + # binary. Both engines below are Chromium builds and need these libs, so + # this runs once, ahead of either. Recover any interrupted dpkg state + # first so an install the bake interrupted can actually complete on retry. _recover_interrupted_dpkg - _log "playwright: installing chromium + apt system libs (this may take a few minutes)" - # `--with-deps` apt-installs the system libraries chromium needs. - # `uv run` uses the workspace venv (the playwright Python wheel is - # already installed via the root pyproject.toml's pin). Subshell so - # the cwd change does not leak to other `_install_` functions. - if (cd "$REPO_ROOT" && uv run playwright install --with-deps chromium); then + _log "playwright_deps: installing apt system libs (this may take a few minutes)" + if (cd "$REPO_ROOT" && uv run playwright install-deps chromium); then touch "$marker" - _log "playwright: install complete, marker written to $marker" + _log "playwright_deps: install complete, marker written to $marker" else - _log "playwright: install FAILED; marker not written so the next boot retries" + _log "playwright_deps: install FAILED; marker not written so the next boot retries" return 1 fi } +# CloakBrowser is a from-source C++ (Blink/V8) stealth patch of Chromium -- +# used everywhere a Chromium binary is launched in this image (the agentic +# browser fleet, and any agent's own direct Playwright calls), replacing +# vanilla Chromium entirely. See libs/browser/README.md. +# +# Pinned to a specific free-tier release (not `latest`): CloakBrowser's +# newest major version is gated behind a paid tier, so `latest` would 404 on +# the asset for an unpaid image. Bump deliberately by updating these three +# vars together (and the SHA256s below) -- never silently drift on restart, +# matching every other deferred package's contract. +readonly _CLOAKBROWSER_VERSION="chromium-v146.0.7680.177.4" +readonly _CLOAKBROWSER_INSTALL_DIR="/opt/cloakbrowser" +readonly _CLOAKBROWSER_RELEASE_URL="https://github.com/CloakHQ/CloakBrowser/releases/download/${_CLOAKBROWSER_VERSION}" +# From that release's SHA256SUMS; recompute + update on every version bump. +readonly _CLOAKBROWSER_SHA256_ARM64="8b71ce53b4fd131327331a31fba3835d71882d19bfaabde78dd0f5390bd16f45" +readonly _CLOAKBROWSER_SHA256_X64="5af027faafb1fef9933eb784c094b764706de22a372a2cee84bc117fc4ab537f" + +_cloakbrowser_asset_for_arch() { + # Maps `uname -m` to CloakBrowser's release asset naming + pinned hash. + case "$(uname -m)" in + aarch64|arm64) + printf 'cloakbrowser-linux-arm64.tar.gz %s\n' "$_CLOAKBROWSER_SHA256_ARM64" + ;; + x86_64|amd64) + printf 'cloakbrowser-linux-x64.tar.gz %s\n' "$_CLOAKBROWSER_SHA256_X64" + ;; + *) + _log "cloakbrowser: unsupported architecture $(uname -m)" + return 1 + ;; + esac +} + +_install_cloakbrowser() { + local marker + marker="$(_marker_for cloakbrowser)" + if [ -f "$marker" ]; then + _log "cloakbrowser: marker present at $marker, skipping" + return 0 + fi + local asset expected_sha256 + read -r asset expected_sha256 < <(_cloakbrowser_asset_for_arch) || return 1 + _log "cloakbrowser: downloading ${_CLOAKBROWSER_VERSION}/${asset}" + local tmp_dir + tmp_dir="$(mktemp -d)" + # shellcheck disable=SC2064 + trap "rm -rf '$tmp_dir'" RETURN + if ! curl -fsSL -o "$tmp_dir/$asset" "${_CLOAKBROWSER_RELEASE_URL}/${asset}"; then + _log "cloakbrowser: download FAILED; marker not written so the next boot retries" + return 1 + fi + local actual_sha256 + actual_sha256="$(sha256sum "$tmp_dir/$asset" | awk '{print $1}')" + if [ "$actual_sha256" != "$expected_sha256" ]; then + _log "cloakbrowser: SHA256 mismatch for $asset (expected $expected_sha256, got $actual_sha256) -- refusing to install" + return 1 + fi + rm -rf "$_CLOAKBROWSER_INSTALL_DIR" + mkdir -p "$_CLOAKBROWSER_INSTALL_DIR" + if ! tar xzf "$tmp_dir/$asset" -C "$_CLOAKBROWSER_INSTALL_DIR"; then + _log "cloakbrowser: extract FAILED; marker not written so the next boot retries" + return 1 + fi + chmod +x "$_CLOAKBROWSER_INSTALL_DIR/chrome" + touch "$marker" + _log "cloakbrowser: install complete (${_CLOAKBROWSER_INSTALL_DIR}/chrome), marker written to $marker" +} + main() { mkdir -p "$MARKER_DIR" local rc=0 - _install_playwright || rc=$? + _install_playwright_deps || rc=$? + _install_cloakbrowser || rc=$? if [ "$rc" -eq 0 ]; then _log "all deferred installs complete" else diff --git a/uv.lock b/uv.lock index 6459ec71b..4795c4fdf 100644 --- a/uv.lock +++ b/uv.lock @@ -221,6 +221,49 @@ requires-dist = [ { name = "inotify-simple", specifier = ">=2.0.0" }, ] +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + [[package]] name = "asttokens" version = "3.0.1" @@ -1704,9 +1747,11 @@ name = "imbue-common" version = "0.1.20" source = { editable = "vendor/mngr/libs/imbue_common" } dependencies = [ + { name = "argon2-cffi" }, { name = "boto3" }, { name = "click" }, { name = "cowsay-python" }, + { name = "cryptography" }, { name = "httpx" }, { name = "loguru" }, { name = "pydantic" }, @@ -1717,9 +1762,11 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "argon2-cffi", specifier = ">=23.1" }, { name = "boto3", specifier = ">=1.34" }, { name = "click", specifier = ">=8.0" }, { name = "cowsay-python", specifier = ">=1.0.2" }, + { name = "cryptography", specifier = ">=42.0" }, { name = "httpx", specifier = ">=0.27" }, { name = "inline-snapshot", marker = "extra == 'dev'", specifier = ">=0.13" }, { name = "loguru", specifier = ">=0.7" },