keystash 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keystash-0.1.0/.github/workflows/ci.yml +32 -0
- keystash-0.1.0/.github/workflows/publish.yml +44 -0
- keystash-0.1.0/.gitignore +28 -0
- keystash-0.1.0/LICENSE +21 -0
- keystash-0.1.0/PKG-INFO +144 -0
- keystash-0.1.0/README.md +107 -0
- keystash-0.1.0/pyproject.toml +76 -0
- keystash-0.1.0/src/keystash/__init__.py +3 -0
- keystash-0.1.0/src/keystash/__main__.py +6 -0
- keystash-0.1.0/src/keystash/cli.py +602 -0
- keystash-0.1.0/src/keystash/clipboard.py +65 -0
- keystash-0.1.0/src/keystash/gen.py +33 -0
- keystash-0.1.0/src/keystash/model.py +81 -0
- keystash-0.1.0/src/keystash/search.py +47 -0
- keystash-0.1.0/src/keystash/vault.py +143 -0
- keystash-0.1.0/tests/test_keystash.py +320 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
exclude:
|
|
16
|
+
- os: macos-latest
|
|
17
|
+
python-version: "3.9"
|
|
18
|
+
- os: windows-latest
|
|
19
|
+
python-version: "3.9"
|
|
20
|
+
runs-on: ${{ matrix.os }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
- name: Install
|
|
27
|
+
run: pip install -e ".[dev]"
|
|
28
|
+
- name: Test
|
|
29
|
+
run: pytest -q
|
|
30
|
+
- name: Lint
|
|
31
|
+
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
|
|
32
|
+
run: ruff check src tests
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes with PyPI Trusted Publishing (OIDC) — no API token stored.
|
|
4
|
+
# One-time setup on pypi.org: Account settings → Publishing → add a
|
|
5
|
+
# "pending publisher" with:
|
|
6
|
+
# owner: thu-lawyer, repository: keystash, workflow: publish.yml,
|
|
7
|
+
# environment: pypi
|
|
8
|
+
# Then run this workflow from the Actions tab (or publish a GitHub release).
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- name: Build
|
|
24
|
+
run: |
|
|
25
|
+
pip install build
|
|
26
|
+
python -m build
|
|
27
|
+
- uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: dist
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment: pypi
|
|
36
|
+
permissions:
|
|
37
|
+
id-token: write
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/download-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
- name: Publish
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
|
|
9
|
+
# Environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Tooling
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# Editors / OS
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# NEVER commit a real vault
|
|
26
|
+
*.keystash
|
|
27
|
+
*.kstash
|
|
28
|
+
vault.enc*
|
keystash-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thu-lawyer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
keystash-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: keystash
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first encrypted vault for API keys, tokens and passwords — one file, fuzzy search, env injection, expiry tracking.
|
|
5
|
+
Project-URL: Homepage, https://github.com/thu-lawyer/keystash
|
|
6
|
+
Project-URL: Repository, https://github.com/thu-lawyer/keystash
|
|
7
|
+
Project-URL: Issues, https://github.com/thu-lawyer/keystash/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/thu-lawyer/keystash/releases
|
|
9
|
+
Author: thu-lawyer
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api-key,cli,developer-tools,password,password-manager,secrets,token,vault
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Security
|
|
27
|
+
Classifier: Topic :: Security :: Cryptography
|
|
28
|
+
Classifier: Topic :: Utilities
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Requires-Dist: cryptography>=41.0
|
|
31
|
+
Requires-Dist: rich>=13.0
|
|
32
|
+
Requires-Dist: typer>=0.12
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# keystash 🔑
|
|
39
|
+
|
|
40
|
+
**Local-first encrypted vault for API keys, tokens and passwords — one file, fuzzy search, env injection, expiry tracking.**
|
|
41
|
+
|
|
42
|
+
Your LLM API keys, cloud tokens and passwords are scattered across `.env` files, shell
|
|
43
|
+
histories and notes apps. `keystash` puts them in **one encrypted file** that you own:
|
|
44
|
+
no server, no account, no subscription. Sync that file with iCloud / Dropbox / SeaDrive /
|
|
45
|
+
Syncthing — it's ciphertext, so syncing it is safe.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install keystash
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Why keystash
|
|
52
|
+
|
|
53
|
+
| | keystash | pass / gopass | Bitwarden / 1Password | Infisical / Vault |
|
|
54
|
+
|---|---|---|---|---|
|
|
55
|
+
| Setup | `pip install` + one password | GPG key ceremony | Account + app | Self-host a server |
|
|
56
|
+
| Storage | one encrypted file you own | many GPG files | vendor cloud | server |
|
|
57
|
+
| Offline | ✅ always | ✅ | partial | ❌ |
|
|
58
|
+
| Dev workflow (env injection, `run`) | ✅ built-in | ❌ | ❌ | ✅ (heavy) |
|
|
59
|
+
| Token expiry tracking | ✅ built-in | ❌ | ❌ | enterprise |
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 1. Create your vault (encrypted with AES-128-CBC + HMAC, PBKDF2-HMAC-SHA256 600k iters)
|
|
65
|
+
keystash init
|
|
66
|
+
|
|
67
|
+
# 2. Store a secret
|
|
68
|
+
keystash add openai --secret sk-... --tags llm,prod --expires 2027-01-31 --env-var OPENAI_API_KEY
|
|
69
|
+
|
|
70
|
+
# 3. Retrieve it
|
|
71
|
+
keystash get openai # masked preview
|
|
72
|
+
keystash get openai -c # → clipboard, auto-clears after 30 s
|
|
73
|
+
keystash get openai -q # raw secret for scripts: export K=$(keystash get openai -q)
|
|
74
|
+
|
|
75
|
+
# 4. Find things (fuzzy)
|
|
76
|
+
keystash ls # everything, expiry warnings included
|
|
77
|
+
keystash ls oprod # fuzzy: matches openai-prod
|
|
78
|
+
keystash ls --tag llm --json
|
|
79
|
+
|
|
80
|
+
# 5. Inject secrets into any command — nothing touches your shell or disk
|
|
81
|
+
keystash run -n openai -n anthropic -- python train.py
|
|
82
|
+
keystash run --tag llm -- python train.py
|
|
83
|
+
eval "$(keystash env --tag llm)" # or export them explicitly
|
|
84
|
+
|
|
85
|
+
# 6. Migrate off plaintext .env files
|
|
86
|
+
keystash import .env # then delete the .env file
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Commands
|
|
90
|
+
|
|
91
|
+
| Command | Purpose |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `init` | Create the vault |
|
|
94
|
+
| `add NAME` | Store a secret (`-s` value, `-g LEN` to generate, `-t` tags, `--expires YYYY-MM-DD`, `--env-var`) |
|
|
95
|
+
| `get NAME` | Show (`-r` reveal), copy (`-c`), raw output (`-q`) |
|
|
96
|
+
| `ls [QUERY]` | List / fuzzy search (`--tag`, `--json`) |
|
|
97
|
+
| `edit NAME` | Update any field in place |
|
|
98
|
+
| `rm NAME` | Delete an entry |
|
|
99
|
+
| `gen [LEN]` | Generate a strong secret (`--save NAME` to store it) |
|
|
100
|
+
| `env NAME…` / `--tag` | Print `export` lines for shell eval |
|
|
101
|
+
| `run -n NAME… -- CMD` | Run a command with secrets injected as env vars (`--tag` selects by tag) |
|
|
102
|
+
| `import FILE` | Bulk-import `.env` or JSON |
|
|
103
|
+
| `export` | Export metadata (or secrets with `--with-secrets`) as JSON / dotenv |
|
|
104
|
+
| `status` | Vault health + expired / expiring-soon report |
|
|
105
|
+
|
|
106
|
+
## Multi-machine sync
|
|
107
|
+
|
|
108
|
+
The vault is a single encrypted file. Point `KEYSTASH_VAULT` at any synced folder:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
export KEYSTASH_VAULT="~/CloudStorage/SeaDrive/vault.json" # or iCloud, Dropbox, …
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each write re-encrypts with a fresh random salt, so last-writer-wins applies —
|
|
115
|
+
prefer one writer per vault at a time, like any sync file.
|
|
116
|
+
|
|
117
|
+
## Security model
|
|
118
|
+
|
|
119
|
+
- **Cipher:** Fernet (AES-128-CBC + HMAC-SHA256, encrypt-then-MAC) via `cryptography`.
|
|
120
|
+
- **Key derivation:** PBKDF2-HMAC-SHA256, 600 000 iterations, per-save 128-bit random salt.
|
|
121
|
+
- **File mode:** `0600`; nothing is ever written to disk in plaintext.
|
|
122
|
+
- **Clipboard:** copied secrets are auto-cleared after 30 s (best effort, detached process).
|
|
123
|
+
- **No network.** No telemetry. The CLI is fully offline.
|
|
124
|
+
- Secrets live in process memory only while a command runs; Python cannot guarantee
|
|
125
|
+
zeroization after exit — the same is true of any CLI in a GC'd language.
|
|
126
|
+
|
|
127
|
+
## Environment variables
|
|
128
|
+
|
|
129
|
+
| Variable | Purpose |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `KEYSTASH_VAULT` | Vault file path (also `--vault`) |
|
|
132
|
+
| `KEYSTASH_PASSWORD` | Master password (for scripts/CI; prefer the interactive prompt) |
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/thu-lawyer/keystash && cd keystash
|
|
138
|
+
uv pip install -e ".[dev]"
|
|
139
|
+
pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
[MIT](LICENSE)
|
keystash-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# keystash 🔑
|
|
2
|
+
|
|
3
|
+
**Local-first encrypted vault for API keys, tokens and passwords — one file, fuzzy search, env injection, expiry tracking.**
|
|
4
|
+
|
|
5
|
+
Your LLM API keys, cloud tokens and passwords are scattered across `.env` files, shell
|
|
6
|
+
histories and notes apps. `keystash` puts them in **one encrypted file** that you own:
|
|
7
|
+
no server, no account, no subscription. Sync that file with iCloud / Dropbox / SeaDrive /
|
|
8
|
+
Syncthing — it's ciphertext, so syncing it is safe.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install keystash
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Why keystash
|
|
15
|
+
|
|
16
|
+
| | keystash | pass / gopass | Bitwarden / 1Password | Infisical / Vault |
|
|
17
|
+
|---|---|---|---|---|
|
|
18
|
+
| Setup | `pip install` + one password | GPG key ceremony | Account + app | Self-host a server |
|
|
19
|
+
| Storage | one encrypted file you own | many GPG files | vendor cloud | server |
|
|
20
|
+
| Offline | ✅ always | ✅ | partial | ❌ |
|
|
21
|
+
| Dev workflow (env injection, `run`) | ✅ built-in | ❌ | ❌ | ✅ (heavy) |
|
|
22
|
+
| Token expiry tracking | ✅ built-in | ❌ | ❌ | enterprise |
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Create your vault (encrypted with AES-128-CBC + HMAC, PBKDF2-HMAC-SHA256 600k iters)
|
|
28
|
+
keystash init
|
|
29
|
+
|
|
30
|
+
# 2. Store a secret
|
|
31
|
+
keystash add openai --secret sk-... --tags llm,prod --expires 2027-01-31 --env-var OPENAI_API_KEY
|
|
32
|
+
|
|
33
|
+
# 3. Retrieve it
|
|
34
|
+
keystash get openai # masked preview
|
|
35
|
+
keystash get openai -c # → clipboard, auto-clears after 30 s
|
|
36
|
+
keystash get openai -q # raw secret for scripts: export K=$(keystash get openai -q)
|
|
37
|
+
|
|
38
|
+
# 4. Find things (fuzzy)
|
|
39
|
+
keystash ls # everything, expiry warnings included
|
|
40
|
+
keystash ls oprod # fuzzy: matches openai-prod
|
|
41
|
+
keystash ls --tag llm --json
|
|
42
|
+
|
|
43
|
+
# 5. Inject secrets into any command — nothing touches your shell or disk
|
|
44
|
+
keystash run -n openai -n anthropic -- python train.py
|
|
45
|
+
keystash run --tag llm -- python train.py
|
|
46
|
+
eval "$(keystash env --tag llm)" # or export them explicitly
|
|
47
|
+
|
|
48
|
+
# 6. Migrate off plaintext .env files
|
|
49
|
+
keystash import .env # then delete the .env file
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
| Command | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `init` | Create the vault |
|
|
57
|
+
| `add NAME` | Store a secret (`-s` value, `-g LEN` to generate, `-t` tags, `--expires YYYY-MM-DD`, `--env-var`) |
|
|
58
|
+
| `get NAME` | Show (`-r` reveal), copy (`-c`), raw output (`-q`) |
|
|
59
|
+
| `ls [QUERY]` | List / fuzzy search (`--tag`, `--json`) |
|
|
60
|
+
| `edit NAME` | Update any field in place |
|
|
61
|
+
| `rm NAME` | Delete an entry |
|
|
62
|
+
| `gen [LEN]` | Generate a strong secret (`--save NAME` to store it) |
|
|
63
|
+
| `env NAME…` / `--tag` | Print `export` lines for shell eval |
|
|
64
|
+
| `run -n NAME… -- CMD` | Run a command with secrets injected as env vars (`--tag` selects by tag) |
|
|
65
|
+
| `import FILE` | Bulk-import `.env` or JSON |
|
|
66
|
+
| `export` | Export metadata (or secrets with `--with-secrets`) as JSON / dotenv |
|
|
67
|
+
| `status` | Vault health + expired / expiring-soon report |
|
|
68
|
+
|
|
69
|
+
## Multi-machine sync
|
|
70
|
+
|
|
71
|
+
The vault is a single encrypted file. Point `KEYSTASH_VAULT` at any synced folder:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
export KEYSTASH_VAULT="~/CloudStorage/SeaDrive/vault.json" # or iCloud, Dropbox, …
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Each write re-encrypts with a fresh random salt, so last-writer-wins applies —
|
|
78
|
+
prefer one writer per vault at a time, like any sync file.
|
|
79
|
+
|
|
80
|
+
## Security model
|
|
81
|
+
|
|
82
|
+
- **Cipher:** Fernet (AES-128-CBC + HMAC-SHA256, encrypt-then-MAC) via `cryptography`.
|
|
83
|
+
- **Key derivation:** PBKDF2-HMAC-SHA256, 600 000 iterations, per-save 128-bit random salt.
|
|
84
|
+
- **File mode:** `0600`; nothing is ever written to disk in plaintext.
|
|
85
|
+
- **Clipboard:** copied secrets are auto-cleared after 30 s (best effort, detached process).
|
|
86
|
+
- **No network.** No telemetry. The CLI is fully offline.
|
|
87
|
+
- Secrets live in process memory only while a command runs; Python cannot guarantee
|
|
88
|
+
zeroization after exit — the same is true of any CLI in a GC'd language.
|
|
89
|
+
|
|
90
|
+
## Environment variables
|
|
91
|
+
|
|
92
|
+
| Variable | Purpose |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `KEYSTASH_VAULT` | Vault file path (also `--vault`) |
|
|
95
|
+
| `KEYSTASH_PASSWORD` | Master password (for scripts/CI; prefer the interactive prompt) |
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git clone https://github.com/thu-lawyer/keystash && cd keystash
|
|
101
|
+
uv pip install -e ".[dev]"
|
|
102
|
+
pytest
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "keystash"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local-first encrypted vault for API keys, tokens and passwords — one file, fuzzy search, env injection, expiry tracking."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "thu-lawyer" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"password",
|
|
15
|
+
"password-manager",
|
|
16
|
+
"api-key",
|
|
17
|
+
"secrets",
|
|
18
|
+
"vault",
|
|
19
|
+
"cli",
|
|
20
|
+
"token",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Environment :: Console",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Operating System :: MacOS",
|
|
29
|
+
"Operating System :: POSIX :: Linux",
|
|
30
|
+
"Operating System :: Microsoft :: Windows",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.9",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Topic :: Security",
|
|
38
|
+
"Topic :: Security :: Cryptography",
|
|
39
|
+
"Topic :: Utilities",
|
|
40
|
+
]
|
|
41
|
+
dependencies = [
|
|
42
|
+
"typer>=0.12",
|
|
43
|
+
"rich>=13.0",
|
|
44
|
+
"cryptography>=41.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/thu-lawyer/keystash"
|
|
49
|
+
Repository = "https://github.com/thu-lawyer/keystash"
|
|
50
|
+
Issues = "https://github.com/thu-lawyer/keystash/issues"
|
|
51
|
+
Changelog = "https://github.com/thu-lawyer/keystash/releases"
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
keystash = "keystash.cli:app"
|
|
55
|
+
|
|
56
|
+
[project.optional-dependencies]
|
|
57
|
+
dev = ["pytest>=8.0", "ruff>=0.4"]
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/keystash"]
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
target-version = "py39"
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
# Keep Optional[...]/List[...] annotations: typer evaluates them at runtime,
|
|
71
|
+
# where `X | None` would fail on Python 3.9 — so the UP rules must stay off.
|
|
72
|
+
select = ["E4", "E7", "E9", "F", "I", "B", "DTZ"]
|
|
73
|
+
ignore = [
|
|
74
|
+
"B008", # typer's Option()/Argument() in defaults is the framework idiom
|
|
75
|
+
"DTZ011", # local-date semantics are intentional for expiry display
|
|
76
|
+
]
|