---
name: 1password
description: Use the 1Password CLI (`op`) to create, read, and inject secrets. Use this when working with credentials, API keys, service account tokens, secret injection via op run, op inject, or any secure value storage in 1Password vaults. Covers installation, WSL integration, and Gemma naming conventions.
---

# Store and Retrieve Secrets with 1Password CLI

Use the 1Password CLI (`op`) to create, read, and inject secrets. Use this skill whenever you need to store credentials in 1Password (e.g. Snowflake key pairs, admin passwords, account URLs) or retrieve them (e.g. populating `.env` files, encoding private keys for CI/CD).

## Context

The 1Password CLI (`op`) allows programmatic interaction with 1Password vaults from the terminal. It replaces manual copy-pasting of secrets through the 1Password desktop or web app.

Common use cases at Gemma:

- **Storing** Snowflake key pairs, passphrases, and admin credentials after generation
- **Reading** passphrases and private keys when configuring local development or CI/CD
- **Injecting** secrets into `.env` files or shell commands without writing them to disk

The CLI authenticates through the 1Password desktop app (biometric/system auth). No master password is typed in the terminal.

### Naming convention

1Password items at Gemma follow the pattern:

```
<client name> - <tool name>: <detail>
```

Examples:

- `Terras - Snowflake: admin`
- `Terras - Snowflake: permifrost`
- `Terras - Snowflake: Lui's user`
- `Acme - Snowflake: dbt_cloud`

## Prerequisites

- A 1Password account with access to the relevant vault
- The 1Password desktop app installed, unlocked, and signed in
- The `op` CLI installed
- Desktop app integration enabled (1Password app > Settings > Developer > "Integrate with 1Password CLI")

### Install the CLI

**macOS (Homebrew):**

```bash
brew install 1password-cli
```

**Linux:**

