opz 2026.5.6

1Password CLI wrapper for seamless secret injection into commands
opz-2026.5.6 is not a library.

opz

crates.io license CI

opz is a small wrapper around the 1Password CLI. It finds items, turns valid field labels into environment variables, and runs commands with those secrets injected.

Features

  • Search 1Password items by title keyword.
  • Check op authentication, optional CLI dependencies, and plaintext .env-style credential files with doctor.
  • Show item field labels that are valid shell environment variable names.
  • Run a command with secrets from one or more 1Password items, with repository-based item auto-detection.
  • Generate env files containing op://... references, preserving unrelated existing lines.
  • Migrate scripts from explicit items or .env files to repository metadata.
  • Save private config files as Secure Notes.
  • Store valid item fields as GitHub repository secrets, guarded by item repository metadata when present.
  • Store valid item fields as Cloudflare Worker secrets through Wrangler.
  • Print the bundled opz Agent Skill.
  • Cache item lists and repository metadata for 60 seconds, then fall back to title contains matching when exact lookup misses.

Installation

cargo install opz

Usage

Find Items

Search item titles by keyword:

opz find <query>

Example:

opz find github
# Output: <item-id>    <vault-name>    github-token

Doctor

Check 1Password CLI status and external command dependencies:

opz doctor

doctor exits non-zero when required op checks fail. Missing optional tools such as gh, wrangler, git, sh, or secretlint are reported as warnings. It also checks for plaintext .env-style credential files and, when secretlint is available, runs it against those files.

Show Item Labels

Show field labels that can be used as environment variable names:

opz show [OPTIONS] [--with-item] <ITEM>...

Options:

  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)
  • --with-item - Show per-item headers

Examples:

# Label names only (one per line)
opz show foo bar

# Include item header sections
opz show --with-item foo bar

Emit Agent Skill

Print the bundled Agent Skills SKILL.md for opz:

opz skills

This lets other agents and tools load the current opz usage context directly in the Agent Skills standard format.

Removed create Command

opz create no longer creates items. It remains as a hidden compatibility shim so older scripts get a clear migration error instead of an unknown-command failure.

Use these commands instead:

# Create an API_CREDENTIAL item from .env and migrate supported scripts
opz migrate --new

# Store a non-.env private file as Secure Note item(s)
opz note app.conf

Run Commands with Secrets

Run a command with secrets from one or more 1Password items:

opz run [OPTIONS] [--env-file <ENV>] [<ITEM>...] -- <COMMAND>...
opz [OPTIONS] [--env-file <ENV>] [<ITEM>...] -- <COMMAND>...

Options:

  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)
  • --env-file <ENV> - Output env file path. If omitted, no file is written.

Arguments:

  • <ITEM>... - Optional item titles to fetch secrets from. When omitted, opz auto-detects one item whose github_repositories metadata matches a current git remote.

When --env-file is set, the file remains after the command exits. Existing files are merged: unrelated lines stay in place, duplicate keys are overwritten, and new keys are appended. If multiple items define the same key, later items win (opz run foo bar ... prefers values from bar).

Examples:

# Run with one item and no .env file generated
opz run example-item -- your-command

# Recommended after migration: auto-detect item from git remote metadata
opz run -- your-command

# Run command with multiple items (later items win on duplicate keys)
opz run foo bar -- your-command

# Generate an env file only when another tool requires op:// references
opz run --env-file .env foo bar -- your-command

# Top-level shorthand also supports multiple items
opz --env-file .env.local foo bar -- your-command

# Quote variables so your shell leaves them for opz to expand
opz run my-service -- curl -H 'Authorization: Bearer $API_TOKEN' https://api.example.test

# Specify vault
opz run --vault Private foo bar -- your-command

Generate Env File

Generate op://... env references without running a command:

opz gen [OPTIONS] [--env-file <ENV>] <ITEM>...

Examples:

# Output sectioned env references to stdout
opz gen foo bar

# Generate .env file
opz gen --env-file .env foo bar

# Generate to custom path
opz gen --env-file .env.production foo bar

# Specify vault
opz --vault Private gen foo bar

Stdout uses per-item comment headers such as # --- item: <title> ---. File output writes the merged key list without those section comments.

Migrate Scripts and .env

Migrate justfile/Justfile recipes and package.json scripts to repository metadata and item auto-detection:

