Skip to content

Releases: Honorboxx/honorbox

0.4.0: invitations that do not expire, and subscriptions

Choose a tag to compare

@LucideLarp LucideLarp released this 20 Jul 18:19

Two capabilities that change what you can sell, and one that stops you losing a sale you already made.

Invitations no longer expire out from under a buyer

GitHub expires an unaccepted repository invitation after seven days. Until now that meant a buyer who did not open the email lost what they paid for, permanently, while Stripe said paid, the ledger said delivered and the run was green.

scripts/renew-invites.js re-issues a pending invitation at six days, which restarts GitHub's clock. Three renewals maximum with a 24 hour cooldown, so a buyer gets about three weeks of open door and never an endless stream of email. When it gives up it says so in a line your monitoring can grep, rather than going quiet.

It runs as a step inside the fulfillment job you already have, so it costs no additional Actions minutes. The arithmetic is in docs/setup.md section 6.

Refunds are respected structurally. --revoke owner/repo:username removes access, deletes any pending invitation, and records the revocation permanently. Renewal refuses anyone on that list, and the planner refuses to run at all if it cannot read the list, rather than assuming nobody was revoked.

Subscriptions, opt-in

If you point a fulfillment grant at a recurring Stripe price, buyers were already delivered correctly. What did not happen was anything when the subscription ended.

scripts/reconcile-subs.js closes that. Every Stripe subscription status maps to a documented action, past_due is never treated as a lapse because Stripe is still retrying the card, and there is a configurable grace period after a genuinely terminal state.

Three properties worth knowing before you turn it on:

  • It is off unless you add a subscriptions block. A store without one behaves exactly as before, and scripts/fulfill.js is byte for byte unchanged from the previous release, so one-time selling cannot have regressed.
  • Reporting mode is the default. It tells you what it would revoke and revokes nothing until you set enforce: true.
  • It only ever revokes grants it recorded itself. Sellers, collaborators, one-time buyers and anyone who had access before you enabled it are out of scope by construction, not by a check that might miss.

There is also a circuit breaker: a pass that would revoke an unusual share of your customers refuses and alarms instead.

Treat enforcement as new. Run it in reporting mode for a while first, which is what that mode exists for.

Honest limit, stated in docs/subscriptions.md: removing a collaborator stops future pulls, it does not claw back what was already cloned. A lapsed subscriber keeps the last version they pulled. What they lose is updates.

A fifth theme

atrium joins terminal, editorial, brutalist and midnight. One flush left axis, a single spacing unit, one colour used with conviction, one short entrance and then stillness. The other four were reworked, and terminal was shipping another theme's theme-color, so a light mode browser painted the wrong chrome around a dark page.

Theme previews are sized for the box they actually occupy now, lossless, about 21 percent lighter.

Also

The docs are on the store as real pages, and their headings have anchors, so docs/setup.md's own internal links resolve instead of going nowhere. Fragment links are checked by a test.

241 tests, up from 91 two releases ago. Anything guarding money or access is mutation checked: the guard is broken deliberately and the test has to go red, because a test that cannot fail proves nothing.

0.3.0: fork protection, honest costs, and a fifth theme

Choose a tag to compare

@LucideLarp LucideLarp released this 20 Jul 15:05

Ninety-one commits since 0.2.0. Most of this release is defects found by using the engine as a stranger would, then fixing what broke.

If you fork this, read these first

A store that kept the vendor's checkout links can no longer deploy. Editing only the fields the setup guide listed used to produce a working store that sold someone else's product into someone else's Stripe account, and the build exited 0. The guard now keys on store identity and names every leftover by file and field. It also survives a cosmetic edit: a query string, a fragment, a trailing slash or a case change no longer walks past it.

Generated payment links have promotion codes off by default. A link that accepts typed codes, plus any live 100%-off coupon, is a free copy of your product to anyone who guesses the code. Turn it on deliberately for a campaign; docs/setup.md §7 shows how to run a $0 test without it.

$0/month is now arithmetic instead of an assurance. The shipped poll is */30, which is 1,488 billable minutes against the 2,000 GitHub Free includes for private repos. docs/setup.md §6 carries the table, the per-job rounding rule, and the rate, so you can check the claim rather than trust it.

Fulfillment

  • Every GitHub response is now distinguishable in the log, including statuses we do not recognise. An unrecognised status counts as not delivered, never as a sale.
  • A paid session that matches no fulfillment grant used to be dropped with new_paid=0 and a green exit. It warns now, because money arriving with nothing delivered is the failure you most need to hear about.
  • Requests carry a deadline. Node's fetch has none, so one unanswered connection could stall an entire cycle and everyone queued behind it.
  • Transient invite failures retry inside the run, honouring GitHub's retry-after, instead of waiting for the next poll. A primary rate limit is declined rather than slept through.
  • The Stripe session list retries on 429 and 5xx. It is the first call of every run, so one failure there used to mean nobody was delivered that cycle.
  • A zero-cost fulfilment says so. A coupon-covered order is a delivery, not a sale, and your revenue line should not blur them.
  • fulfill.js and fulfill-core.js are checked in CI against the engine, so a vendored copy cannot silently drift from the code you are reading.

Setup

