Claude a06682f40a
ci / test (pull_request) Successful in 46s
heartbeat / tick (pull_request_review) Successful in 9s
Taskmaster: a checkout per deal, a seat holder the app can find, a return that closes the deal
The reviewer objected that three paths the finish line depends on look broken. The review reached me cut off
partway through its first point ("Checkout re…"), so rather than guess at points two and three I ran each DONE
line against Whop's real semantics instead of the test doubles. Three paths failed, and they are the three fixed
here; the probes are now tests, so each one fails if the old behaviour comes back.

**A reused variant was sending the first deal's metadata.** `checkout()` returned the stored configuration whole
for any later deal that landed on the same price, so ann's configuration — with ann's deal, thread and member in
its metadata — was handed to bob. Bob's payment would have routed to a deal that was already paid: the webhook
answers `already`, bob's own deal never closes, his thread never leaves haggle mode. The card asks for variant
reuse, and reuse is what the *variant* is for, so the plan is still made once per price and named by `plan_id`
thereafter; the configuration on top of it is per deal, because its metadata is the whole routing mechanism.
`writing()` now runs before the store is read too, so a known variant is not a way around the live guard.

**A seat holder could never be found.** `whoIs` asked Whop what the member holds, and a seat holder holds nothing
— the pack is the lead's membership — so coach mode for a pack only ever worked for the buyer who bought it. The
flow test hid this behind a fake whose `membershipsFor` answered every user with every membership; it now answers
the way Whop does. `packIndex` (seats.js) remembers which pack a username was seated on and the server reads each
pack it names straight from Whop, so the index says where to look and Whop's metadata still says whether the seat
is there: a stale pointer costs one read and grants nothing, which a test asserts. The other half of the same
break: memberships answer to a Whop user id and seat lists are written in usernames, so a thread now carries both
names and `whoIs` uses the right one for each question.

**Back from checkout, the deal never closed.** The card allows a membership check on return as the other road a
confirmed payment arrives by, and the mode did switch — but nothing called `paid()`, so the deal stayed agreed,
was never logged, and showed no share card. Worse, `/api/walkaway` then logged a paid deal as a *walkaway*: a
closed sale recorded as a lost one, in the log the card calls the price discovery. `/api/return` now closes and
logs it, once, on a membership whose metadata either names this deal or names none; a membership from an earlier
deal does not close this one.