opz migrate [OPTIONS]

Options:

  • --dry-run - Print metadata and file changes without editing 1Password items or files.
  • --new - Create a new API_CREDENTIAL item from .env before rewriting .env-based scripts. The item title defaults to the first git remote repository name.
  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)

Behavior:

  • opz run <ITEM> -- <COMMAND> becomes opz run -- <COMMAND> after recording the current git remote in <ITEM> metadata.
  • opz <ITEM> -- <COMMAND> becomes opz -- <COMMAND> with the same metadata update.
  • op item get <ITEM> is used as a metadata registration signal, but is not rewritten because it is not equivalent to opz run.
  • .env-based scripts are rewritten only with --new; without it, they are reported and skipped.
  • package.json is patched at the matching script string, so key order and formatting outside the changed value stay intact.

Examples:

# Preview migration
opz migrate --dry-run

# Rewrite scripts and update item metadata
opz migrate

# Create a new item from .env and migrate .env-based scripts
opz migrate --new

Save Private Config as Secure Note

Store a private config file as Secure Note item(s), titled from git remotes:

opz note <FILE>

Behavior:

  • Stores the file as a fenced note body: ```<file name>\n<content>\n```.
  • Uses git remote repository names (org/repo) as item titles.
  • If multiple remotes exist, creates one item per remote; duplicate titles get -2, -3, and so on.
  • Fails if no parseable git remote is available.

Examples:

opz note app.conf
opz --vault Private note app.conf

Add GitHub Repository Metadata to Existing Items

Add or update github_repositories metadata on existing 1Password items:

opz github-repo [OPTIONS] <ITEM>...

Options:

  • --repo <OWNER/REPO> - Repository to record. Repeat for multiple repositories. Defaults to parseable git remotes from the current repository.
  • --dry-run - Print the metadata update without editing items.
  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)

Examples:

# Preview migration using current git remotes
opz github-repo --dry-run my-service shared-secrets

# Add current git remote repository metadata
opz github-repo my-service shared-secrets

# Add explicit repositories
opz github-repo --repo owner/repo --repo other/service my-service

Existing github_repositories entries are preserved and merged with the requested repositories.

Store GitHub Repository Secrets

Store valid item fields as GitHub repository secrets:

opz github-secret [OPTIONS] <ITEM>...

Options:

  • --repo <OWNER/REPO> - Target GitHub repository (defaults to the current gh repository)
  • --dry-run - Print the secret names that would be set without writing values
  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)

Examples:

# Preview secret names
opz github-secret --dry-run my-service

# Store secrets in the current repository
opz github-secret my-service

# Store secrets in a specific repository
opz github-secret --repo owner/repo my-service shared-secrets

github-secret uses the same valid field labels as gen and run. Duplicate names across multiple items use the later item. Secret values are resolved in memory and passed to gh secret set through stdin; values are not printed or passed as command arguments. Names starting with GITHUB_ are rejected because GitHub reserves that prefix.

If a selected 1Password item has a github_repositories field, the target repository must match one of its owner/repo entries before opz resolves or writes secret values. Multiple repositories are allowed by separating entries with newlines or commas. Items without this metadata are still allowed, but opz prints a warning because the repository guard cannot be applied.

Store Cloudflare Worker Secrets

Store valid item fields as Cloudflare Worker secrets through Wrangler:

opz cloudflare-secret [OPTIONS] <ITEM>...

Options:

  • --name <WORKER> - Worker name passed to wrangler secret bulk --name
  • --env <ENV> - Wrangler environment passed to wrangler secret bulk --env
  • --config <PATH> - Wrangler config path passed to wrangler secret bulk --config
  • --dry-run - Print the secret names that would be set without writing values
  • --vault <NAME> - Vault name (optional, searches all vaults if omitted)

Examples:

# Preview secret names
opz cloudflare-secret --dry-run my-service

# Store secrets using the current Wrangler project config
opz cloudflare-secret my-service

# Store secrets for a specific Worker environment
opz cloudflare-secret --name worker-app --env production my-service shared-secrets

cloudflare-secret uses the same valid field labels as gen and run. Duplicate names across multiple items use the later item. Secret values are resolved in memory and passed to wrangler secret bulk through stdin as JSON; values are not printed or passed as command arguments.

