Skip to content

Supporter codes: a page on the Mac, and a worker that hands them out - #80

Merged
McCal-Codes merged 2 commits into
mainfrom
kofi-worker-admin
Sep 22, 2026
Merged

McCal-Codes merged 2 commits into
mainfrom
kofi-worker-admin

Conversation

@McCal-Codes

Copy link
Copy Markdown
Owner

Two steps of the supporter code admin plan (rows A1 and A2 of the 0.6.7 build plan). Nothing here is deployed, and the signing key never leaves McCal's Mac: the worker only ever hands out codes minted there in advance.

The page on the Mac (scripts/code-admin.py)

Run it from the checkout that holds supporter-key.pem, or double-click scripts/Folio Codes.command. It listens on 127.0.0.1 with a fresh token each launch.

  • Drop in the Ko-fi CSV export (Ko-fi has no API for reading transactions) and it lists who needs a code and for how many months.
  • One click gives a ready code of that length, or mints one, with the code, the folio://redeem link and a draft message to copy.
  • It marks codes sent or withdrawn, and prints the serial for BetaKeys.WITHDRAWN.
  • Its ledger is supporter-ledger.json (gitignored, 0600, real codes and emails). The first run takes in the codes already in supporter-codes*.txt, so nothing is handed out twice.

scripts/beta-code.py keeps its behaviour; its minting is split into scope_bits, shape, sign and describe so both tools share one code format.

The worker's admin side (tools/kofi-worker/admin.js)

Everything under /admin/ needs Authorization: Bearer <ADMIN_TOKEN> and is off until that secret is set.

  • GET /admin/health, GET /admin/recent, POST /admin/pool, POST /admin/test, POST /admin/claim.
  • handled keeps the Ko-fi transaction id, name, type, amount and currency, so the Mac can match a hand-out to the CSV. No email address is stored.
  • Ko-fi's own Send test is recorded and uses no code.
  • The page's four checks: a test payment that puts its code back, a low-stock warning, a health check (secrets, stock, and that pool codes carry the Mac's signature), and the payments the worker missed.

Pricing (McCal, 22 Sep 2026)

A tip or donation of any size earns one month, each payment counting. The tiers stay Coffee $3, Backer $7, Builder $15; each Backer or Builder payment earns a month, and Coffee carries no code. This replaces "$5 = one month" and the $3/$6/$12 idea; docs/releases/kofi.md records both.

Tests

  • node tools/kofi-worker/admin.test.mjs (new, 7): the admin routes and the webhook against real SQLite with the real schema.sql.
  • node tools/kofi-worker/worker.test.mjs (11) and beta.test.mjs (9) still pass.
  • The app's BetaCodesTest (9) and BetaCodeToolTest (1) pass against the reworked script.
  • Tried end to end against wrangler dev --local with a throwaway key: refill, health, test payment, a real payment, a Ko-fi test, sync and a missed payment.

Still to do

Deploying is McCal's (Cloudflare, the secrets, the Ko-fi webhook address); the runbook is at the top of tools/kofi-worker/README.md. Folio Dev's "Hand out a code" (row A3) comes next and will use /admin/claim.

scripts/code-admin.py reads the Ko-fi CSV, shows who still needs a code and for
how many months, and hands out a ready code or mints one, with the redeem link
and a draft message to copy. It listens on 127.0.0.1 with a fresh token each
launch, and keeps its ledger in a gitignored supporter-ledger.json that starts
from the existing trackers. beta-code.py's minting is split into pieces both
share. kofi.md records the pricing McCal set on 22 Sep: a donation is one month,
Backer and Builder payments a month each, Coffee no code.
The worker gains an admin side for McCal's Mac: stock and health, the codes it
handed out with the Ko-fi transaction behind each one, a refill that takes codes
minted on the Mac, a claim for Folio Dev, and a test payment that runs the real
rules and puts its code back. Ko-fi's own Send test is recorded and costs no code.

The admin page grows a Ko-fi worker section with those checks, a warning when a
pool runs low, and the payments the worker missed. Its default pricing follows
the tiers: a tip earns a month, Backer and Builder a month each, Coffee nothing.

Also: a free port instead of a stack trace when the page is already running, and
a double-click launcher for it.
@McCal-Codes
McCal-Codes merged commit 19dba87 into main Sep 22, 2026
2 checks passed
@McCal-Codes
McCal-Codes deleted the kofi-worker-admin branch September 22, 2026 18:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58cb249408

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

POOLS = '{"tiers":{"*":"beta"},"tipFrom":5,"tipPool":"beta"}'
# McCal, 22 Sep 2026: a tip or donation of any size is one month; Backer and Builder payments are a month each;
# Coffee carries no code. m1 is the one-month pool that scripts/code-admin.py mints and uploads.
POOLS = '{"tiers":{"Backer":"m1","Builder":"m1"},"tipFrom":1,"tipPool":"m1","tipCurrency":"USD","tipRates":{"EUR":1.08,"GBP":1.27,"CAD":0.73,"AUD":0.66,"CHF":1.1}}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor any-size tips in currencies worth less than USD

The configured tipFrom: 1 is applied after currency conversion, so a valid 1 CAD or 1 AUD tip becomes 0.73 or 0.66 USD and silently earns nothing despite the stated “any size” rule. Because the currency is successfully priced, this also creates no problems row; use a positive-payment rule that does not impose a one-USD floor.

Useful? React with 👍 / 👎.

Comment thread scripts/code-admin.py
Comment on lines +169 to +171
owed = [x for x in person["payments"] if not x["code"] and x["months"]]
person["owed"] = [x["id"] for x in owed]
person["months"] = min(MAX_MONTHS, sum(x["months"] for x in owed))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve credits beyond the 15-month code cap

When a supporter has more than 15 uncovered qualifying payments, this caps the generated code at 15 months, but give() later attaches the entire person["owed"] list to that code. The remaining payments are therefore marked covered without receiving their months; only the payments represented by the capped code should be consumed so the rest remain owed.

Useful? React with 👍 / 👎.

Comment thread scripts/code-admin.py
Comment on lines +428 to +430
except ValueError:
for code in codes:
self.ledger.add(code, note="minted for the worker, which didn't take them")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep ambiguous refill failures out of the ready pool

If /admin/pool commits successfully but the response is lost or times out, Worker.call() raises ValueError and these same codes are recorded locally as ready to hand out. The worker can then give one of them to a payer while the admin page gives it to someone else; timeout/connection failures must retain a pending/pool state or reconcile by retrying the same idempotent upload rather than making the codes available locally.

Useful? React with 👍 / 👎.

# ln -s ~/dev/duo-fold-launcher/scripts/"Folio Codes.command" ~/Desktop/
#
# macOS may ask once whether you're sure you want to open it, because it didn't come from the App Store.
cd "$(dirname "$0")/.." || exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the checkout before following the launcher symlink

When the launcher is invoked through the documented Desktop symlink, $0 is the Desktop path, so this changes into the user's home directory and python3 scripts/code-admin.py fails. Resolve the symlink target before deriving the checkout directory; a literal copied launcher likewise cannot satisfy the claim that it continues to run this checkout without storing its location.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant