Metadata-Version: 2.4
Name: passsh
Version: 0.1.0
Summary: Local encrypted password manager with a CLI and terminal UI
License: MIT License
        
        Copyright (c) 2026 Nish
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/nishcola/pass.sh
Project-URL: Repository, https://github.com/nishcola/pass.sh.git
Project-URL: Issues, https://github.com/nishcola/pass.sh/issues
Keywords: password-manager,cli,tui,encryption
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=44.0
Requires-Dist: pyperclip>=1.8
Requires-Dist: click>=8.1
Requires-Dist: textual>=0.58
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Dynamic: license-file

# pass.sh

`pass.sh` is a local password manager with a command-line interface and a
terminal UI. It stores logins in one encrypted vault file and does not require
an account or a hosted service.

The master password is used to derive an encryption key with Argon2id. Neither
the password nor the derived key is written to disk. A short-lived background
process can cache the key in memory so nearby commands do not need another
password prompt.

## What it includes

- AES-256-GCM authenticated encryption with a fresh nonce for every vault write
- Argon2id key derivation with a 64 MiB memory cost, 3 iterations, and 4 lanes
- Authentication of the vault version and KDF settings to detect header changes
- A Click-based CLI for creating, reading, updating, importing, and exporting entries
- A Textual terminal UI with search, add, edit, copy, open, delete, and lock actions
- Clipboard copying that clears only if the copied password is still present
- Five-minute session caching over an owner-only Unix domain socket
- Local retry delays after failed unlock attempts
- Atomic vault writes through a temporary file and same-filesystem rename

## Requirements

- Python 3.10 or later
- macOS or Linux for cached sessions
- A clipboard provider supported by `pyperclip`

The CLI and vault operations also work on Windows, but the Unix socket session
agent is disabled there. Each command will ask for the master password.

## Gallery
<img width="885" height="418" alt="SCR-20260815-kdvn" src="https://github.com/user-attachments/assets/06e6a26d-b142-43bf-9d25-9444b5ff4323" />
<img width="884" height="453" alt="SCR-20260815-keuw" src="https://github.com/user-attachments/assets/000a1cd6-9ef0-43f3-8223-ae474277de09" />
<img width="870" height="432" alt="SCR-20260815-kelu" src="https://github.com/user-attachments/assets/4bbc3c34-f197-456f-954b-d607d9368458" />
<img width="879" height="437" alt="SCR-20260815-kexj" src="https://github.com/user-attachments/assets/73f40541-4e8d-4709-94ef-2aa48e2c4ada" />

## Install

```sh
git clone https://github.com/nishcola/pass.sh.git
cd pass.sh
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
```

This installs the `pm` command and the dependencies declared in
`pyproject.toml`: `cryptography`, `pyperclip`, `click`, and `textual`.

On Linux, `pyperclip` needs a supported clipboard tool. Install one if copy
commands report that the clipboard is unavailable:

```sh
sudo apt install xclip
# or
sudo apt install xsel
```

## Quick start

Create the default vault at `~/.passsh/vault.json`:

```console
$ pm init
Master password:
Confirm password:
Vault created at /home/you/.passsh/vault.json
```

Add and retrieve an entry:

```console
$ pm add github --username alice --url https://github.com
Master password:
Password for 'github':
Confirm password:
Added 'github'.

$ pm get github
Name:     github
Username: alice
URL:      https://github.com
Password copied to clipboard (clears in 15s).
```

Launch the terminal UI:

```sh
pm tui
```

Every vault command accepts `--vault PATH` for a non-default location:

```sh
pm init --vault ./work-vault.json
pm list --vault ./work-vault.json
```

## CLI reference

| Command | Purpose |
| --- | --- |
| `pm init` | Create an empty encrypted vault |
| `pm add NAME` | Add an entry with optional username, URL, and notes |
| `pm get NAME` | Show entry metadata and copy its password |
| `pm list` | List entry names and usernames |
| `pm update NAME` | Change selected fields on an entry |
| `pm rename OLD NEW` | Rename an entry |
| `pm delete NAME` | Delete an entry after confirmation |
| `pm open NAME` | Open the saved URL in the default browser |
| `pm generate` | Generate a password with `secrets.choice` |
| `pm passwd` | Re-encrypt the vault under a new master password |
| `pm export` | Export every entry as plaintext JSON |
| `pm import FILE` | Merge entries from an exported JSON file |
| `pm lock` | End the cached session immediately |
| `pm tui` | Open the terminal UI |