How It Works

  1. When item titles are provided, opz fetches the item list from 1Password and caches that metadata for 60 seconds.
  2. Title lookup tries exact match first, then title contains matching.
  3. When item titles are omitted, opz reads git remotes, loads a cached github_repositories index, and accepts the result only when exactly one item matches.
  4. After the item is selected, opz fetches it and builds op://<vault_id>/<item_id>/<field> references for fields with valid env labels.
  5. If --env-file is set, opz writes references to that file and preserves unrelated existing lines. The usual path is file-free opz run; env files are for tools that require op:// references.
  6. Secret values are resolved with op run --env-file <temp> -- sh -c 'env -0', with op read per reference as a fallback.
  7. opz runs the command with the resolved values in the environment. $VAR and ${VAR} in command arguments are expanded only for variables resolved from the selected items.

gen stops after writing references. show fetches items and prints valid labels without resolving secret values.

op Command Usage

For security transparency, here's how opz uses the op CLI:

sequenceDiagram
    participant opz
    participant op as op CLI

    Note over opz: User runs: opz example-item -- claude "hello"

    opz->>op: op item list --format json
    op-->>opz: [{id, title, vault}, ...]
    Note over opz: Match "example-item" → get item ID

    opz->>op: op item get <id> --format json
    op-->>opz: {fields: [{label, value}, ...]}
    Note over opz: Resolve secret values<br/>(inject as env vars)

    Note over opz: Optional: write .env if specified

    opz->>op: sh -c "claude \"hello\""
    Note over opz: Execute with secrets in environment
    op-->>opz: Exit status

Security: opz delegates secret access and authentication to the op CLI. The 60-second caches store item-list and repository metadata only, not secret values.

Tracing (OpenTelemetry + Jaeger)

opz can emit OTLP traces, but it is disabled by default. If OTEL_EXPORTER_OTLP_ENDPOINT is not set, tracing is a no-op.

Local setup

just jaeger-up
just trace-run item=<your-item-title>
just trace-ui

E2E trace on Jaeger

If you want to inspect traces generated by tests/e2e_real_op.rs:

just jaeger-up
just e2e-trace
just trace-ui

In Jaeger Search, select service opz-e2e.
just e2e-trace automatically sets OPZ_GIT_COMMIT=$(git rev-parse --short=12 HEAD).

Compare traces by ref or version

Generate traces on each target commit/tag (or release version), then compare:

just trace-report <ref-or-version>
just trace-compare <base-ref-or-version> <head-ref-or-version>

<ref-or-version> accepts commit hash, git tag (for example v2026.5.1), or service.version (for example 2026.5.1). Both commands print markdown tables (duration and top child span) for easy copy into PRs.

For less noisy comparisons, aggregate multiple runs and ignore failed traces:

just trace-report-samples <ref-or-version> samples=5 status=ok
just trace-compare-samples <base-ref-or-version> <head-ref-or-version> samples=5 status=ok

samples uses latest N traces per operation and reports median/average. status can be all, ok, or error.

Then open Jaeger Search and select service opz (or your OTEL_SERVICE_NAME) to inspect spans such as:

  • cli.<command> (root)
  • parse_args
  • load_config
  • load_inputs
  • main_operation
  • write_outputs

Environment variables

  • OTEL_EXPORTER_OTLP_ENDPOINT - Enables OTLP export when set (example: http://localhost:4317)
  • OTEL_SERVICE_NAME - Optional service name override (default: opz)
  • OTEL_TRACES_SAMPLER - Optional sampler setting (always_on, traceidratio, etc.)
  • OTEL_TRACES_SAMPLER_ARG - Optional sampler parameter (for ratio-based samplers)
  • OPZ_TRACE_CAPTURE_ARGS - 1 to include sanitized cli.args in trace attributes (default: disabled)
  • OPZ_GIT_COMMIT - Optional override for trace resource attribute git.commit (default: git rev-parse --short=12 HEAD)

Requirements

Install and authenticate 1Password CLI (op) before using secret-backed commands.

github-secret needs GitHub CLI (gh). cloudflare-secret needs Wrangler (wrangler). migrate and note need Git (git) when they read repository remotes.

E2E Test

Real 1Password e2e test is available in tests/e2e_real_op.rs.

It is gated for safety and runs only when OPZ_E2E=1 is set:

OPZ_E2E=1 cargo test --test e2e_real_op -- --nocapture

Or use just:

just e2e