zecret 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.
- zecret-0.1.0/.github/dependabot.yml +30 -0
- zecret-0.1.0/.github/workflows/ci.yml +121 -0
- zecret-0.1.0/.github/workflows/pages.yml +60 -0
- zecret-0.1.0/.github/workflows/release.yml +180 -0
- zecret-0.1.0/.gitignore +26 -0
- zecret-0.1.0/.python-version +1 -0
- zecret-0.1.0/CHANGELOG.md +77 -0
- zecret-0.1.0/LICENSE +21 -0
- zecret-0.1.0/PKG-INFO +242 -0
- zecret-0.1.0/README.md +215 -0
- zecret-0.1.0/SECURITY.md +75 -0
- zecret-0.1.0/assets/date-dark.png +0 -0
- zecret-0.1.0/assets/date-light.png +0 -0
- zecret-0.1.0/assets/editor-dark.png +0 -0
- zecret-0.1.0/assets/editor-light.png +0 -0
- zecret-0.1.0/assets/entries-dark.png +0 -0
- zecret-0.1.0/assets/entries-light.png +0 -0
- zecret-0.1.0/assets/help-dark.png +0 -0
- zecret-0.1.0/assets/help-light.png +0 -0
- zecret-0.1.0/assets/search-dark.png +0 -0
- zecret-0.1.0/assets/search-light.png +0 -0
- zecret-0.1.0/docs/CNAME +1 -0
- zecret-0.1.0/docs/index.html +524 -0
- zecret-0.1.0/pyproject.toml +163 -0
- zecret-0.1.0/src/zecret/__init__.py +11 -0
- zecret-0.1.0/src/zecret/__main__.py +41 -0
- zecret-0.1.0/src/zecret/app.py +197 -0
- zecret-0.1.0/src/zecret/app.tcss +288 -0
- zecret-0.1.0/src/zecret/config.py +112 -0
- zecret-0.1.0/src/zecret/crypto.py +192 -0
- zecret-0.1.0/src/zecret/models.py +113 -0
- zecret-0.1.0/src/zecret/py.typed +0 -0
- zecret-0.1.0/src/zecret/screens/__init__.py +1 -0
- zecret-0.1.0/src/zecret/screens/base.py +173 -0
- zecret-0.1.0/src/zecret/screens/confirm.py +46 -0
- zecret-0.1.0/src/zecret/screens/date_prompt.py +85 -0
- zecret-0.1.0/src/zecret/screens/editor.py +162 -0
- zecret-0.1.0/src/zecret/screens/entry_list.py +423 -0
- zecret-0.1.0/src/zecret/screens/header.py +69 -0
- zecret-0.1.0/src/zecret/screens/help.py +169 -0
- zecret-0.1.0/src/zecret/screens/search.py +140 -0
- zecret-0.1.0/src/zecret/screens/settings.py +251 -0
- zecret-0.1.0/src/zecret/screens/unlock.py +147 -0
- zecret-0.1.0/src/zecret/storage.py +610 -0
- zecret-0.1.0/tests/conftest.py +58 -0
- zecret-0.1.0/tests/test_chrome.py +203 -0
- zecret-0.1.0/tests/test_config.py +160 -0
- zecret-0.1.0/tests/test_crypto.py +349 -0
- zecret-0.1.0/tests/test_date_prompt_screen.py +168 -0
- zecret-0.1.0/tests/test_docs_page.py +164 -0
- zecret-0.1.0/tests/test_editor_screen.py +497 -0
- zecret-0.1.0/tests/test_entry_list_screen.py +870 -0
- zecret-0.1.0/tests/test_help_screen.py +281 -0
- zecret-0.1.0/tests/test_locking.py +357 -0
- zecret-0.1.0/tests/test_main.py +74 -0
- zecret-0.1.0/tests/test_models.py +282 -0
- zecret-0.1.0/tests/test_properties.py +260 -0
- zecret-0.1.0/tests/test_scale.py +144 -0
- zecret-0.1.0/tests/test_search_screen.py +328 -0
- zecret-0.1.0/tests/test_settings_screen.py +493 -0
- zecret-0.1.0/tests/test_storage.py +1087 -0
- zecret-0.1.0/tests/test_unlock_screen.py +326 -0
- zecret-0.1.0/tools/screenshots.py +218 -0
- zecret-0.1.0/uv.lock +1394 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
# Keep the workflow's actions current. Security fixes to actions land
|
|
5
|
+
# here, and pinned major versions otherwise drift out of support.
|
|
6
|
+
- package-ecosystem: github-actions
|
|
7
|
+
directory: /
|
|
8
|
+
schedule:
|
|
9
|
+
interval: weekly
|
|
10
|
+
commit-message:
|
|
11
|
+
prefix: "ci"
|
|
12
|
+
|
|
13
|
+
# Keep uv.lock current. This matters more than usual for Zecret: a
|
|
14
|
+
# cryptography or argon2-cffi advisory should surface as a pull request,
|
|
15
|
+
# not wait for someone to notice.
|
|
16
|
+
- package-ecosystem: uv
|
|
17
|
+
directory: /
|
|
18
|
+
schedule:
|
|
19
|
+
interval: weekly
|
|
20
|
+
commit-message:
|
|
21
|
+
prefix: "deps"
|
|
22
|
+
groups:
|
|
23
|
+
# One PR for routine dev-tool bumps; crypto dependencies stay
|
|
24
|
+
# separate so they get looked at on their own.
|
|
25
|
+
dev-tools:
|
|
26
|
+
patterns:
|
|
27
|
+
- ruff
|
|
28
|
+
- mypy
|
|
29
|
+
- pytest*
|
|
30
|
+
- textual-dev
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# Actions are pinned to full commit SHAs, with the version in a trailing
|
|
10
|
+
# comment. Tags are mutable and not every action publishes a floating major
|
|
11
|
+
# tag, so a SHA is both the reproducible choice and the one that cannot be
|
|
12
|
+
# repointed under us. Dependabot bumps these and rewrites the comment.
|
|
13
|
+
|
|
14
|
+
# Least privilege: nothing here writes to the repository.
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
# A new push to the same branch makes the previous run obsolete.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
env:
|
|
24
|
+
# Every uv command asserts uv.lock is present and current, and fails if
|
|
25
|
+
# pyproject.toml changed without re-locking. Set here rather than as a
|
|
26
|
+
# --locked flag so it covers `uv run` too, not just `uv sync`.
|
|
27
|
+
UV_LOCKED: "1"
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
lint:
|
|
31
|
+
name: Lint and format
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
timeout-minutes: 10
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
36
|
+
|
|
37
|
+
- name: Install uv
|
|
38
|
+
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
cache-dependency-glob: uv.lock
|
|
42
|
+
|
|
43
|
+
- name: Install dependencies
|
|
44
|
+
run: uv sync
|
|
45
|
+
|
|
46
|
+
- name: Ruff lint
|
|
47
|
+
run: uv run ruff check --output-format=github .
|
|
48
|
+
|
|
49
|
+
- name: Ruff format
|
|
50
|
+
run: uv run ruff format --check --diff .
|
|
51
|
+
|
|
52
|
+
types:
|
|
53
|
+
name: Type check
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
timeout-minutes: 10
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
58
|
+
|
|
59
|
+
- name: Install uv
|
|
60
|
+
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
|
|
61
|
+
with:
|
|
62
|
+
enable-cache: true
|
|
63
|
+
cache-dependency-glob: uv.lock
|
|
64
|
+
|
|
65
|
+
- name: Install dependencies
|
|
66
|
+
run: uv sync
|
|
67
|
+
|
|
68
|
+
- name: mypy
|
|
69
|
+
run: uv run mypy
|
|
70
|
+
|
|
71
|
+
test:
|
|
72
|
+
name: Test
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
timeout-minutes: 15
|
|
75
|
+
# No version matrix. `requires-python` is a floor rather than a pin, so
|
|
76
|
+
# newer interpreters are permitted, but 3.13 is the one every push is
|
|
77
|
+
# checked against -- testing the floor is what catches a symbol that
|
|
78
|
+
# only a later version has. Everything else here agrees: mypy's
|
|
79
|
+
# python_version and ruff's target-version both name 3.13 too.
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
82
|
+
|
|
83
|
+
# Named explicitly rather than left to .python-version, so that the
|
|
84
|
+
# interpreter CI runs on is visible in this file.
|
|
85
|
+
- name: Install uv
|
|
86
|
+
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
|
|
87
|
+
with:
|
|
88
|
+
enable-cache: true
|
|
89
|
+
cache-dependency-glob: uv.lock
|
|
90
|
+
python-version: "3.13"
|
|
91
|
+
|
|
92
|
+
- name: Install dependencies
|
|
93
|
+
run: uv sync
|
|
94
|
+
|
|
95
|
+
# --cov and `-m ""` here rather than in addopts: a local
|
|
96
|
+
# `uv run pytest` stays quick, skipping coverage and the scale tests,
|
|
97
|
+
# while neither can quietly erode on main. `-m ""` overrides the
|
|
98
|
+
# `-m "not slow"` default and selects everything; the coverage
|
|
99
|
+
# threshold lives in pyproject.toml under [tool.coverage.report].
|
|
100
|
+
- name: Run tests
|
|
101
|
+
run: uv run pytest -q --cov -m ""
|
|
102
|
+
|
|
103
|
+
# Catches packaging breakage the test suite cannot: the console
|
|
104
|
+
# script is only wired up when the project is actually installed.
|
|
105
|
+
- name: Smoke-test the entry point
|
|
106
|
+
run: uv run zecret --help
|
|
107
|
+
|
|
108
|
+
# A single required check to protect the branch with, so jobs can be
|
|
109
|
+
# added or removed without editing branch protection rules.
|
|
110
|
+
ci:
|
|
111
|
+
name: CI
|
|
112
|
+
if: always()
|
|
113
|
+
needs: [lint, types, test]
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
timeout-minutes: 5
|
|
116
|
+
steps:
|
|
117
|
+
- name: Fail if any job failed or was cancelled
|
|
118
|
+
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
|
119
|
+
run: exit 1
|
|
120
|
+
- name: Success
|
|
121
|
+
run: echo "All checks passed."
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Pages
|
|
2
|
+
|
|
3
|
+
# The product page at docs/index.html. Separate from CI on purpose: it has
|
|
4
|
+
# nothing to do with whether the code is sound, and a page typo should not
|
|
5
|
+
# read as a failing build on a commit that only touched prose.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
paths:
|
|
11
|
+
- docs/**
|
|
12
|
+
- .github/workflows/pages.yml
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
# Actions are pinned to full commit SHAs, with the version in a trailing
|
|
16
|
+
# comment, matching ci.yml. Dependabot bumps these and rewrites the comment.
|
|
17
|
+
|
|
18
|
+
# Least privilege: the build only reads the repository. The deploy job asks
|
|
19
|
+
# for the two extra scopes it needs, and nothing else inherits them.
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
# One deployment at a time, and never cancel one in flight: a half-finished
|
|
24
|
+
# deploy that gets cancelled leaves the live site on the old version with
|
|
25
|
+
# nothing queued to fix it. Superseded runs simply wait their turn.
|
|
26
|
+
concurrency:
|
|
27
|
+
group: pages
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
build:
|
|
32
|
+
name: Build site
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
timeout-minutes: 10
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
|
+
|
|
38
|
+
# No actions/configure-pages step: that exists to feed a base path
|
|
39
|
+
# into a static site generator and to auto-enable Pages. This is one
|
|
40
|
+
# hand-written file with relative links, so it would only be another
|
|
41
|
+
# pinned action to keep current.
|
|
42
|
+
- name: Upload docs/ as the site
|
|
43
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
44
|
+
with:
|
|
45
|
+
path: docs
|
|
46
|
+
|
|
47
|
+
deploy:
|
|
48
|
+
name: Deploy to GitHub Pages
|
|
49
|
+
needs: build
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
timeout-minutes: 10
|
|
52
|
+
permissions:
|
|
53
|
+
pages: write
|
|
54
|
+
id-token: write
|
|
55
|
+
environment:
|
|
56
|
+
name: github-pages
|
|
57
|
+
url: ${{ steps.deploy.outputs.page_url }}
|
|
58
|
+
steps:
|
|
59
|
+
- id: deploy
|
|
60
|
+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Tags only. Pushing a `v*` tag publishes that version to PyPI and opens a
|
|
4
|
+
# GitHub release for it; nothing else triggers this. There is no manual
|
|
5
|
+
# dispatch on purpose -- a release should be a tag in the history, not a
|
|
6
|
+
# button someone pressed once.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
# Actions are pinned to full commit SHAs, with the version in a trailing
|
|
12
|
+
# comment, exactly as in ci.yml. A mutable tag on a workflow that holds a
|
|
13
|
+
# publishing identity is the one place that would matter most.
|
|
14
|
+
|
|
15
|
+
# Least privilege by default; each job widens only what it needs.
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
env:
|
|
20
|
+
UV_LOCKED: "1"
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
# The version in pyproject.toml and the tag being released have to agree.
|
|
24
|
+
# They are written in different places by different hands, and the failure
|
|
25
|
+
# is silent otherwise: PyPI would happily accept a tag naming one version
|
|
26
|
+
# while the package carries another, and the wrong number would be public
|
|
27
|
+
# and unrepublishable.
|
|
28
|
+
check:
|
|
29
|
+
name: Check the tag matches the version
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
timeout-minutes: 5
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
|
+
|
|
35
|
+
- name: Compare
|
|
36
|
+
run: |
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
39
|
+
version="$(python3 -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
40
|
+
echo "tag=$tag pyproject=$version"
|
|
41
|
+
if [ "$tag" != "$version" ]; then
|
|
42
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject version $version"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# CI runs on every push to main, but a tag can be pushed at any commit, so
|
|
47
|
+
# the checks are run again here against exactly what is about to ship.
|
|
48
|
+
verify:
|
|
49
|
+
name: Test before publishing
|
|
50
|
+
needs: [check]
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
55
|
+
|
|
56
|
+
- name: Install uv
|
|
57
|
+
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
|
|
58
|
+
with:
|
|
59
|
+
enable-cache: true
|
|
60
|
+
cache-dependency-glob: uv.lock
|
|
61
|
+
python-version: "3.13"
|
|
62
|
+
|
|
63
|
+
- name: Install dependencies
|
|
64
|
+
run: uv sync
|
|
65
|
+
|
|
66
|
+
- name: Lint, types, tests
|
|
67
|
+
run: |
|
|
68
|
+
uv run ruff check .
|
|
69
|
+
uv run ruff format --check .
|
|
70
|
+
uv run mypy
|
|
71
|
+
uv run pytest -q --cov -m ""
|
|
72
|
+
|
|
73
|
+
pypi:
|
|
74
|
+
name: Publish to PyPI
|
|
75
|
+
needs: [verify]
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
timeout-minutes: 10
|
|
78
|
+
# Trusted Publishing (OIDC): PyPI is configured to trust this repository
|
|
79
|
+
# and this workflow filename, so there is no API token anywhere in the
|
|
80
|
+
# repo or its secrets. `id-token: write` is what mints the short-lived
|
|
81
|
+
# credential; it is granted here and nowhere else.
|
|
82
|
+
permissions:
|
|
83
|
+
contents: read
|
|
84
|
+
id-token: write
|
|
85
|
+
# Named so PyPI's pending publisher can require it, and so a release can
|
|
86
|
+
# be gated behind a reviewer in the repository's environment settings.
|
|
87
|
+
environment:
|
|
88
|
+
name: pypi
|
|
89
|
+
url: https://pypi.org/p/zecret
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
92
|
+
|
|
93
|
+
- name: Install uv
|
|
94
|
+
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
|
|
95
|
+
with:
|
|
96
|
+
enable-cache: true
|
|
97
|
+
cache-dependency-glob: uv.lock
|
|
98
|
+
python-version: "3.13"
|
|
99
|
+
|
|
100
|
+
- name: Build the sdist and wheel
|
|
101
|
+
run: uv build
|
|
102
|
+
|
|
103
|
+
# What goes up is worth looking at in the log: the sdist should hold
|
|
104
|
+
# the project and not the working tree, and the wheel needs app.tcss
|
|
105
|
+
# and py.typed or the installed program is broken in ways no test here
|
|
106
|
+
# would catch.
|
|
107
|
+
- name: Show what is about to be published
|
|
108
|
+
run: |
|
|
109
|
+
set -euo pipefail
|
|
110
|
+
for f in dist/*.tar.gz; do echo "== $f"; tar tzf "$f"; done
|
|
111
|
+
for f in dist/*.whl; do echo "== $f"; unzip -l "$f"; done
|
|
112
|
+
|
|
113
|
+
- name: Publish
|
|
114
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
115
|
+
|
|
116
|
+
github-release:
|
|
117
|
+
name: Open the GitHub release
|
|
118
|
+
needs: [pypi]
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
timeout-minutes: 5
|
|
121
|
+
# The only job that writes to the repository, and all it writes is a
|
|
122
|
+
# release. Deliberately after PyPI: a release pointing at a version that
|
|
123
|
+
# failed to publish is worse than no release yet.
|
|
124
|
+
permissions:
|
|
125
|
+
contents: write
|
|
126
|
+
steps:
|
|
127
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
128
|
+
|
|
129
|
+
# A release page is read, not clicked through, so it carries the
|
|
130
|
+
# changelog section itself rather than a link to it. CHANGELOG.md stays
|
|
131
|
+
# the one place the words are written; this only lifts them out, and
|
|
132
|
+
# fails the release rather than publishing empty notes if the section
|
|
133
|
+
# for this tag is missing.
|
|
134
|
+
- name: Build the release notes
|
|
135
|
+
env:
|
|
136
|
+
TAG: ${{ github.ref_name }}
|
|
137
|
+
run: |
|
|
138
|
+
set -euo pipefail
|
|
139
|
+
version="${TAG#v}"
|
|
140
|
+
# From this version's heading to the next one, stopping short of
|
|
141
|
+
# the link definitions at the foot of the file. Matched by string
|
|
142
|
+
# rather than regex: a version is full of dots, and every one of
|
|
143
|
+
# them is a wildcard that would match a different release.
|
|
144
|
+
awk -v v="$version" '
|
|
145
|
+
index($0, "## [" v "]") == 1 { inside = 1; next }
|
|
146
|
+
inside && /^## / { exit }
|
|
147
|
+
inside && /^\[[^]]+\]: / { exit }
|
|
148
|
+
inside { print }
|
|
149
|
+
' CHANGELOG.md > notes.md
|
|
150
|
+
|
|
151
|
+
if ! grep -q '[^[:space:]]' notes.md; then
|
|
152
|
+
echo "::error::CHANGELOG.md has no section for $version"
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Relative links resolve against the releases URL rather than the
|
|
157
|
+
# repository root, so they would 404 on the release page. Point
|
|
158
|
+
# them at this tag instead.
|
|
159
|
+
sed -i "s#](\([A-Za-z][A-Za-z0-9_.-]*\.md\))#](https://github.com/${GITHUB_REPOSITORY}/blob/${TAG}/\1)#g" notes.md
|
|
160
|
+
|
|
161
|
+
{
|
|
162
|
+
echo
|
|
163
|
+
echo "---"
|
|
164
|
+
echo
|
|
165
|
+
echo "Install with \`uv tool install zecret\`, or \`pipx install zecret\` on Python 3.13 or newer."
|
|
166
|
+
} >> notes.md
|
|
167
|
+
|
|
168
|
+
echo "--- notes.md ---"
|
|
169
|
+
cat notes.md
|
|
170
|
+
|
|
171
|
+
# `gh` is preinstalled on the runner, so this needs no third-party
|
|
172
|
+
# action -- one less thing holding a token.
|
|
173
|
+
- name: Create the release
|
|
174
|
+
env:
|
|
175
|
+
GH_TOKEN: ${{ github.token }}
|
|
176
|
+
run: |
|
|
177
|
+
gh release create "$GITHUB_REF_NAME" \
|
|
178
|
+
--title "$GITHUB_REF_NAME" \
|
|
179
|
+
--notes-file notes.md \
|
|
180
|
+
--verify-tag
|
zecret-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
# Hypothesis's example database: machine-local, and regenerated on demand.
|
|
10
|
+
.hypothesis/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
# Diary files -- never commit one, encrypted or not.
|
|
14
|
+
*.enc
|
|
15
|
+
.zecret/
|
|
16
|
+
|
|
17
|
+
# uv.lock is deliberately NOT ignored: Zecret is an application, not a
|
|
18
|
+
# library, so the lockfile is committed to pin exact dependency versions.
|
|
19
|
+
# CI installs with `uv sync --locked`, which needs it present and current.
|
|
20
|
+
|
|
21
|
+
# IDE and coverage artifacts
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
.coverage
|
|
25
|
+
coverage.xml
|
|
26
|
+
htmlcov/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to Zecret, newest first. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions
|
|
5
|
+
follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
Anything that changes the on-disk format gets its own heading here, with
|
|
8
|
+
what it means for a diary written by an older version. Nothing else in this
|
|
9
|
+
file matters as much as that does.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-08-17
|
|
14
|
+
|
|
15
|
+
The first release. What follows is what Zecret does on its first day rather
|
|
16
|
+
than a list of changes from something earlier: the entries that tracked its
|
|
17
|
+
development described a program nobody outside had run, so they have been
|
|
18
|
+
folded into this one and left in the git history, where they belong.
|
|
19
|
+
|
|
20
|
+
A `0.x` version on purpose. The program is finished and tested, but no one
|
|
21
|
+
has yet kept a diary in it for a year, and the keys and the command line
|
|
22
|
+
should stay free to move until someone has. The **file format is a
|
|
23
|
+
different promise, and it is already made**: it is version 1, and a diary
|
|
24
|
+
written today will be readable by every later Zecret or migrated by one.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **A diary of days.** One entry per calendar day, and the date is the
|
|
29
|
+
entry's whole identity — no titles, no ids, nothing to name or file.
|
|
30
|
+
Opening a day means writing it or continuing what is already there. The
|
|
31
|
+
day is the local one, so something written at 23:50 belongs to the day
|
|
32
|
+
you would call it.
|
|
33
|
+
- **A master password and nothing else.** Argon2id derives the key
|
|
34
|
+
(time_cost 3, 64 MiB, 4 lanes) and every entry is sealed on its own with
|
|
35
|
+
AES-256-GCM. Editing one day never re-encrypts another. A wrong password
|
|
36
|
+
or an altered file fails loudly rather than returning nonsense — and
|
|
37
|
+
there is no recovery, by design. Nobody, including you, opens the file
|
|
38
|
+
without the password.
|
|
39
|
+
- **The diary locks itself.** After fifteen minutes without a keystroke
|
|
40
|
+
Zecret forgets the diary and the key and asks for the password again;
|
|
41
|
+
`L` does it on demand. A half-written entry holds the lock off rather
|
|
42
|
+
than being discarded by a timer, and the wait is configurable, including
|
|
43
|
+
turning it off.
|
|
44
|
+
- **Search across every entry**, filtering as you type, with no separate
|
|
45
|
+
step to submit.
|
|
46
|
+
- **An entry list that stays usable at years of writing.** Days are grouped
|
|
47
|
+
under month headings, newest first; `j`/`k` move a day, `g`/`G` and
|
|
48
|
+
`home`/`end` jump to the newest and oldest, and the page keys move a
|
|
49
|
+
screenful.
|
|
50
|
+
- **Every key documented in the app.** `?` lists all of them, generated
|
|
51
|
+
from the bindings themselves, so the popup cannot fall out of step with
|
|
52
|
+
what the program actually does. The footer shows the seven that fit an
|
|
53
|
+
80-column terminal; the rest are a keypress away.
|
|
54
|
+
- **Themes**, chosen in settings and remembered between runs in
|
|
55
|
+
`~/.zecret/config.json` — the one file Zecret writes in the clear, and it
|
|
56
|
+
holds preferences and nothing else. No entry text, no dates, nothing
|
|
57
|
+
derived from your key.
|
|
58
|
+
- **Writes that do not lose a diary.** Saves go to a temporary file in the
|
|
59
|
+
same directory, are fsynced, and replace the real one in a single
|
|
60
|
+
indivisible step. A second Zecret writing the same file is detected and
|
|
61
|
+
refused rather than silently overwritten, and `r` re-reads without asking
|
|
62
|
+
for the password again. Creating a diary claims the path in the same step
|
|
63
|
+
that writes it, so two first runs cannot race.
|
|
64
|
+
- **A diary anywhere you like.** `~/.zecret/diary.enc` by default,
|
|
65
|
+
`--path` or `ZECRET_DIARY_PATH` for somewhere else — separate diaries, or
|
|
66
|
+
a throwaway one to try it with.
|
|
67
|
+
|
|
68
|
+
### Security
|
|
69
|
+
|
|
70
|
+
- The threat model, what counts as a vulnerability, and how to report one
|
|
71
|
+
privately are written down in [SECURITY.md](SECURITY.md), along with the
|
|
72
|
+
properties that are known and accepted rather than bugs. Chief among
|
|
73
|
+
them: the file reveals *which days* have entries, though not a word of
|
|
74
|
+
what they say.
|
|
75
|
+
|
|
76
|
+
[Unreleased]: https://github.com/kfurtak1024/zecret/compare/v0.1.0...HEAD
|
|
77
|
+
[0.1.0]: https://github.com/kfurtak1024/zecret/releases/tag/v0.1.0
|
zecret-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Krzysztof Furtak
|
|
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.
|