Skip to content

feat: guided secret onboarding via 'op-env add' subcommand #38

Description

@DAESA24

Summary

Today's op-env onboarding for a new secret required walking through ~8 coordinated manual steps across multiple files, with several non-obvious friction points along the way. Proposing a guided op-env add <ENV_VAR> subcommand (and a few sibling commands) that turns the multi-file, multi-tool dance into a single interactive flow.

This issue is intentionally scoped to be a cold-start-readable design discussion, not a finished spec. The goal is to capture the real friction I just hit so a future session can pick this up and refine into an implementation plan.


Background — what triggered this

While working in a Claude Code session, I needed to add a Bright Data API token to op-env so the BrightData MCP server could read it at runtime instead of having the token hardcoded in ~/.claude.json. The wiring path turned out to be longer than I'd remembered. I went through it manually on purpose so I could feel the friction.

What I noticed:

  • Each step is individually small, but they span three different files and four different command-line tools (op, op-env, code/claude, plus whatever the downstream consumer is)
  • The order matters — if you update ~/.claude.json before verifying the template resolves, you can end up with a broken state that's harder to diagnose
  • The vault UUID / item UUID / field lookup is the most error-prone part. I made a shell-syntax mistake (literal angle brackets in op item get) that produced a confusing "no such file or directory" error
  • There is no obvious place in the README or the --help output that walks through "add a new key end-to-end." The README explains the what and the architecture, but the operational add-a-key workflow is implicit
  • The --check flag is excellent for validation but only kicks in after you've already authored the template line correctly

Manual flow today (what I just did)

For a single new secret going through op-env into a downstream consumer (Claude Code MCP server in this case):

  1. Find the vault UUIDop vault list --format=json | jq (or remember it from earlier work)
  2. Find the item UUIDop item list --vault <vault-uuid> | grep -i <item-name>
  3. Find the field nameop item get <item-uuid> --format json | grep -E '"id"|"label"' (and intuit which field actually holds the credential value — typically credential, but not always)
  4. Construct the reference — manually assemble op://<vault-uuid>/<item-uuid>/<field>
  5. Append the line to the template — open ~/.claude/.env.tpl in an editor, add MY_VAR=op://... near alphabetical neighbors
  6. Verify resolutionop-env --check and confirm the new key shows ✓
  7. Update the downstream consumer — for an MCP server, open ~/.claude.json, find the relevant env block, change the hardcoded value to "${MY_VAR}"
  8. Restart in a way that re-reads everything — full quit of Claude Code (or the parent app), relaunch via op-env claude (or op-env code for the VS Code extension)
  9. Verify at runtimeprintenv MY_VAR in the new session to confirm injection landed

Steps 7-9 are out of op-env's scope strictly speaking, but they're part of the workflow that op-env users actually need to complete. A useful tool would at minimum acknowledge that these steps exist (a "Next steps:" printout after the template line is added).


Pain points worth optimizing

# Friction Why it matters
1 op vault list + op item list + op item get is three commands to find one reference Most users only need vault → item → credential, but they have to know the API
2 The reference syntax op://vault/item/field is easy to typo A single character off and op read returns nothing useful
3 No interactive picker — copy/paste UUIDs between commands is brittle This is where my "no such file or directory" zsh redirection error happened
4 The template file has no schema validation beyond op read succeeding A typo in the env-var NAME silently breaks downstream interpolation but resolves fine in --check
5 The "what file consumes this var?" knowledge is in the user's head Especially painful for MCP servers in ~/.claude.json where the binding isn't obvious
6 No "test the consumer received it" step op-env --check ✓ does not imply the child process actually sees the var (e.g., GUI-launched VS Code)

Proposed feature — op-env add <ENV_VAR_NAME>

A guided onboarding subcommand that walks the user from "I have a secret in 1Password" to "the template line is written and verified." Sketch:

$ op-env add BRIGHTDATA_API_TOKEN

op-env: adding BRIGHTDATA_API_TOKEN to ~/.claude/.env.tpl

[1/4] Select vault:
  1) Credentials-Workflow-Tools  (VAULT_UUID)
  2) Private                     (VAULT_UUID_B)
  3) Personal                    (VAULT_UUID_C)
> 1

[2/4] Search items in Credentials-Workflow-Tools (or press enter to list all):
> bright

  1) BrightData API Key
  2) BrightData MCP Token (backup)
> 1

[3/4] Select field:
  1) credential       (default)
  2) notesPlain
  3) username
> 1  (or press enter for default)

[4/4] Resolving op://VAULT_UUID/ITEM_UUID/credential ...
  ✓ Resolved (first 2 chars: **)