init.js accepted --price=2900 and then said --price is required; discarded a typo'd flag and blamed a different one; printed a prompt and exited 0 having created nothing when stdin was not a terminal, which any script reads as success; demanded a Stripe key for --dry-run, which uses none; and treated --dry-run=true as not a dry run, creating live Stripe objects from a command that asked for a preview. Switches now refuse a value rather than guessing at one. A Stripe call that never answers now fails by name instead of hanging forever.

Store and docs

  • The docs are published as real pages, so the guides the FAQ points at now exist on the site.
  • Five themes. atrium is new: one flush-left axis, a single spacing unit, one orchestrated entrance and then stillness. editorial, brutalist and midnight were reworked; terminal was wearing another theme's theme-color.
  • Markdown: tables render, nested lists nest, code spans are inert, links keep their parens and titles, and a long identifier no longer widens the page on a phone.
  • Terms, refunds and license pages print legibly instead of blank or grey.
  • Showcase images are sized for the box they occupy, lossless: about 21% lighter.
  • One URL gate for every href, so config text cannot rewrite the page, and a tab can no longer smuggle an off-origin authority past it.

Tests

147, up from 91. Anything guarding a money path is mutation-checked: the guard is broken deliberately and the test has to go red, because a test that cannot fail is decoration.

0.2.0: webhook mode, hardening, and a storefront overhaul

Choose a tag to compare

@LucideLarp LucideLarp released this 19 Jul 12:57

The theme of this release: the default stack stays at zero infrastructure,
and everything around it got faster, safer, and easier to audit.

Opt-in webhook mode: delivery in seconds

Default fulfillment is unchanged. A scheduled Action polls Stripe; no server,
no webhook endpoint, nothing new to run. Webhook mode is for sellers who want
the invite to land in seconds instead of minutes:

  • Two interchangeable single-file relays, dependency-free:
    webhook-mode/relay-cloudflare.mjs
    (Cloudflare Workers free tier) and
    webhook-mode/relay-node.mjs (Val Town free
    tier, or any Node ≥ 20 box behind TLS).
  • The relay verifies the Stripe webhook signature (HMAC-SHA256, constant-time
    compare, 5-minute replay window) and fires a repository_dispatch. It
    holds no Stripe API key, does no fulfillment, and forwards no buyer data.
    Fulfillment truth still comes from Stripe's API through the normal poller
    logic, so a forged or replayed webhook can at worst trigger an empty run.
  • The poll stays on as the safety net: whichever fires first delivers, the
    other no-ops. An optional heartbeat workflow catches cron drift.
  • Setup is ~10 minutes: webhook-mode/README.md.
    Full walkthrough and threat model in
    docs/instant-delivery.md. The signature
    verification is covered by
    scripts/test/dispatch.test.js:
    known-good vector passes; tampered payload, forged signature, and stale
    timestamp are rejected.

Hardening

  • GitHub Actions pinned to full commit SHAs across the deploy workflow and
    the setup templates (supply chain).
  • Every config-authored URL is escaped when written into an HTML attribute,
    including the buy-button payment_link href. Covered by tests.
  • Markdown renderer: image src attributes escaped, rejected URL schemes
    surfaced instead of silently passed through, safety tests added.
  • Fulfillment ledger: dedup guard by ref, so concurrent runners (poll +
    webhook racing) can't double-append a sale.
  • Org-wide security policy
    with private vulnerability reporting enabled on this repo, plus
    CONTRIBUTING
    and CODE_OF_CONDUCT.

Storefront overhaul

  • stand theme modernized: coin-drop hero mark, ticket-style product cards,
    ruled sections, unified logo/favicon geometry, dark-aware accents.
  • SEO plumbing, all at build time (no client JS): Open Graph/Twitter cards,
    JSON-LD for the store, products, and guide articles, sitemap with
    lastmod + priority, and an accessibility pass.
  • Positioning broadened to everyone selling to buyers with GitHub accounts.
  • Two guides published:
    Deliver digital products through GitHub
    and Lemon Squeezy vs Gumroad vs DIY (2026).
    Fee claims across the site were re-checked against Gumroad's and Lemon
    Squeezy's published schedules (July 2026) and corrected where they had
    drifted.
  • The live demo store now runs a two-product catalog, so multi-product
    fulfillment is exercised in production, not just supported in theory.

Trust surface

  • The full terminal theme from Pro is published in this repo
    (themes/terminal/) as an auditable sample, alongside a
    public evidence doc. Audit the code standard before
    buying anything.
  • The Pro page now says plainly who shouldn't buy it.
  • Stability promise in the README: store.config.json, product frontmatter,
    and the fulfillment grant format are stable. Breaking changes only in a
    major version, with an UPGRADING note and a migration path.

Fixes

  • Markdown renderer: inline formatting now works across wrapped source lines
    (bold spanning a line break used to render literal **), and list items
    with wrapped source lines stay inside their <li>. Regression tests for
    both.
  • New support_email config key so the bot's acknowledgement messages carry
    a real contact address.

48 tests passing across the core and dispatch suites (npm test).

0.1.1 — storefront galleries and guides

Choose a tag to compare

@LucideLarp LucideLarp released this 19 Jul 07:45
  • Markdown renderer: standalone image + gallery-grid support (tested)
  • Product pages can show theme/product screenshots; assets ship to dist
  • Two guides published: Gumroad alternatives and selling with Stripe Payment Links
  • Home page links the guides; IndexNow ping on every deploy
  • Fulfillment: grants can match by price id (API-created sessions); seller self-purchases fulfill cleanly