# pm — Password Manager CLI

A secure, local password manager for developers. Runs entirely on your laptop — no cloud, no sync service, no account required.

**Python 3.10 · 3.11 · 3.12 · 3.13**

---

## Features

- 4 entry types: **Login**, **Database**, **API Credential**, **Secure Note**
- AES-256-GCM encryption — every secret encrypted individually at rest
- Folder organisation with typo-safe folder creation
- Interactive wizard — no flags to memorise
- **Session caching** — `pm unlock` once, skip the password prompt for 15 minutes
- **TOTP / 2FA** — store authenticator secrets, live 6-digit codes in `pm get`
- Camera-safe display — `c` copy · `t` copy TOTP · `s` reveal · clipboard auto-clears in 45s
- **Password history** — last 5 rotations archived per entry (`pm history <name>`)
- **Vault health report** — detects reused/weak passwords, missing 2FA, never-rotated credentials
- **Import** from Chrome CSV, Bitwarden CSV, and 1Password 1PUX
- Custom fields — attach arbitrary key-value pairs to any entry
- Shell tab completion — `pm get git<TAB>` completes to entry names
- `--output table | json | raw` for scripting and piping
- Encrypted export/restore for laptop migration

---

## Requirements

- Python 3.10+
- macOS / Linux / Windows

---

## Installation

```bash
git clone <repo>
cd password-manager
pip install -e .
```

Verify:

```bash
pm --version
```

---

## Quick Start

```bash
pm init                    # create vault (~/.pm/vault.db)
pm unlock                  # cache session (skip password for 15 min)
pm create                  # interactive wizard
pm list                    # list all entries
pm get github              # view entry
pm status                  # vault summary + stale report
pm status --health         # full security audit
```

---

## Commands

### `pm init`
Initialise a new vault. Run once. Vault is stored at `~/.pm/vault.db`.

```bash
pm init
```

---

### `pm unlock` / `pm lock`
Cache the master password derivation so subsequent commands skip the prompt.

```bash
pm unlock                  # cache for 15 minutes (default)
pm unlock --timeout 60     # cache for 1 hour
pm lock                    # clear the session immediately
```

Session is stored in the OS keychain (macOS Keychain / Linux SecretService). Each command that prompts for a password also starts a session automatically.

---

### `pm create`
Interactive wizard. Prompts for entry type then relevant fields.

```
pm create

→ Type [login/database/api/note]: database
→ Name: prod-postgres
→ Kind [mysql/postgres/redis/mongodb/sqlite/other]: postgres
→ Host: db.example.com
→ Port [5432]:
→ Database: myapp
→ Username: admin
→ Generate Password? [y/n]: y  →  Generated: Xk9#...
→ Notes:
→ Folder [personal]: company-a
  Folder 'company-a' doesn't exist. Existing folders: personal
  Create new folder 'company-a'? [y/n]: y
✓ prod-postgres (Database) saved in folder company-a.
```

**Entry types and their fields:**

| Type | Fields |
|------|--------|
| `login` | username, password, website, TOTP secret, custom fields, notes |
| `database` | kind, host, port, database, username, password, notes |
| `api` | service, api key, api secret, endpoint, environment, notes |
| `note` | title, content |

Login entries support optional **TOTP secrets** and **custom fields** (e.g. account ID, recovery email).

---

### `pm get <name>`
View an entry. Secrets are masked by default.

```bash
pm get github              # masked
pm get github --show       # reveal secrets
pm get github --copy       # copy primary secret to clipboard silently
```

**Camera-safe interactive mode** — after displaying the masked entry:

```
  ↳ c copy password  t copy totp  s reveal  q/any key to quit
```

- `c` — copies password/api key silently; **auto-clears clipboard in 45s**
- `t` — copies the live TOTP code (only shown when entry has a 2FA secret)
- `s` — reveals masked fields inline + warns to clear terminal
- `q` / any other key — dismisses

If the entry has markdown notes or long notes, they are rendered below the table with syntax highlighting.

---

### `pm edit <name>`
Interactive wizard pre-filled with current values.

```bash
pm edit github                    # full interactive edit
pm edit github --folder company-a # move to different folder only
```

---

### `pm rename <old> <new>`
Rename an entry without recreating it.

```bash
pm rename github github-personal
```

---

### `pm duplicate <source> <new-name>`
Clone an entry — copies all fields, metadata, TOTP secret, and custom fields.

```bash
pm duplicate prod-postgres staging-postgres
```

---

### `pm history <name>`
Show the last 5 password rotations for an entry.

```bash
pm history github              # masked
pm history github --show       # reveal previous passwords
```

History is recorded automatically on every `pm edit` that changes the password.

---

### `pm list`
List all entries grouped by folder.

```bash
pm list                      # all entries
pm list --folder company-a   # one folder
pm list --folders            # folder summary with counts
pm list --search postgres    # search across all folders
```

---

### `pm find <query>`
Deep search across all fields including encrypted metadata (host, database name, service, environment, custom fields, etc.).

```bash
pm find postgres             # matches name, host, or database name
pm find stripe               # matches service field in api entries
pm find production           # matches environment in api entries
pm find company-a            # matches folder name
```