Appending to ~/.claude/.env.tpl:
  BRIGHTDATA_API_TOKEN=op://VAULT_UUID/ITEM_UUID/credential

Done. Next steps:
  • Update the consuming config file to reference ${BRIGHTDATA_API_TOKEN}
    (common locations: ~/.claude.json mcpServers blocks, .env consumers, app configs)
  • Quit and relaunch the consuming process so it inherits the new env var
  • Run `printenv BRIGHTDATA_API_TOKEN` in the new shell to confirm

Key behaviors:

  • Non-interactive mode supported: op-env add MY_VAR --vault <uuid> --item <uuid> --field credential for scripting / CI / re-running from history
  • --dry-run flag that prints the line that would be appended without writing
  • Reuses the existing --tpl flag for non-default template paths
  • Fails closed: aborts before writing if resolution fails

Adjacent companion commands (open for debate)

Command Purpose Priority
op-env add The big one above P0
op-env list Print all template keys with resolution status and (optional) which file consumes each P1
op-env rotate <VAR> Walk the user through rotating a leaked credential — 1Password update + cache invalidation reminder P1
op-env scaffold Detect unwired secrets in known consumers (e.g., ~/.claude.json MCP env blocks with hardcoded values) and propose migration P2
op-env init Bootstrap a fresh ~/.claude/.env.tpl from a chosen vault P2
op-env add --for claude-code-mcp <server> One-shot: add the var AND update the ~/.claude.json MCP block to interpolate it P2 — high value, high risk (touches a file op-env doesn't own today)

The P2 "consumer-aware" mode is the most powerful but also the biggest scope expansion — op-env today is deliberately narrow (read template, exec command). Touching downstream config files is a meaningful boundary shift. Worth a separate issue once add lands.


Code-area touch points

Area What changes
bin/op-env Subcommand routing — detect first non-flag arg as subcommand verb (add, list, rotate...). Today's op-env <command> [args] becomes op-env [subcommand] [flags] [command] [args]. Backward compatibility: any first arg that isn't a known subcommand keeps existing exec-target behavior.
bin/op-env (new functions) op_env_add() orchestrating the 4-phase flow. Uses op vault list --format=json + op item list --format=json + op item get --format=json + jq for selection. read -r -p for prompts.
templates/env.tpl.example Possibly add a header comment noting op-env add as the recommended way to add lines
docs/architecture.md Add an "Adding a new secret" section that walks through both manual and op-env add paths
README.md Promote op-env add to the Quick start example
test/test-op-env.sh New test cases mocking op vault list / op item list / op item get and feeding scripted stdin
.github/workflows/... Existing checks (B1 no exfiltration, B2 TTY preservation, F1/F2 complexity flags) should already cover the new code — verify the new path doesn't introduce a non-exec exit

Dependencies to consider:

  • jq becomes a hard dependency for add (currently soft). Already in the README as a usage hint, so probably fine
  • Optional: fzf for nicer selection if present, fall back to numbered list if not
  • Optional: gum (Charm) for prettier prompts — same fallback story

Explicitly out of scope for v1

  • Writing to ~/.claude.json or any other consumer config (defer to the --for P2 idea)
  • A TUI / full-screen UI — keep it line-oriented to match the rest of op-env's style
  • Vault discovery across multiple 1Password accounts (use the active default account)
  • Editing existing template lines (use the manual flow, or implement later as op-env edit)

Acceptance sketch (for the future session that picks this up)

A working op-env add is shipped when:

  1. op-env add MY_VAR walks an interactive flow that ends with a new line in the template
  2. The new line resolves via op-env --check immediately after
  3. Existing 56 tests still pass
  4. New test coverage exists for: vault selection, item selection, field selection, resolution failure abort, --dry-run, and non-interactive flags
  5. The README's "Adding a new secret" section is updated to point at the new command first, with manual flow as fallback
  6. A "Next steps" guidance block prints after the line is added, calling out the most common downstream consumers (Claude Code MCP servers being the prime example)

Open questions

  • Should op-env add be smart enough to detect if ~/.claude.json has hardcoded values matching the just-added secret and offer to migrate them? Yes-but-as-a-follow-up feels right.
  • Is interactive-only acceptable for v1, or do the non-interactive flags need to land at the same time? My intuition says interactive-only is fine for v1 — scripted usage is a power-user feature
  • How verbose should the "Next steps" block be? Drift between "minimal" (just the variable name) and "opinionated" (specific paths for Claude Code) is a real tradeoff

This issue is intentionally written to be readable cold. If you're picking this up in a future session, the manual flow section is the most important context — that's the lived experience this feature is trying to remove.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backlogUnprocessed backlog itemenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions