quotalens 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.
- quotalens-1.0/.github/ISSUE_TEMPLATE/bug_report.md +54 -0
- quotalens-1.0/.github/ISSUE_TEMPLATE/config.yml +7 -0
- quotalens-1.0/.github/ISSUE_TEMPLATE/endpoint_drift.md +38 -0
- quotalens-1.0/.github/workflows/ci.yml +136 -0
- quotalens-1.0/.github/workflows/release.yml +125 -0
- quotalens-1.0/.gitignore +46 -0
- quotalens-1.0/LICENSE +21 -0
- quotalens-1.0/PKG-INFO +582 -0
- quotalens-1.0/PLAN.md +181 -0
- quotalens-1.0/README.md +550 -0
- quotalens-1.0/SECURITY.md +52 -0
- quotalens-1.0/VISION.md +102 -0
- quotalens-1.0/design/DESIGN.md +482 -0
- quotalens-1.0/design/DIRECTIONS.html +574 -0
- quotalens-1.0/design/MARKS.html +367 -0
- quotalens-1.0/design/RING.html +310 -0
- quotalens-1.0/design/favicon.svg +4 -0
- quotalens-1.0/design/logo.svg +5 -0
- quotalens-1.0/design/mark.svg +4 -0
- quotalens-1.0/design/marks/01-ring.svg +4 -0
- quotalens-1.0/design/marks/02-tank.svg +4 -0
- quotalens-1.0/design/marks/03-bar.svg +4 -0
- quotalens-1.0/design/marks/04-break.svg +4 -0
- quotalens-1.0/design/marks/05-columns.svg +4 -0
- quotalens-1.0/design/marks/06-gauge.svg +4 -0
- quotalens-1.0/design/marks/07-dial.svg +4 -0
- quotalens-1.0/design/marks/08-glass.svg +4 -0
- quotalens-1.0/design/marks/09-brackets.svg +4 -0
- quotalens-1.0/design/marks/10-q.svg +4 -0
- quotalens-1.0/design/marks/11-pair.svg +4 -0
- quotalens-1.0/design/marks/12-battery.svg +4 -0
- quotalens-1.0/design/marks/boost-rocket.svg +19 -0
- quotalens-1.0/design/marks/ring/01-track-favicon-16.svg +5 -0
- quotalens-1.0/design/marks/ring/01-track.svg +4 -0
- quotalens-1.0/design/marks/ring/02-open.svg +4 -0
- quotalens-1.0/design/marks/ring/03-bead.svg +4 -0
- quotalens-1.0/design/marks/ring/04-dual.svg +4 -0
- quotalens-1.0/design/og-image.png +0 -0
- quotalens-1.0/design/preview.html +310 -0
- quotalens-1.0/design/render_gear.py +84 -0
- quotalens-1.0/design/render_mark_png.py +108 -0
- quotalens-1.0/design/tokens.css +136 -0
- quotalens-1.0/docs/FEATURE-REVIEW.md +856 -0
- quotalens-1.0/docs/MVP-SCOPE.md +158 -0
- quotalens-1.0/docs/QA.md +550 -0
- quotalens-1.0/docs/RELEASE-CHECKLIST.md +149 -0
- quotalens-1.0/docs/RELEASE-NOTES-v1.0.md +119 -0
- quotalens-1.0/docs/V0.1.0-PLAN.md +217 -0
- quotalens-1.0/pyproject.toml +67 -0
- quotalens-1.0/qa/fake_claude.py +136 -0
- quotalens-1.0/qa/smoke.py +266 -0
- quotalens-1.0/src/quotalens/__init__.py +3 -0
- quotalens-1.0/src/quotalens/__main__.py +7 -0
- quotalens-1.0/src/quotalens/alerts.py +122 -0
- quotalens-1.0/src/quotalens/api.py +616 -0
- quotalens-1.0/src/quotalens/boost.py +209 -0
- quotalens-1.0/src/quotalens/budget.py +277 -0
- quotalens-1.0/src/quotalens/burn.py +158 -0
- quotalens-1.0/src/quotalens/cli.py +846 -0
- quotalens-1.0/src/quotalens/client.py +352 -0
- quotalens-1.0/src/quotalens/config.py +505 -0
- quotalens-1.0/src/quotalens/credits.py +185 -0
- quotalens-1.0/src/quotalens/dashboard.py +1660 -0
- quotalens-1.0/src/quotalens/export.py +162 -0
- quotalens-1.0/src/quotalens/metrics.py +227 -0
- quotalens-1.0/src/quotalens/notify.py +411 -0
- quotalens-1.0/src/quotalens/origin_guard.py +112 -0
- quotalens-1.0/src/quotalens/parse.py +480 -0
- quotalens-1.0/src/quotalens/poller.py +735 -0
- quotalens-1.0/src/quotalens/render.py +1463 -0
- quotalens-1.0/src/quotalens/retention.py +219 -0
- quotalens-1.0/src/quotalens/runway.py +177 -0
- quotalens-1.0/src/quotalens/secrets.py +217 -0
- quotalens-1.0/src/quotalens/service.py +787 -0
- quotalens-1.0/src/quotalens/sessions.py +320 -0
- quotalens-1.0/src/quotalens/settings_view.py +223 -0
- quotalens-1.0/src/quotalens/state.py +162 -0
- quotalens-1.0/src/quotalens/status.py +290 -0
- quotalens-1.0/src/quotalens/store.py +677 -0
- quotalens-1.0/src/quotalens/views.py +170 -0
- quotalens-1.0/src/quotalens/web/app.css +397 -0
- quotalens-1.0/src/quotalens/web/app.js +278 -0
- quotalens-1.0/src/quotalens/web/boost-rocket.svg +19 -0
- quotalens-1.0/src/quotalens/web/chart.js +206 -0
- quotalens-1.0/src/quotalens/web/favicon.svg +4 -0
- quotalens-1.0/src/quotalens/web/mark.png +0 -0
- quotalens-1.0/src/quotalens/web/tokens.css +136 -0
- quotalens-1.0/src/quotalens/web/vendor/README.md +27 -0
- quotalens-1.0/src/quotalens/web/vendor/claude.svg +1 -0
- quotalens-1.0/src/quotalens/web/vendor/openai.svg +1 -0
- quotalens-1.0/tests/conftest.py +266 -0
- quotalens-1.0/tests/test_alerts.py +381 -0
- quotalens-1.0/tests/test_api.py +120 -0
- quotalens-1.0/tests/test_boost.py +343 -0
- quotalens-1.0/tests/test_boost_mark.py +391 -0
- quotalens-1.0/tests/test_budget.py +363 -0
- quotalens-1.0/tests/test_burn.py +137 -0
- quotalens-1.0/tests/test_cli.py +217 -0
- quotalens-1.0/tests/test_client.py +221 -0
- quotalens-1.0/tests/test_config_store.py +169 -0
- quotalens-1.0/tests/test_controls.py +251 -0
- quotalens-1.0/tests/test_credits.py +100 -0
- quotalens-1.0/tests/test_dashboard.py +698 -0
- quotalens-1.0/tests/test_export.py +154 -0
- quotalens-1.0/tests/test_favicon_render.py +158 -0
- quotalens-1.0/tests/test_lapsed_window.py +296 -0
- quotalens-1.0/tests/test_mark.py +227 -0
- quotalens-1.0/tests/test_metrics.py +374 -0
- quotalens-1.0/tests/test_notify.py +332 -0
- quotalens-1.0/tests/test_origin_guard.py +163 -0
- quotalens-1.0/tests/test_parse.py +334 -0
- quotalens-1.0/tests/test_poller.py +308 -0
- quotalens-1.0/tests/test_profiles.py +165 -0
- quotalens-1.0/tests/test_prompt5.py +337 -0
- quotalens-1.0/tests/test_readout_colour.py +178 -0
- quotalens-1.0/tests/test_redaction.py +139 -0
- quotalens-1.0/tests/test_rendered_type.py +154 -0
- quotalens-1.0/tests/test_retention.py +151 -0
- quotalens-1.0/tests/test_runway.py +102 -0
- quotalens-1.0/tests/test_service.py +422 -0
- quotalens-1.0/tests/test_sessions.py +450 -0
- quotalens-1.0/tests/test_status_and_settings.py +806 -0
- quotalens-1.0/tests/test_store.py +118 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something QuotaLens did that it should not have
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Before you paste anything
|
|
8
|
+
|
|
9
|
+
**`quotalens probe` output is your account's data.** It never contains your
|
|
10
|
+
session cookie, and UUID-shaped values are masked by default — but only by
|
|
11
|
+
default. Do not paste output from `probe --no-redact` or from
|
|
12
|
+
`/api/export.json?table=samples&raw=1`; both are unmasked on purpose.
|
|
13
|
+
|
|
14
|
+
If you need to show a payload, `quotalens probe` (no flags) is the safe form.
|
|
15
|
+
If you are unsure whether something identifies you, leave it out and say so; a
|
|
16
|
+
missing field is easier to ask for than a leaked one is to take back.
|
|
17
|
+
|
|
18
|
+
Never paste your cookie. Nobody debugging this will ever need it. If you think
|
|
19
|
+
you have pasted one anywhere, sign out of claude.ai on all devices, which
|
|
20
|
+
invalidates it.
|
|
21
|
+
|
|
22
|
+
## What happened
|
|
23
|
+
|
|
24
|
+
<!-- What you saw, and what you expected instead. -->
|
|
25
|
+
|
|
26
|
+
## How to reproduce
|
|
27
|
+
|
|
28
|
+
<!-- The exact commands, and the URL if it was the dashboard. Every view is a
|
|
29
|
+
URL, so pasting the address bar captures the range, the lookback and which
|
|
30
|
+
series were hidden. -->
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
- QuotaLens version: <!-- quotalens --version -->
|
|
35
|
+
- OS and version:
|
|
36
|
+
- Installed with: <!-- pipx / uvx / from source -->
|
|
37
|
+
- Python: <!-- python3 --version -->
|
|
38
|
+
|
|
39
|
+
## Collector state
|
|
40
|
+
|
|
41
|
+
<!-- `quotalens status` output. It contains your window percentages and reset
|
|
42
|
+
times but no identifiers. Trim it if you would rather not share those. -->
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Anything in the log
|
|
48
|
+
|
|
49
|
+
<!-- `quotalens logs -n 50`. The cookie is redacted from logs by design; if you
|
|
50
|
+
ever find one in there, that is itself the bug and it is a serious one —
|
|
51
|
+
please report it privately rather than in a public issue. -->
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Security issue
|
|
4
|
+
url: https://github.com/amitshcc/quotalens/security/advisories/new
|
|
5
|
+
about: >-
|
|
6
|
+
Anything involving the session cookie, credential storage or redaction.
|
|
7
|
+
Report it privately, not in a public issue. See SECURITY.md.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Endpoint drift
|
|
3
|
+
about: QuotaLens says the response shape changed, or a number looks wrong
|
|
4
|
+
labels: drift
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Before you paste anything
|
|
8
|
+
|
|
9
|
+
`quotalens probe` masks UUID-shaped values by default. Use it without
|
|
10
|
+
`--no-redact`, and do not paste raw sample exports. See the bug report template
|
|
11
|
+
for the longer version; the short version is that the payload is your account's
|
|
12
|
+
data and the safe form is `quotalens probe` with no flags.
|
|
13
|
+
|
|
14
|
+
## What the dashboard said
|
|
15
|
+
|
|
16
|
+
<!-- "shape drifted", "could not be parsed", a window missing, a percentage that
|
|
17
|
+
disagrees with claude.ai's own Settings page, and so on. -->
|
|
18
|
+
|
|
19
|
+
## The parsed section of `quotalens probe`
|
|
20
|
+
|
|
21
|
+
<!-- The `== parsed ==` block alone is often enough, and it contains no
|
|
22
|
+
identifiers at all. Start with that. -->
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## The raw payload, if the parsed section is not enough
|
|
28
|
+
|
|
29
|
+
<!-- `quotalens probe` (masked). Delete anything you would rather not share;
|
|
30
|
+
the top-level key names are the part that matters for drift. -->
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What claude.ai's own Settings → Usage page showed at the same moment
|
|
36
|
+
|
|
37
|
+
<!-- A number to compare against. A screenshot with the account details cropped
|
|
38
|
+
out is fine. -->
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Cross-platform is a headline claim in the README. Until this is green on all
|
|
4
|
+
# three runners it stays a claim, and the README says "Windows untested".
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main, master]
|
|
9
|
+
pull_request:
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: ci-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
lint:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.11"
|
|
27
|
+
- run: python -m pip install "ruff==0.16.*"
|
|
28
|
+
- run: ruff check src tests qa
|
|
29
|
+
- run: ruff format --check src tests qa
|
|
30
|
+
|
|
31
|
+
# Whether the favicon draws, and what size a cell computes to, are questions
|
|
32
|
+
# about SVG and CSS rather than about the operating system, so they run once
|
|
33
|
+
# rather than on all six matrix jobs. A separate job because these are the only
|
|
34
|
+
# things here that need a browser.
|
|
35
|
+
render:
|
|
36
|
+
name: what a browser actually renders
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.13"
|
|
43
|
+
- run: python -m pip install --upgrade pip
|
|
44
|
+
- run: python -m pip install -e ".[test,browser]"
|
|
45
|
+
- run: python -m playwright install --with-deps chromium
|
|
46
|
+
- run: python -m pytest tests/test_favicon_render.py tests/test_rendered_type.py -q
|
|
47
|
+
|
|
48
|
+
test:
|
|
49
|
+
name: ${{ matrix.os }} / py${{ matrix.python }}
|
|
50
|
+
runs-on: ${{ matrix.os }}
|
|
51
|
+
strategy:
|
|
52
|
+
fail-fast: false # a Windows failure must not hide a Linux one
|
|
53
|
+
matrix:
|
|
54
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
55
|
+
python: ["3.11", "3.13"]
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
|
|
59
|
+
- uses: actions/setup-python@v5
|
|
60
|
+
with:
|
|
61
|
+
python-version: ${{ matrix.python }}
|
|
62
|
+
|
|
63
|
+
- name: Build the wheel
|
|
64
|
+
run: |
|
|
65
|
+
python -m pip install --upgrade pip build
|
|
66
|
+
python -m build --wheel
|
|
67
|
+
|
|
68
|
+
# Installing the wheel rather than the source tree is the point: it is what
|
|
69
|
+
# a user gets from pipx, and it catches anything missing from the package
|
|
70
|
+
# data (the stylesheet, the scripts, the favicon).
|
|
71
|
+
- name: Install from the wheel
|
|
72
|
+
shell: bash
|
|
73
|
+
run: |
|
|
74
|
+
WHEEL=$(ls dist/*.whl)
|
|
75
|
+
python -m pip install "${WHEEL}[test]"
|
|
76
|
+
|
|
77
|
+
- name: Unit tests
|
|
78
|
+
run: python -m pytest -q
|
|
79
|
+
|
|
80
|
+
# The tests mock every HTTP call, so nothing above proves the process can
|
|
81
|
+
# actually start, poll, write rows and serve them on this platform.
|
|
82
|
+
- name: Smoke (server against a fake upstream, then the CLI lifecycle)
|
|
83
|
+
run: python qa/smoke.py
|
|
84
|
+
|
|
85
|
+
# Registering a scheduled task is the one thing unit tests cannot prove: the
|
|
86
|
+
# XML either satisfies Task Scheduler's schema or it does not, and only
|
|
87
|
+
# Windows can say. Install for real, confirm the task exists, then remove it.
|
|
88
|
+
# pwsh, not bash: Git Bash rewrites a leading-slash argument into a path, so
|
|
89
|
+
# `schtasks /Query` would arrive as `schtasks C:/Program Files/Git/Query`.
|
|
90
|
+
- name: Auto-start at login registers and unregisters (Windows)
|
|
91
|
+
if: runner.os == 'Windows'
|
|
92
|
+
shell: pwsh
|
|
93
|
+
run: |
|
|
94
|
+
$data = Join-Path $env:RUNNER_TEMP "svc"
|
|
95
|
+
python -m quotalens --data-dir $data service install --interval 300
|
|
96
|
+
if ($LASTEXITCODE -ne 0) { throw "service install failed" }
|
|
97
|
+
schtasks /Query /TN QuotaLens
|
|
98
|
+
if ($LASTEXITCODE -ne 0) { throw "install reported success but no task was registered" }
|
|
99
|
+
python -m quotalens --data-dir $data service status
|
|
100
|
+
python -m quotalens --data-dir $data service uninstall
|
|
101
|
+
if ($LASTEXITCODE -ne 0) { throw "service uninstall failed" }
|
|
102
|
+
schtasks /Query /TN QuotaLens 2>$null
|
|
103
|
+
if ($LASTEXITCODE -eq 0) { throw "the task survived uninstall" }
|
|
104
|
+
Write-Host "registered, queried and removed"
|
|
105
|
+
exit 0 # the query above is meant to fail; without this the step inherits its code
|
|
106
|
+
|
|
107
|
+
- name: Package contents
|
|
108
|
+
shell: bash
|
|
109
|
+
run: |
|
|
110
|
+
python - <<'PY'
|
|
111
|
+
from importlib import resources
|
|
112
|
+
web = resources.files("quotalens.web")
|
|
113
|
+
# An asset that works from the source tree and is missing from the
|
|
114
|
+
# installed wheel is exactly what this step exists to catch.
|
|
115
|
+
for name in (
|
|
116
|
+
"tokens.css", "app.css", "app.js", "chart.js",
|
|
117
|
+
"favicon.svg", "boost-rocket.svg",
|
|
118
|
+
"mark.png", # the notification icon
|
|
119
|
+
):
|
|
120
|
+
data = web.joinpath(name).read_bytes()
|
|
121
|
+
assert data, name
|
|
122
|
+
print(f"{name}: {len(data)} bytes")
|
|
123
|
+
|
|
124
|
+
# Vendor marks are optional by design -- a missing file renders the row
|
|
125
|
+
# with the name alone -- so this asserts the *directory* ships, and
|
|
126
|
+
# reports whichever marks are present.
|
|
127
|
+
from quotalens.status import VENDORS
|
|
128
|
+
vendor = web.joinpath("vendor")
|
|
129
|
+
assert vendor.is_dir(), "quotalens/web/vendor/ is missing from the wheel"
|
|
130
|
+
for v in VENDORS:
|
|
131
|
+
if not v.logo:
|
|
132
|
+
continue
|
|
133
|
+
path = vendor.joinpath(v.logo)
|
|
134
|
+
state = f"{len(path.read_bytes())} bytes" if path.is_file() else "absent (name only)"
|
|
135
|
+
print(f"vendor/{v.logo}: {state}")
|
|
136
|
+
PY
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing is the owner's action, so this workflow does nothing on its own:
|
|
4
|
+
# it fires on a tag you push, or on a manual dispatch aimed at TestPyPI.
|
|
5
|
+
#
|
|
6
|
+
# No API token anywhere. Both jobs mint a short-lived credential per run through
|
|
7
|
+
# OIDC (PyPI "trusted publishing"), which is why each has `id-token: write` and
|
|
8
|
+
# names an environment. A token in `secrets.*` would work and would be worse: it
|
|
9
|
+
# does not expire, and it is one leaked log line from being someone else's.
|
|
10
|
+
#
|
|
11
|
+
# A manual dispatch can only reach TestPyPI. Pushing a tag is the only route to
|
|
12
|
+
# PyPI, because that is the only condition under which the tag/__version__/wheel
|
|
13
|
+
# agreement check runs.
|
|
14
|
+
#
|
|
15
|
+
# Before the first real tag, publish once to TestPyPI via workflow_dispatch. The
|
|
16
|
+
# upload is irreversible and a filename can never be reused, even after a
|
|
17
|
+
# deletion — so a bad wheel on PyPI burns `quotalens-1.0-py3-none-any.whl`
|
|
18
|
+
# forever.
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
push:
|
|
22
|
+
tags: ["v*"]
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
inputs:
|
|
25
|
+
target:
|
|
26
|
+
description: "Where to publish"
|
|
27
|
+
required: true
|
|
28
|
+
default: testpypi
|
|
29
|
+
type: choice
|
|
30
|
+
# TestPyPI only. A dispatch can run from any ref, and the step that
|
|
31
|
+
# checks the tag, pyproject.toml and __version__ against each other
|
|
32
|
+
# is gated on refs/tags/v* -- so a dispatch to PyPI would be an
|
|
33
|
+
# irreversible upload with no version check at all. The tag is the
|
|
34
|
+
# only route to PyPI.
|
|
35
|
+
options: [testpypi]
|
|
36
|
+
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
# The tag is not evidence. Build from the tagged tree, then run the same
|
|
42
|
+
# checks CI runs, against the artifact that is about to be uploaded rather
|
|
43
|
+
# than against the source tree.
|
|
44
|
+
build:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- uses: actions/setup-python@v5
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.11"
|
|
52
|
+
|
|
53
|
+
- run: python -m pip install --upgrade pip build twine
|
|
54
|
+
|
|
55
|
+
- name: Build sdist and wheel
|
|
56
|
+
run: python -m build
|
|
57
|
+
|
|
58
|
+
# `twine check` reads the metadata PyPI will read. It catches a README
|
|
59
|
+
# that does not render before the upload does, which is the one class of
|
|
60
|
+
# error you cannot take back.
|
|
61
|
+
- name: Check the metadata
|
|
62
|
+
run: python -m twine check dist/*
|
|
63
|
+
|
|
64
|
+
# The version in the tag, the version in pyproject.toml and the version
|
|
65
|
+
# the installed package reports must be one number. They have disagreed
|
|
66
|
+
# before, and a release that says 1.0 in two places and 0.1.0 in the
|
|
67
|
+
# third is worse than no release.
|
|
68
|
+
- name: The tag, the metadata and __version__ agree
|
|
69
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
70
|
+
shell: bash
|
|
71
|
+
run: |
|
|
72
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
73
|
+
WHEEL=$(ls dist/*.whl)
|
|
74
|
+
python -m pip install "${WHEEL}"
|
|
75
|
+
PKG=$(python -c "import quotalens; print(quotalens.__version__)")
|
|
76
|
+
echo "tag=$TAG package=$PKG wheel=$WHEEL"
|
|
77
|
+
[ "$TAG" = "$PKG" ] || { echo "::error::tag v$TAG does not match __version__ $PKG"; exit 1; }
|
|
78
|
+
case "$WHEEL" in *"quotalens-$TAG-"*) ;; *) echo "::error::wheel filename does not carry $TAG"; exit 1;; esac
|
|
79
|
+
|
|
80
|
+
# A wheel that installs but cannot start is what CI's smoke test exists to
|
|
81
|
+
# catch. Run it here too: this is the artifact being published, not a
|
|
82
|
+
# rebuild of it.
|
|
83
|
+
- name: Smoke the built wheel
|
|
84
|
+
run: |
|
|
85
|
+
python -m pip install "$(ls dist/*.whl)[test]"
|
|
86
|
+
python -m pytest -q
|
|
87
|
+
python qa/smoke.py
|
|
88
|
+
|
|
89
|
+
- uses: actions/upload-artifact@v4
|
|
90
|
+
with:
|
|
91
|
+
name: dist
|
|
92
|
+
path: dist/
|
|
93
|
+
|
|
94
|
+
testpypi:
|
|
95
|
+
needs: build
|
|
96
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
environment: testpypi
|
|
99
|
+
permissions:
|
|
100
|
+
id-token: write
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/download-artifact@v4
|
|
103
|
+
with:
|
|
104
|
+
name: dist
|
|
105
|
+
path: dist/
|
|
106
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
107
|
+
with:
|
|
108
|
+
repository-url: https://test.pypi.org/legacy/
|
|
109
|
+
|
|
110
|
+
# Reachable only by pushing a tag, which is also the only condition under
|
|
111
|
+
# which the version-agreement step above runs. See the dispatch input.
|
|
112
|
+
pypi:
|
|
113
|
+
needs: build
|
|
114
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
environment: pypi # add a required reviewer on this environment, so a
|
|
117
|
+
# tag alone cannot publish without you clicking
|
|
118
|
+
permissions:
|
|
119
|
+
id-token: write # OIDC: this is the whole credential story
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/download-artifact@v4
|
|
122
|
+
with:
|
|
123
|
+
name: dist
|
|
124
|
+
path: dist/
|
|
125
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
quotalens-1.0/.gitignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# secrets and local state — never commit
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
*.db
|
|
5
|
+
*.db-journal
|
|
6
|
+
*.db-wal
|
|
7
|
+
*.db-shm
|
|
8
|
+
*.sqlite
|
|
9
|
+
*.sqlite3
|
|
10
|
+
probe-output*.json
|
|
11
|
+
scratch/
|
|
12
|
+
|
|
13
|
+
# python
|
|
14
|
+
__pycache__/
|
|
15
|
+
*.py[cod]
|
|
16
|
+
*.egg-info/
|
|
17
|
+
build/
|
|
18
|
+
dist/
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# editors / os
|
|
28
|
+
.DS_Store
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
|
|
32
|
+
# local reference checkouts, never committed
|
|
33
|
+
TEMP/
|
|
34
|
+
|
|
35
|
+
# working prompts and the local agent definition — kept out of the public repo
|
|
36
|
+
# on purpose. The versioned copies live in the private quotalens-web repo under
|
|
37
|
+
# prompts/. Removed from history on 2026-09-06; see that repo for the record.
|
|
38
|
+
design/prompts/
|
|
39
|
+
docs/prompts/
|
|
40
|
+
.claude/
|
|
41
|
+
CLAUDE_CODE_PROMPT.md
|
|
42
|
+
docs/V0.1.0-PROMPT.md
|
|
43
|
+
docs/AUDIT-*.md
|
|
44
|
+
docs/SECURITY-AUDIT-*.md
|
|
45
|
+
docs/LAUNCH-READINESS-*.md
|
|
46
|
+
docs/LAUNCH-TODO.md
|
quotalens-1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amit Sharma
|
|
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.
|