---

### `pm delete <name>`

```bash
pm delete github             # prompts for confirmation
pm delete github --yes       # skip confirmation
```

---

### `pm status`
Vault summary and stale credential report.

```bash
pm status                    # default 90-day threshold
pm status --days 30          # stricter threshold
pm status --health           # full health audit
```

`--health` adds:
- **Reused passwords** — entries that share the same password
- **Weak passwords** — too short or no digits/symbols
- **Never rotated** — password has never been changed since creation
- **Missing 2FA** — login entries without a TOTP secret

---

### `pm import`
Import passwords from external password managers.

```bash
# Chrome / Edge CSV export
pm import ~/chrome-passwords.csv --format chrome

# Bitwarden unencrypted CSV export
pm import ~/bitwarden-export.csv --format bitwarden

# 1Password 1PUX export
pm import ~/1password-export.1pux --format 1password

# Override destination folder for all imported entries
pm import ~/bitwarden-export.csv --folder work
```

Format is auto-detected from the file extension and header when `--format` is omitted. A preview table is shown before import is confirmed.

---

### `pm export` / `pm restore`
Portable backup for laptop migration.

```bash
# Export (encrypted — safe to store in cloud/USB)
pm export backup.enc

# Export plain JSON (keep secure — contains raw secrets)
pm export backup.json --format json

# Restore on new laptop
pm init
pm restore backup.enc
```

---

### `pm open <name>`
Open the saved URL in the default browser.

```bash
pm open github
```

---

### `pm generate`
Generate a password without saving it.

```bash
pm generate                  # 20 chars, letters + digits + symbols
pm generate --length 32
pm generate --no-symbols
pm generate --count 5        # generate 5 options
```

---

### `pm completion`
Enable shell tab completion for entry names (`pm get git<TAB>`).

```bash
# Zsh
pm completion --shell zsh
# → follow the printed instructions to add to ~/.zshrc

# Bash
pm completion --shell bash

# Fish
pm completion --shell fish
```

Entry names complete when a vault session is active (`pm unlock` first).

---

## Output Formats

All read commands (`get`, `list`, `find`, `status`) support `--output` / `-o`:

| Format | Description |
|--------|-------------|
| `table` | Rich terminal display (default) |
| `json` | Full JSON — secrets masked unless `--show` |
| `raw` | Primary secret only, suitable for piping |

```bash
pm get github -o raw                            # password only
pm get prod-postgres -o raw                     # postgresql://user:pass@host/db
pm get stripe-prod -o raw                       # api key only

pm get github -o json --show                    # full JSON with plaintext secrets
pm list -o json | jq '.[].name'                 # extract names
pm status --days 30 -o json | jq '.stale[].name'
pm find production -o raw                       # one entry name per line
```

---

## Folders

Entries are grouped by folder (`personal` by default). Folders are created implicitly — no setup needed. A confirmation prompt protects against typos when a new folder name is used.

```bash
pm create                    # wizard asks for folder
pm edit github --folder work # move entry to a different folder
pm list --folders            # see all folders
```

---

## TOTP / 2FA

Store an authenticator secret alongside any login entry. The live 6-digit code appears automatically in `pm get` and can be copied with `t`.

```
 Name     github
 Password ••••••••
 TOTP     847291  (12s)
  ↳ c copy password  t copy totp  s reveal  q/any key to quit
```

Add or update via `pm create` or `pm edit` — the wizard prompts for a TOTP secret (base32 format, e.g. from a QR code scanner).

---

## Custom Fields

Attach arbitrary key-value pairs to any entry during `pm create` or `pm edit`:

```
Add/edit custom fields? [y/n]: y
Custom field (label=value): Account ID=GH-123456
Custom field (label=value): Recovery email=backup@email.com
Custom field (label=value):          ← empty to finish
```

Custom fields are encrypted at rest alongside other metadata.

---

## Laptop Migration

```bash
# On old laptop
pm export ~/backup.enc       # prompts for a backup password

# Copy backup.enc to new laptop (USB, AirDrop, encrypted cloud, etc.)

# On new laptop
pip install -e .
pm init
pm restore ~/backup.enc      # prompts for the backup password
```

The backup password is independent of your master password.

---

## Security

| Property | Detail |
|----------|--------|
| Encryption | AES-256-GCM per field |
| Key derivation | PBKDF2-HMAC-SHA256, 600 000 iterations |
| Master password | Never stored — derived key verified via token |
| Storage | SQLite at `~/.pm/vault.db` |
| Session cache | Derived key stored in OS keychain (macOS Keychain / Linux SecretService) |
| Clipboard | Auto-cleared after 45s; only cleared if clipboard still holds the copied value |
| Export | Re-encrypted with a separate backup password + fresh salt |

Override vault location: `export PM_VAULT_PATH=/path/to/vault.db`
Skip password prompt in scripts: `export PM_MASTER_PASSWORD=...`

---

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `PM_VAULT_PATH` | `~/.pm/vault.db` | Path to vault file |
| `PM_MASTER_PASSWORD` | — | Skip interactive password prompt |
