> ## Documentation Index
> Fetch the complete documentation index at: https://capy.sc/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync Encrypted .env Across Your Team (Capy)

> How Capy syncs encrypted secrets across your team: three-way merge of pinned, local, and remote .env state with conflict resolution in an interactive TUI.

The bare `capy` command is the main loop. Run it any time and Capy:

1. Authenticates you (if needed) via a browser-based login.
2. Unlocks your master key by co-decrypting it with the Capy service.
3. Pulls the latest encrypted `keep.lock` from the service.
4. Diffs remote values against your local `.env`.
5. Prompts you to resolve any conflicts.
6. Rewrites `.env` in place with `capy:…` snippets that encode the resolved values.

The plaintext only exists in memory during the diff. It's never written to disk.

## First run vs. subsequent runs

On the **first run in a project**, Capy treats `.env` as authoritative:

* Asks which organization and project to use (or creates them).
* Generates a project encryption key via the zero-trust co-decrypt flow.
* Writes `keep.lock` to the project root and commits it to git.
* Creates a `development` branch.
* Encrypts every value in `.env` and uploads the ciphertext.
* Adds `.env` and `.capy/` to `.gitignore`.
* Installs git hooks (`post-checkout` and `post-merge`) that run `capy status` after branch switches and merges.
* Writes a commented-out backup of your original `.env` to `.env.pre-capy.old` (gitignored) and rewrites `.env` with `capy:…` snippets.

On **subsequent runs**, Capy reconciles three sources: what's on the server, what you last pinned, and what's in your local `.env`. Conflicts open an interactive prompt where you pick a value per variable.

## Conflicts and resolution

Every `capy` run is a three-way compare between:

* **Pinned** - hashes stored in `keep.lock` from the last sync.
* **Local** - what's in your `.env` right now (decrypted in memory).
* **Remote** - what's on the service for this branch.

For each variable, Capy classifies the state from the triple `(pinned, local, remote)` and either applies it automatically or prompts you. Here's every case, using `A` for one value, `B` for another, and `—` for "absent":

### One side changed - auto-applied

| Pinned | Local | Remote | Action                                    |
| :----: | :---: | :----: | ----------------------------------------- |
|    A   |   A   |    A   | unchanged (not shown)                     |
|    —   |   A   |    —   | push A to remote (new locally)            |
|    —   |   —   |    A   | pull A to local (new on remote)           |
|    A   |   B   |    A   | push B to remote (changed locally)        |
|    A   |   A   |    B   | pull B to local (changed on remote)       |
|    A   |   —   |    A   | push deletion to remote (deleted locally) |
|    A   |   A   |    —   | prompt; typically pull the delete         |

When only one side moved, there's nothing to reconcile and Capy just applies the change.

### Both sides changed - conflict

| Pinned | Local | Remote | Action                                  |
| :----: | :---: | :----: | --------------------------------------- |
|    A   |   B   |    C   | CONFLICT — pick local or remote         |
|    —   |   B   |    C   | CONFLICT — same name, different origins |
|    A   |   B   |    B   | both agree on B — apply, no prompt      |
|    A   |   B   |    —   | CONFLICT — you edited, remote deleted   |
|    A   |   —   |    B   | CONFLICT — you deleted, remote edited   |
|    A   |   —   |    —   | delete propagated — no prompt           |

On a conflict Capy prints a three-way diff table and then a bulk-action menu:

```
  Variable       Pinned              Local               Remote
  ────────────────────────────────────────────────────────────────────
  DATABASE_URL   postg...5432/old    postg...5432/dev    postg...5432/prod
  API_KEY        sk_li...abcd        sk_li...wxyz        sk_li...abcd

? What would you like to do?
❯ Commit all local values
  Retrieve all pinned values
  Retrieve all remote values
  Individually resolve
  Continue working
```

Pick **Individually resolve** to open an interactive arrow-key table where each variable has its own row and its own Choice:

<div className="block dark:hidden">
  ```ansi theme={null}
    [90m← → select value   ↑ ↓ move between rows   Enter confirm   q cancel[0m
    Resolved: 0/2

    Variable       Pinned              Local               Remote              Choice
    ──────────────────────────────────────────────────────────────────────────────────
    DATABASE_URL   postg...5432/old    [40m[37mpostg...5432/dev [0m   postg...5432/prod   [90mlocal   [0m
    API_KEY        sk_li...abcd        sk_li...wxyz        sk_li...abcd
  ```
</div>

<div className="hidden dark:block">
  ```ansi theme={null}
    [90m← → select value   ↑ ↓ move between rows   Enter confirm   q cancel[0m
    Resolved: 0/2

    Variable       Pinned              Local               Remote              Choice
    ──────────────────────────────────────────────────────────────────────────────────
    DATABASE_URL   postg...5432/old    [47m[30mpostg...5432/dev [0m   postg...5432/prod   [90mlocal   [0m
    API_KEY        sk_li...abcd        sk_li...wxyz        sk_li...abcd
  ```
</div>

← → moves the selection across `pinned`, `local`, `remote`, and `delete`; ↑ ↓ moves between variables; Enter confirms the row and jumps to the next unresolved one. Once every row is confirmed, Capy applies the merged set and rewrites `.env` in place. `q` cancels without writing anything.

### "New on both sides" vs. "edit vs. edit"

A subtle distinction: if both sides independently **added** a variable with the same name but different values (pinned is `—`), Capy marks the conflict as `isNew` and the prompt frames it as two new candidates. If both sides **edited** a previously-synced variable (pinned is `A`), it's framed as an edit conflict.

The two look the same at the value level but have different provenance. Capy determines the difference by comparing resource IDs (the `capy:{resourceId}:…` prefix): different IDs mean the variables were created independently; the same ID means they share a common ancestor.

### Remote unreachable

If the service is down or you lack access to the branch, Capy falls back to a two-way compare of **pinned vs. local**. It won't destructively overwrite anything it can't see, and it reports every affected variable with a `?` marker (see [`capy status`](/cli/status)).

## Status and push

```bash theme={null}
capy status       # show drift between local, pinned, and remote
capy push         # push local changes without pulling
```

`status` is read-only and does not modify anything.

## What ends up in git

* `keep.lock` - a versioning manifest with no keys and no plaintext. **Committed.**
* `.env` - ciphertext snippets at rest, but gitignored. Capy syncs the content over the wire instead of through git.
* `.env.pre-capy.old` - commented-out backup of your original `.env` from before first-run. Gitignored.
* `.capy/` - local state (session tokens, cache). Gitignored.

## Branches

Capy has its own concept of **secret branches** - independent sets of values for dev, staging, prod, or ephemeral environments. Secret branches are tracked separately from git branches, but `post-checkout` hooks can auto-switch them when your git branch changes. See [Branches](/using/branches/overview).

## What's next

<Columns cols={2}>
  <Card title="capy (CLI reference)" icon="terminal" href="/cli/capy" horizontal>
    Every flag on the sync command.
  </Card>

  <Card title="Branches" icon="code-branch" href="/using/branches/overview" horizontal>
    Isolate secrets per environment.
  </Card>
</Columns>