Two Whop writes that could take a member's turn down with them, while I was in there: a seat holder's usage write
refused by the live guard (which is every day of card #66) 500'd a coaching turn the member had paid for, and now
reports `usage_noted: false` instead; a refused checkout 500'd the accept turn and lost the thread's own record of
it, and now costs the quote card its button rather than the deal, with `/api/agree` minting it once Whop will
take it. And the seats routes were open to anyone holding a membership id — a paying org's staff list, readable
and writable by any caller — so they now take a thread, the same identity every other route runs on, and answer
403 to anyone but the buyer.

Nothing created on Whop, nothing bought: the live guard is intact and stronger, and the suite (83 tests, up from
75) still runs against an injected `fetch` with simulated signed webhooks. Since the reviewer's diff ran out
inside `dealdesk.test.js`, the README's layout now maps every test file to what it covers, so the half of the
suite that falls past a diff budget can at least be read about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M6pq5GUaS75LDV7WDaddA

Closes #66
2026-10-20 16:08:54 +00:00
2026-10-20 13:14:19 +00:00
…

time

A Gitea-native agent factory, running on Teahub prod (teahub.xyz). The Gitea repo is the ledger, Gitea Actions are the clock (Time), the worker is a Claude or Venice seat with hands or a Cursor cloud agent, and chat is where you plan.

  • Cards: an issue with a DONE: line, labelled ready.
  • Heartbeat (heartbeat.yml, hourly and whenever a card is labelled ready): moves cards forward. In dry-run (default) it only says what it would do; with TIME_MODE=live it dispatches each card to the worker (TIME_WORKER: a seat such as claude:sonnet or venice:kimi, which edits the repo and opens the PR itself, or a Cursor cloud agent), which opens a PR that closes the card. An optional reviewer (TIME_REVIEW) checks the PR. At most TIME_DAILY_CAP launches a day. Spend governor: every model call is booked to time/ledger/<YYYY-MM>.jsonl (tokens and dollars); once today's spend reaches TIME_DAILY_USD (default 5) or the month's reaches TIME_MONTHLY_USD (default 100), Time stops calling models, opens an andon issue, and a monthly breach also sets TIME_MODE=dry-run. No model decides whether to spend.
  • Mentions (mention.yml): @sonnet, @opus, @kimi, @grok 4.7, @qwen max high in any issue, PR or comment gets that seat's reply.
  • Hands-off (TIME_AUTOMERGE=on): label an issue spec and Time splits it into cards that run one at a time. A card's PR merges itself once the reviewer approves and checks are green, then the next card starts. Objections, red checks and protected paths (workflows, deploy, secrets) go to a human.
  • Venture loop (venture.yml, every 20 min once TIME_VENTURE=on): one venture card a day, or one right away from Actions → venture → Run workflow → "Start a new venture now". It scouts the repos that gained the most stars this week; seats pitch; the board votes (Borda, quorum of two); every board seat writes a launch plan (positioning, first users, pricing, channels, 7-day success and kill lines) and one seat synthesizes them into LAUNCH.md; the worker builds the product as the page on gh-pages (the tool fills the first screen under a slim header with the one signup button; pricing, limits and the why sit below); deterministic checks and headless Chrome (phone and desktop) gate it, with two fix rounds before an andon; GitHub Pages ships it at /ventures/<slug>/. Signups go through an issue form (.github/ISSUE_TEMPLATE/early-access.yml), each one thanked on its issue; after 7 days the count of distinct people decides: build v2, kill, or watch a week more. The studio index lives at /ventures/. Launch posts are drafts for a human; nothing is posted outside the repo.
  • Batches (TIME_VENTURE_BATCH=3, or Run workflow → "batch"): one scout pass, a field of pitches, and the board's top k become venture cards in one milestone, built side by side and launched together. A held child gets one batch retry before it's counted dead; a retro compares the cohort once every child is measured.
  • Seed (seed.yml, manual): land a venture that was built somewhere else. Give it a slug, the URL of a .tgz whose top folder is that slug (or of its parts, in order) and the .tgz's sha256, plus a name and a one-liner for a new venture. seed.py checks the bundle, copies it to ventures/<slug>/ on gh-pages in one commit, adds or refreshes its line in ventures/ventures.json, and asks Teahub pages to pull, so it is live at <slug>.yolopreneur.lol within the minute. Nothing in the bundle runs.
  • Refresh (refresh.yml, manual): for a venture that lives on gh-pages and makes some of its own files (fonts, art, screenshots, generated code). It runs ventures/<slug>/tools/build.sh on the runner, in place and without credentials, then commits what changed to gh-pages in one commit and asks Teahub pages to pull. from takes the folder from another branch first, so a reworked venture lands whole.
  • Backends (optional, CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID): each venture also gets its own Cloudflare Worker, D1 database and Workers AI, serving its Pages folder at <slug>.<TIME_VENTURE_DOMAIN>. The page talks to it through time.js (share links that save a creation, a rate-limited small model, and accounts with passkeys: no passwords, no email); every call degrades gracefully, so products still work on Pages alone. Code: .github/time/cloudflare.py and cloudflare/worker.js.
  • Doctor (doctor.yml, manual): runs the tests and checks label, settings, keys and today's spend.
  • Update (update.yml, weekly or manual): opens a PR with the latest engine from teahubxyz/time, so every project stays on one Time instead of drifting copies.

Setup

Started on GitHub as a proof of concept (teahubxyz/time, now read-only); ported to Gitea 2026-10-14.

Open this repo in Claude Code or Cursor and say "set up Time": the agent follows .claude/skills/time/SKILL.md. The recommended path needs one secret, ANTHROPIC_API_KEY: Claude seats plan, build and review (TIME_WORKER=claude:sonnet, TIME_REVIEW=claude:haiku; haiku, sonnet, opus, fable are the seats, priced in .github/time/seats.json). With VENICE_API_KEY instead, Venice seats do the same (TIME_WORKER=venice:qwen coder, TIME_REVIEW=venice:grok). A Cursor cloud agent can be the worker instead if you have a Cursor API key. The landing page walks through it: https://teahubxyz.github.io/time/

Status

For changes and release history, see CHANGELOG.md.

S
Description
Time: the clock for the factory. Ported from GitHub (PoC) 2026-10-14.
Readme
32 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 62%
JavaScript 36.6%
HTML 1.4%