Run `pm COMMAND --help` for the full option list. Common options include:

- `pm add --username TEXT --url URL --notes TEXT`
- `pm get --no-copy` to print a password instead of copying it
- `pm get --clear-delay SECONDS` to change the clipboard timer
- `pm update --username TEXT --url URL --notes TEXT --password`
- `pm generate --length N --no-symbols --exclude-ambiguous`
- `pm delete --yes` to skip the confirmation prompt
- `pm import --force` to replace entries with matching names

`pm init --force` can replace an existing vault after confirmation. This
deletes every entry in that vault.

## Terminal UI controls

The terminal UI displays service names, usernames, and update times. Passwords
remain hidden until copied or explicitly revealed in the entry form.

| Key | Action |
| --- | --- |
| `a` | Add an entry |
| `Enter` | Edit the selected entry |
| `c` | Copy the selected password |
| `o` | Open the selected URL |
| `d` | Delete the selected entry |
| `/` | Focus search |
| `Esc` | Return focus to the entry list or close a form |
| `j` / `k` | Move down or up |
| `l` | Lock the session |
| `q` | Quit |

The add and edit forms can generate a 20-character password and reveal it
before saving.

## How the vault works

The vault is a JSON document with three relevant parts:

1. Version and Argon2id settings, including a random salt
2. An encryption label for AES-256-GCM
3. Base64-encoded ciphertext containing the entry map

The version and KDF settings are passed to AES-GCM as authenticated data.
Changing those fields causes decryption to fail instead of silently applying
weaker settings. Each save encrypts the complete entry map with a new random
nonce, writes the encrypted document to a temporary file, flushes it, and
replaces the previous vault with an atomic rename.

The first successful unlock starts a detached session agent on macOS and
Linux. Later commands request the cached key through a Unix domain socket.
The socket directory uses mode `0700`, and the socket uses mode `0600`. The
agent drops the cached key and exits after five minutes without a request.
It also attempts to lock the key's memory pages with `mlock` when the platform
and process limits allow it.

## Security boundaries

`pass.sh` is designed to protect a vault file at rest and to reject modified
ciphertext or authenticated header fields. Its safeguards have narrower
limits at runtime:

- A process running as the same operating-system user can access the session
  socket and may be able to inspect process memory.
- Memory locking is best effort. The operating system can reject `mlock`.
- Clipboard contents are available to other applications until the timer
  clears them. The clear worker leaves newer clipboard content untouched.
- The retry delay is a local guard against repeated attempts through the app.
  An attacker with a copied vault can remove its state file and perform an
  offline password-guessing attack.
- `pm export` writes plaintext passwords. Export files need separate
  protection and secure deletion when no longer needed.
- `pm open` sends a saved URL to the default browser. No password is included.

Use a long, unique master password and keep backups of the encrypted vault.

## Code structure

| Path | Responsibility |
| --- | --- |
| `src/passsh/cli.py` | Click commands and command-line output |
| `src/passsh/tui.py` | Textual screens, forms, search, and keyboard actions |
| `src/passsh/session.py` | Shared unlock policy for the CLI and TUI |
| `src/passsh/agent.py` | In-memory key cache and local socket protocol |
| `src/passsh/storage.py` | Vault serialization, encryption calls, and atomic writes |
| `src/passsh/crypto.py` | Argon2id and AES-GCM primitives |
| `src/passsh/clipboard.py` | Copy and compare-before-clear worker |
| `src/passsh/ratelimit.py` | Failed-attempt state and retry delays |
| `src/passsh/entry_ops.py` | Shared entry creation and update behavior |
| `src/passsh/generator.py` | Password generation |
| `tests/` | Unit, CLI, socket integration, and Textual pilot tests |

Both interfaces call the same session, storage, clipboard, and entry helper
modules. This keeps encryption and persistence behavior out of the
presentation layers.

## Development

Install the development dependencies and run the test suite:

```sh
python -m pip install -e ".[dev]"
pytest -q
```

The tests use temporary vaults and replace clipboard and browser integrations
with fakes. They cover cryptographic round trips, tamper rejection, vault
storage and permissions, CLI workflows, session-agent sockets, retry delays,
and TUI interactions.

## License

Released under the [MIT License](LICENSE).