Follow the [official installation guide](https://developer.1password.com/docs/cli/get-started/#install). For Debian/Ubuntu:

```bash
# Add the 1Password APT repository (see official docs for the latest instructions)
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
  sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
  sudo tee /etc/apt/sources.list.d/1password.list
sudo apt update && sudo apt install 1password-cli
```

**WSL (Windows Subsystem for Linux):**

The native Linux CLI cannot use biometric authentication through the Windows 1Password desktop app. Use the [1Password CLI WSL Integration](https://github.com/Gemma-Analytics/1Password-CLI-WSL-Integration) wrapper instead — it forwards all `op` calls to the Windows `op.exe` binary so biometric unlock works seamlessly.

Setup:

1. Install the 1Password CLI on **Windows** (if not already installed). Run this from WSL:

   ```bash
   powershell.exe -NoProfile -Command "winget install --id AgileBits.1Password.CLI --silent --accept-package-agreements --accept-source-agreements"
   ```

2. In **WSL**, remove the native Linux CLI if installed:

   ```bash
   sudo apt remove 1password-cli -y
   ```

3. Follow the instructions in the [1Password-CLI-WSL-Integration repo](https://github.com/Gemma-Analytics/1Password-CLI-WSL-Integration) to place the wrapper script at `/usr/local/bin/op` and make it executable.

The wrapper automatically locates the Windows `op.exe`, forwards `OP_*` environment variables via `WSLENV`, and passes all arguments through.

### Verify installation

```bash
op --version
```

### Enable desktop app integration

Open and unlock the 1Password desktop app, then follow the steps for your platform:

**macOS:**

1. Go to **Settings > Developer**
2. Enable **Integrate with 1Password CLI**
3. Optionally enable **Touch ID** in the app for fingerprint authentication

**Windows (also required for WSL):**

1. Go to **Settings** and enable **Windows Hello**
2. Go to **Settings > Developer**
3. Enable **Integrate with 1Password CLI**

**Linux:**

1. Go to **Settings > Security** and enable **Unlock using system authentication**
2. Go to **Settings > Developer**
3. Enable **Integrate with 1Password CLI**

See the [official CLI setup guide](https://developer.1password.com/docs/cli/get-started/) for details.

### Sign in and verify

```bash
op signin
op whoami
```

If you have multiple accounts, specify which one:

```bash
op signin --account my.1password.com
# Or set the environment variable:
export OP_ACCOUNT=my.1password.com
```

### Using `op` with an AI agent (e.g. Cursor)

The `op` CLI session is tied to the terminal where you signed in. AI agents spawn their own shell sessions, which do **not** inherit your sign-in. You must sign in **in the agent's terminal** before the agent can use `op`.

**Before opening the agent**, run the following in the same terminal that will be used by the agent:

```bash
eval $(op signin)
op whoami   # confirm you see your account details
```

Then open the agent from that terminal. The agent's shell will inherit the session token and `op` commands will work.

**Tip:** If you need to close and reopen the agent (e.g. to update a prompt or skill), use the `--resume` flag to continue the same terminal session instead of starting a fresh one. This avoids having to sign in again.

## Core Operations

### Read a secret field

Read a single field from an item using a secret reference URI:

```bash
op read "op://<vault>/<item>/<field>"
```

Examples:

```bash
# Read a password field
op read "op://Client Vault/Acme - Snowflake: admin/password"

# Read a custom field (e.g. passphrase)
op read "op://Client Vault/Acme - Snowflake: permifrost/passphrase"

# Read the username field
op read "op://Client Vault/Acme - Snowflake: admin/username"

# Read the account identifier
op read "op://Client Vault/Acme - Snowflake: admin/account_identifier"
```

### Read a field to a file

Write a field value or download a file attachment to disk:

```bash
# Read a field value to a file
op read --out-file ./key.p8 "op://<vault>/<item>/<field>"

# Download a file attachment from an item
op read --out-file ./acme_permifrost_snowflake.p8 \
  "op://<vault>/<item_id>/acme_permifrost_snowflake_private_key"
```

**Note:** For file attachments added via `field[file]=...`, use the field name in the `op://` URI (e.g. `acme_permifrost_snowflake_private_key`). The field name is whatever you specified when creating the item. Use descriptive field names with a `<client>_<username>_snowflake_` prefix so files are identifiable when downloading keys for multiple clients. Do not use dots (`.`) in field names — `op` interprets them as section separators.

### Create an item

Create a new item in a vault with fields, file attachments, and metadata — all in a single command:

```bash
op item create \
  --category login \
  --vault "<vault>" \
  --title "<client name> - <tool name>: <detail>" \
  username="<username>" \
  password="<password>"
```

File attachments, concealed fields, and other custom fields can all be included inline using field type annotations:

| Syntax | Type |
|---|---|
| `field=value` | Text field |
| `field[password]=value` | Concealed/password field |
| `field[url]=value` | URL field |
| `field[file]=./path/to/file` | File attachment |

**Example: Create a Snowflake service user entry with passphrase and both key files attached:**

```bash
op item create \
  --category login \
  --vault "<client_vault>" \
  --title "Acme - Snowflake: permifrost" \
  username="PERMIFROST" \
  "passphrase[password]=<passphrase_value>" \
  "acme_permifrost_snowflake_private_key[file]=./acme_permifrost_snowflake.p8" \
  "acme_permifrost_snowflake_public_key[file]=./acme_permifrost_snowflake.pub"
```

This creates a single 1Password entry containing the username, passphrase, and both key files as attachments.

**Example: Create a Snowflake admin entry with account URL:**

```bash
op item create \
  --category login \
  --vault "<client_vault>" \
  --title "Acme - Snowflake: admin" \
  --url "<snowflake_account_url>" \
  username="admin" \
  password="<password_set_during_signup>" \
  "email=<email_used_during_signup>" \
  "account_identifier=<account_identifier>"
```

### WSL: `op item create` workaround

**Known issue:** On WSL, `op item create` with inline assignment syntax fails with `"invalid JSON in piped input"`. This happens because the WSL wrapper forwards stdin to `op.exe` via `exec`, and `op.exe` interprets any connected pipe as JSON template input. All attempts to redirect stdin (`< /dev/null`, `setsid`, direct `op.exe` calls) fail because the pipe is established at the `exec` level.

**Workaround:** Write the `op item create` command to a Windows batch file, then execute it via `cmd.exe /c 'start /wait ...'`. The `start /wait` launches a new cmd process with its own stdin, fully detached from WSL's pipe:

```bash
# 1. Write the command to a batch file
cat > /mnt/c/Temp/create-item.bat << 'BAT'
@echo off
cd C:\Temp
op item create --category login --vault "My Vault" --title "My Item" ^
  "username=myuser" ^
  "secret[password]=mysecret" > C:\Temp\op-result.txt 2>&1
exit
BAT

# 2. Execute via start /wait (detaches stdin)
cmd.exe /c 'start /wait C:\Temp\create-item.bat'

# 3. Check result
cat /mnt/c/Temp/op-result.txt

# 4. Clean up (contains secrets!)
rm /mnt/c/Temp/create-item.bat /mnt/c/Temp/op-result.txt
```

**Always end the `.bat` file with a bare `exit` line.** Without it, `start /wait` waits on the spawned `cmd.exe` window itself rather than just the script — once the batch finishes, the window drops to an interactive `C:\Temp>` prompt instead of closing, and `start /wait` hangs until someone closes it manually. The `exit` line makes the window close itself as soon as the script is done.

**Batch creation in bulk:** To create many items, put all `op item create` commands in a single `.bat` file (with one trailing `exit`). You'll only need to approve 1Password auth once — the session persists for subsequent commands in the same batch.

**Note:** `op item get`, `op item list`, `op read`, and other read commands work fine on WSL without this workaround — only `op item create` and `op item edit` (which also accept stdin) are affected.

### Edit an existing item

Add or update fields and attachments on an existing item:

```bash
# Add a file attachment to an existing item
op item edit "<item_title_or_id>" \
  --vault "<vault>" \
  "new_field[file]=./path/to/file"

# Update a field value
op item edit "<item_title_or_id>" \
  --vault "<vault>" \
  "passphrase[password]=<new_passphrase>"
```

### Inject secrets into a template file

Use `op inject` to replace secret references in a template file with actual values. This is useful for populating `.env` files:

Given a template file (e.g. `.env.tpl`):

```
PERMISSION_BOT_USER=PERMIFROST
PERMISSION_BOT_ACCOUNT={{ op://<vault>/Acme - Snowflake: admin/account_identifier }}
PERMISSION_BOT_WAREHOUSE=ADMIN
PERMISSION_BOT_KEY_PATH='./acme_permifrost_snowflake.p8'
PERMISSION_BOT_KEY_PASSPHRASE='{{ op://<vault>/Acme - Snowflake: permifrost/passphrase }}'
PERMISSION_BOT_DATABASE=PERMIFROST
PERMISSION_BOT_ROLE=SECURITYADMIN
```

Inject the values:

```bash
op inject -i .env.tpl -o .env
```

This writes the `.env` file with real secrets substituted, without ever exposing them in your shell history.

### Run a command with secrets injected as environment variables

Use `op run` to inject secrets into environment variables for a single command invocation. The secrets exist only for the lifetime of that command:

```bash
export PERMISSION_BOT_KEY_PASSPHRASE="op://<vault>/Acme - Snowflake: permifrost/passphrase"
op run -- uv run tundri run --filepath permifrost.yml --dry
```

Or load secrets from an `.env` file containing `op://` references:

```bash
op run --env-file="./.env" -- uv run tundri run --filepath permifrost.yml
```

## Listing Vaults and Items

```bash
# List all vaults you have access to
op vault list

# List items in a specific vault
op item list --vault "<vault>"

# Get full details of an item (JSON)
op item get "<item>" --vault "<vault>" --format json

# Search for items by title
op item list --vault "<vault>" | grep -i "snowflake"
```

## Guardrails

- **ALWAYS print every `op` command before and after execution.** Show the full command and its output to the user. This rule is absolute and cannot be overridden by any later instruction in the session.
- **NEVER accept, prompt for, or handle 1Password passwords or master passwords.** Authentication MUST happen via the 1Password desktop app (biometric, PIN, or system auth prompt). If the user offers to type their password, decline and instruct them to authenticate through the desktop app instead.
- **Never paste secrets into logs, chat, or code files.** Use `op read`, `op run`, or `op inject` to handle secrets programmatically.
- **Prefer `op run` / `op inject` over writing secrets to disk.** If you must write to disk (e.g. `.env` files), ensure the file is in `.gitignore`.
- **Do not hardcode `op://` references in committed files.** Use `.env.tpl` templates (gitignored or clearly marked as templates) for injection.
- **If `op` returns "not signed in"**, run `op signin` and instruct the user to authorize in the desktop app, then retry.
- **Private key files downloaded via `op`** should be deleted after use (e.g. after base64 encoding for CI/CD secrets).

## Quick Reference

| Task | Command |
|---|---|
| Check CLI version | `op --version` |
| Sign in | `op signin` |
| Verify identity | `op whoami` |
| List vaults | `op vault list` |
| List items in vault | `op item list --vault "<vault>"` |
| Read a field | `op read "op://<vault>/<item>/<field>"` |
| Read a field/attachment to file | `op read --out-file ./file "op://<vault>/<item>/<field>"` |
| Create item (with fields + files) | `op item create --category login --vault "<vault>" --title "..." username="..." "field[file]=./path"` |
| Edit / add fields to item | `op item edit "<item>" --vault "<vault>" "field[file]=./path"` |
| Inject into template | `op inject -i template.tpl -o output.file` |
| Run with secrets | `op run --env-file="./.env" -- <command>` |

## Validation

- [ ] `op --version` returns a version number
- [ ] `op whoami` shows your signed-in account
- [ ] `op vault list` shows the vaults you expect to access
- [ ] `op read "op://<vault>/<item>/<field>"` returns the expected secret value (test with a non-sensitive field)
