Metadata-Version: 2.4
Name: zeno-secrets-1password
Version: 1.1.0
Summary: Zeno secrets adapter: 1Password CLI-backed `SecretsStore` for agent runtime secrets.
Project-URL: Homepage, https://github.com/nkootstra/zeno
Project-URL: Repository, https://github.com/nkootstra/zeno
Project-URL: Issues, https://github.com/nkootstra/zeno/issues
Project-URL: Changelog, https://github.com/nkootstra/zeno/blob/main/CHANGELOG.md
Author: Niels Kootstra
License-Expression: MIT
License-File: LICENSE
Keywords: 1password,agent,ai,secrets,vault,zeno
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: zeno-core
Description-Content-Type: text/markdown

# zeno-secrets-1password

1Password-backed `SecretsStore` for Zeno.

Ships `OpCliSecretsStore`, a `SecretsStore` adapter that reads secrets
through the 1Password CLI (`op read`). Intended for agents that run in
environments where a 1Password service-account token is available but
mounting the raw secret material into the process env is undesirable.

## Install

```bash
uv add 'zeno-framework[secrets-1password]'
```

Install the 1Password CLI on the host:

```bash
brew install 1password-cli       # macOS
# or follow https://developer.1password.com/docs/cli/get-started/
```

Export a service-account token:

```bash
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."
```

## Usage

```python
from zeno.app import ZenoApp
from zeno.secrets_1password import OpCliSecretsStore

app = ZenoApp(
    ...,
    secrets=OpCliSecretsStore(
        references={
            "OPENAI_API_KEY": "op://Engineering/openai/credential",
            "STRIPE_KEY":     "op://Engineering/stripe/api_key",
        },
        cache_ttl=60.0,
    ),
)
```

Inside a tool:

```python
@tool
async def call_stripe(ctx: Ctx) -> str:
    api_key = await ctx.secrets.get("STRIPE_KEY")
    ...
```

## What it does

- `get(name)` — if `name` begins with `op://`, invokes `op read name`
  directly; otherwise looks up the reference in the `references` map and
  shells `op read <ref>`.
- Results are cached in-process for `cache_ttl` seconds (default 60).
  Pass `cache_ttl=None` to disable caching.
- Invocations time out at `timeout` seconds (default 5). The subprocess
  is killed and reaped on timeout; no `ResourceWarning` leaks.
- `OP_SERVICE_ACCOUNT_TOKEN` is passed to the subprocess via an explicit
  env dict that inherits only `PATH` — unrelated parent env vars are not
  leaked into the `op` process. Pass `token="ops_..."` to the
  constructor to override the env var.
- Missing `op` binary → `ConfigurationError` with an install pointer.
- Missing token → `ConfigurationError` raised before the subprocess
  runs.
- `op read` exit codes are inspected: "item not found" / "no such" /
  "isn't an item" map to `SecretNotFoundError`; everything else (auth
  failures, network errors) maps to `SecretsBackendError` with the
  stderr text preserved in the message.

## Why `op` CLI, not the Connect HTTP API?

The CLI path works both locally (via 1Password desktop auth) and in CI
(via a service-account token) with no Connect server to self-host.
Connect-based and SDK-based stores can ship later as sibling classes
inside the same package without breaking existing callers.

Part of the [Zeno framework](https://github.com/nkootstra/zeno).
