Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md — Universal Claimer

> Entry point for any AI or human working on this repository. Read this first.
> **Keep it up to date**: whenever a decision changes, update this file in the same change,
> and update the canonical source it points to. This file is a fast index, not the source
> of truth — the canonical documents are the constitution and the vision doc.

## What this project is

Universal Claimer is a **personal, open-source, self-hosted** platform that automatically
performs recurring "claim" actions on the operator's own third-party accounts:

- Claim free games (Epic Games weekly; later Amazon Prime Gaming, GOG)
- Collect Microsoft Rewards
- Resubscribe to a Twitch channel with Twitch Prime

It is **not** a commercial/hosted SaaS. Each person self-hosts their own instance via Docker.

## Canonical documents (source of truth)

| Document | What it holds |
|---|---|
| [.specify/memory/constitution.md](.specify/memory/constitution.md) | Non-negotiable principles and constraints. **Supersedes everything else.** |
| [docs/vision.md](docs/vision.md) | Product intent, confirmed technical reality, all key decisions, architecture, security posture. |
| [specs/](specs/) | Spec-kit feature specs. Current: [001-connect-and-claim](specs/001-connect-and-claim/spec.md). |

If this file disagrees with the constitution, the constitution wins — and this file is wrong
and must be fixed.

## Non-negotiable rules (from the constitution)

1. **English everywhere** — code, comments, specs, docs, commits, UI copy.
2. **Tests + docs mandatory** — no feature/connector merges without automated tests
   (unit + contract against a mocked platform fixture) and documentation.
3. **Connector isolation** — each target platform is a versioned, independently testable
   plugin with a uniform interface and a health-check.
4. **Secret custody minimalism** — third-party secrets are encrypted at rest (envelope
   encryption, key from compose env, outside the DB), never in plaintext, logs, screenshots,
   or dumps; decrypted only in memory during a job; prefer revocable sessions over passwords.
5. **Layered anti-detection; captcha as exception** — prevent (CloakBrowser) → auto-solve
   (anti-captcha.com) → human fallback. **VNC / remote desktop is prohibited.**
6. **TOS transparency** — a service-specific TOS warning + timestamped consent is required
   before any automation on a service, shown on every account-add.
7. **Identity isolation** — persistent per-account fingerprint; proxies deferred for the MVP.

## Key decisions (already made — do not re-litigate)

- **Tenancy**: single-user per deployment. No multi-tenancy, no RLS. Auth = one admin login
  set at onboarding.
- **Data layer**: bundled **plain Postgres** in the Docker Compose stack. **Supabase was
  dropped** (Auth/RLS pointless for single-user; managed dependency conflicts with easy
  self-hosting).
- **Distribution**: **Docker Compose with a deliberately minimal YAML** — `docker compose up`
  must Just Work. YAML holds only DB URL + app encryption key + port. Everything else is
  configured in the web onboarding wizard.
- **Host**: self-hosted, initially a home NAS, **x86_64 only** (CloakBrowser ships an x86_64
  Chromium binary; ARM is unsupported). Not a managed PaaS (they ban userbots/VNC).
- **Browser engine**: **CloakBrowser** (source-patched Chromium, drop-in for
  Playwright/Puppeteer), run **headed via Xvfb** on the headless host.
- **Third-party account auth**: **hybrid per service** — guided manual cookies export
  (upload/paste a `cookies.txt`) preferred; credential + TOTP login fallback. No public OAuth
  exists for the claim action, so this is session custody, not OAuth.
- **MVP scope**: Epic + Microsoft Rewards + Twitch Prime connectors, on-demand claims.
- **No monetization.** No proxies in the MVP (home residential IP).
- **One account per service** in the MVP.
- **Notifications**: in-portal realtime via SSE + one optional outbound webhook
  (Discord/Telegram/ntfy).
- **Admin password recovery**: optional 3 security questions (declinable) + always-available
  host-side reset command.

## Tech stack (decided at /plan)

TypeScript monorepo (pnpm workspaces) on Node 20. Next.js (portal + API + SSE); Playwright
driving CloakBrowser (headed via Xvfb); pg-boss (Postgres-backed job queue, no Redis);
Drizzle ORM + PostgreSQL 16; argon2id (password/recovery hashing); Node crypto AES-256-GCM
(envelope encryption); otplib (TOTP); Zod (validation). Tests: Vitest + Playwright contract
tests against recorded fixtures. Two images (web, worker) + stock Postgres via one Compose.

## Architecture (one Docker Compose stack)

```
Web app (Next.js: portal + onboarding wizard + SSE)
   └─ Postgres (admin, connected accounts [encrypted], jobs, results, consent)
   └─ Job orchestrator / queue
        └─ Workers: CloakBrowser (headed via Xvfb) — Epic / MS Rewards / Twitch
```

## Reference implementations (for connector inspiration)

- Epic / Prime / GOG: `vogler/free-games-claimer`
- Microsoft Rewards: `TheNetsky/Microsoft-Rewards-Script` (the mature reference: TOTP,
  persistent fingerprint, humanized delays), `charlesbel/Microsoft-Rewards-Farmer` (archived)
- Twitch Prime resub: `kylefmohr/twitch_prime_autosub`
- Amazon Prime loot: `srhinos/primelooter`

## Workflow (Spec-Driven Development via spec-kit)

Order: `constitution` → `specify` → `clarify` → `plan` → `tasks` → `implement`
(with `analyze` / `converge` as checks).

**Current state**: constitution ratified (v1.0.0); vision written; feature `001-connect-and-claim`
specified, clarified, planned, and **broken into tasks** (`specs/001-connect-and-claim/tasks.md`,
60 tasks across 7 phases) and **analyzed** (cross-artifact consistency check passed: 0
critical, coverage 100%; the MEDIUM findings were remediated by adding T012a/T020a/T027a/T035a).
**Implementation in progress** (`/speckit.implement`): **Phase 1 Setup + Phase 2
Foundational COMPLETE and verified.** Packages: `core` (config, crypto, logger, captcha),
`connectors` (Connector interface + runtime, CloakBrowser factory, cookies/TOTP, registry),
`db` (Drizzle schema 9 tables + migrations + pg-boss queue + connector-health), `apps/worker`
(state machine + startup reconciliation + queue loop), `apps/web` (Next.js skeleton + signed
session auth + argon2 + design-token baseline), `deploy/` (Dockerfiles web/worker + minimal
compose + .env.example). **40 unit tests green, `tsc -b` clean, web typecheck clean.**
Done: T001–T017 + T012a. **US1 (onboarding) COMPLETE**: admin/security-question/
notification-target data access, admin-service (setup/login/recover/reset behind an
AdminStore interface, 8 unit tests), setup+auth API routes, reset-admin CLI (tsx), onboarding
wizard + login + recover + dashboard pages, docs. **48 unit tests green, `tsc -b` + web
typecheck clean.** DB-backed integration tests are gated on `DATABASE_URL_TEST` (not run
here — Docker daemon was off).

**US2 (connect + consent) COMPLETE**: `connected_account`/`consent_record`/`service` data
access, Epic connector (authenticate + claim + healthCheck behind an injectable
`EpicPageDriver`; Playwright driver is best-effort DOM), services + accounts + consent API
routes (auth-guarded), connect UI (TOS gate → consent → method form), docs. Key design: the
**web app never launches a browser** — connect stores the sealed secret + default
fingerprint; session validity is checked on the first claim (worker). Shared zod schemas in
`schemas.ts` with a contract test. **58 unit tests green.** Done: T026–T033 + T027a.
**US3 (run claim) COMPLETE → MVP (US1+US2+US3) functionally done.** job data access +
state machine, `connector.claim` refactored to own its session (launch → auth-from-secret →
claim → close), worker `runClaim` orchestration (injectable deps, 5 unit tests) + production
wiring (Epic registered, CloakBrowser factory, anti-captcha, pg-boss loop), claim enqueue API
(consent + concurrency `409` guards), jobs API + **SSE** stream (Postgres LISTEN/NOTIFY),
dashboard Run button + live job list, docs. **73 unit tests green, `tsc -b` + web typecheck
clean.** Note: claim signature changed to `claim(input, fingerprint, ctx)` — contract doc updated.

**Feature 001 IMPLEMENTATION COMPLETE** — all 4 user stories + Polish. US4: `requires_human_action`
is a non-terminal outcome (pause → resume by re-enqueue, hand-back per FR-014, no VNC);
`@uc/notifications` best-effort webhooks wired into the worker. Polish: design hardening in
`globals.css` (focus states, controls, responsive — impeccable/taste-skill CLIs weren't
available, principles applied manually), connector auto-disable gating (enqueue `503` +
dashboard badge), `login`/`recover` rate-limiting (`429`), coverage top-ups.
**82 unit/contract tests green, `tsc -b` + web typecheck clean.**

Done: T001–T056 (T051 scroll-world SKIPPED, optional). **E2E VALIDATED against a live Postgres
(Docker)**: 87/87 tests green incl. 5 integration; production `next build` passes; full browser
walkthrough onboarding → connect Epic (cookies.txt + TOS consent) → Run claim → **live SSE**
status → graceful failure without CloakBrowser. Bugs found+fixed during e2e: pg-boss v10
`createQueue` (packages/db/src/queue.ts); Next `.js`/playwright-core bundling (webpack
extensionAlias + server external in next.config.mjs); `output: standalone` gated behind
`NEXT_OUTPUT` (Windows symlink).

**CloakBrowser is now integrated via the official `cloakbrowser` npm package** (real binary,
auto-managed — no manual path). `CloakBrowserFactory` lives at the `@uc/connectors/browser`
subpath (kept out of the web bundle; `defaultFingerprint` moved to `fingerprint.ts`). The
worker image pre-downloads the binary at build (`cloakbrowser install`); it also downloads on
first launch (~535MB, signature+checksum verified). Free v146 (no key); Pro via
`CLOAKBROWSER_LICENSE_KEY`. **Verified with the REAL binary**: a smoke test launched
CloakBrowser, drove the Epic connector against live epicgames.com with an empty session, and
correctly returned `reauth_needed`. **Still unproven:** a *successful* Epic claim (needs a
real authenticated Epic session); the connector's DOM selectors are best-effort. Feature 001
is otherwise complete. To run: see `docs/operations/onboarding.md` + quickstart.

**Assisted login (auto-capture cookies) — NEW, in progress** (docs/design/assisted-login.md).
Chosen approach: the operator logs in inside the instance-controlled CloakBrowser and cookies
are captured automatically (HttpOnly cookies can't be read by a web page, so a controlled
browser or an extension is required). **Iteration 1 DONE + tested** (88 tests): connector
`InteractiveLogin` capability (`loginUrl`/`isLoggedIn`/`extractCookies`, Epic impl +
driver `getCookies`/`goto`), worker `runLogin` orchestration (launch → poll-until-logged-in →
capture+store → close, with timeout/failure handling; unit-tested with fakes). Works in
local-display mode today. **Iteration 2 DONE + live-validated:** `login_session` table, `login`
pg-boss queue, db data-access (`login.ts`), worker `login` handler wiring (builds LoginDeps:
launch CloakBrowser → wait for the operator's confirm → capture cookies → store as a
session_import account), API (`POST /api/services/[id]/login-session`, `GET
/api/login-sessions/[id]`, `POST .../confirm`), UI (connect page "Log in for me" button +
`/login-session/[id]`), security notice shown.

**Wizard is mode-aware (feat/007):** local deployments use the **native CloakBrowser window**;
headless/remote deployments (container on a NAS, wizard reached from another machine, where the
window is invisible to the operator) use the **CDP screencast relay** (docs/design/cdp-relay.md):
`Page.startScreencast` JPEG frames + `Input.insertText`/dispatch over a **same-origin WebSocket**
bridged by a custom Next server (`apps/web/server.mjs`), authorized by a short-lived relay
ticket (client) / `RELAY_TOKEN` (worker). Fluid + copy-paste, no lag. **The old screenshot-in-DB
+ `login_input` relay was removed** (migration 0006 drops the table + `frame` column); frames and
keystrokes are now event-driven and **never persisted** (stronger Principle II). Shared protocol
+ coordinate mapping + ticket + bridge live in `@uc/core` (`relay*.ts`), unit-tested. Gated by
`LOGIN_RELAY_EMBED` (web + worker). **Live-validated locally:** bridge auth/forwarding (6/6) and
a real Epic login page streamed 73 JPEG frames end-to-end (worker CDP → web bridge → client).
Cosmetic TODO: the login page title is a hardcoded placeholder ("the service"). Epic checkout
(Get → Add to library across the purchase iframe) is walked step by step with bounded waits, looks
for a captcha inside the purchase window too, and returns the steps it reached as the failure
reason (`walkCheckout` in `packages/connectors/src/epic/checkout.ts`, driven by
`PlaywrightEpicDriver` in `driver.ts`; docs/operations/epic-games.md). A checkout captcha goes
straight to the operator (the solver is not asked: Epic's hCaptcha lives in its own purchase
window), and the pause keeps the games claimed before it. Its reference is
`vogler/free-games-claimer` on the `dev` branch; selectors still need live validation, and an
opt-in real-browser suite (`EPIC_BROWSER_TESTS=1`, `tests/epic.checkout.browser.test.ts`) reads
local stand-ins of the checkout in Chromium.

Note: spec-kit commands in this repo are GitHub Copilot prompts under `.github/prompts/`;
the PowerShell scripts under `.specify/scripts/powershell/` do the file scaffolding.

## Feature 002 — Automatic scheduling (branch `feat/002-scheduling`)

Recurring per-account claim scheduling (spec/plan/tasks under `specs/002-scheduling/`).
`schedule` table (one per account, cascade; migration 0002), `computeNextRun`/`jitterSeconds`
in `@uc/core` (shared by web + worker), worker `runScheduler` orchestration driven by a
pg-boss cron `boss.schedule(SCHEDULER_QUEUE, "* * * * *")` — due schedules enqueue a claim via
the existing pipeline (skips accounts with an active claim; advances always so no backlog;
jittered startAfter for Principle VII). API `GET/PUT/DELETE /api/accounts/[id]/schedule`,
dashboard `ScheduleEditor` (daily/weekly + time). Connector-agnostic. **99 tests green,
`tsc -b` + web typecheck + next build clean.** Not yet run live (scheduler tick needs the
running worker + Postgres). Git: committed on the feature branch; `main` is the stable
baseline.

## Feature 003 — Twitch Prime connector + per-account config (branch `feat/003-twitch`)

Second connector, proving the plugin architecture with a targeted action (resub) and adding
**per-account connector config**. `spec`/`tasks` under `specs/003-twitch-connector/`.
- Generic config infra: `ConfigField` + `configFields` on `Connector`; `claim(input,
  fingerprint, config, ctx)` (Epic ignores config); `connected_account.config` +
  `login_session.config` jsonb (migration 0003); connect UI renders fields generically;
  connect + assisted-login validate/carry config; `missingConfigKeys` helper.
- `TwitchConnector` (Connector + InteractiveLogin, `configFields=[channel]`, claim = resub
  with Prime → claimed/nothing_to_claim/failed/reauth/human-action) + `PlaywrightTwitchDriver`
  (best-effort selectors). `defaultRegistry()` (Epic + Twitch) shared by worker + web; `twitch`
  service seeded. Docs `docs/operations/twitch.md`.
- **108 tests green, tsc -b + web typecheck + next build clean.** Not run live. DOM selectors
  best-effort (need live validation, like Epic). Branch chain: main → feat/002 → feat/003.

## Feature 004 — Microsoft Rewards connector (branch `feat/004-ms-rewards`)

Third headline connector (completes Epic + Twitch + MS Rewards). `spec`/`tasks` under
`specs/004-ms-rewards/`. `MsRewardsConnector` (Connector + InteractiveLogin, `configFields=[]`)
— claim runs the day's outstanding desktop Bing searches with varied queries
(`queries.ts` `pickQuery`) and humanized delays; injectable `sleep`/`rand`/`maxSearches`
(default 30) for deterministic tests. Outcomes: nothing_to_claim / claimed (count) /
reauth_needed / requires_human_action (verification) / failed. `PlaywrightMsRewardsDriver`
best-effort selectors (modern Bing Rewards dashboard only; desktop searches only for v1).
Registered in `defaultRegistry()`; `microsoft` service seeded; docs
`docs/operations/microsoft-rewards.md`. No schema change. **115 tests green, tsc -b + web
typecheck + next build clean.** Not run live (selectors best-effort). Branch chain:
main → 002 → 003 → 004.

## Feature 005 — Per-account proxy & IP isolation (branch `feat/005-proxies`)

Optional per-account proxy (bring-your-own) so accounts don't share an IP (Principle VII, was
deferred at MVP). `spec`/`tasks` under `specs/005-proxies/`. `@uc/core` `isValidProxyUrl` +
`maskProxy` (tested). Encrypted proxy columns on `connected_account` + `login_session`
(migration 0004) — proxy is sealed like the secret (may contain creds; never plaintext/logs).
`CloakBrowserFactory` gains a `proxy` option; the worker builds a **fresh factory per run**
with that account's decrypted proxy (claims + assisted login) — connectors unchanged
(Principle I). Connect + login-session APIs validate + seal the proxy; connect UI has an
optional proxy field. Docs `docs/operations/proxies.md`. **119 tests green, tsc -b + web
typecheck + next build clean.** Not run live. Branch chain: main → 002 → 003 → 004 → 005.

## Feature 006 — CI & packaging / hardening (branch `feat/006-ci-packaging`)

Operational hardening + automated full test suite. `spec`/`tasks` under
`specs/006-ci-packaging/`.
- **CI** `.github/workflows/ci.yml`: on push/PR, `pnpm install --frozen-lockfile` → `pnpm
  build` → web typecheck → `pnpm test` (with a Postgres service + `DATABASE_URL_TEST`, so the
  gated integration tests run) → web production build. This is the automated "all tests" gate.
- `docker compose up`: **two** containers, `postgres` + `app`. `deploy/entrypoint.mjs` is the
  app's PID 1 and runs secrets bootstrap → Xvfb → preflight (`node packages/db/dist/migrate.js`:
  migrations, seed, key check) → browser download → web + worker, supervised by `supervise()` in
  `packages/core/src/supervisor.ts` (a child that dies takes the container down; SIGTERM reaches
  both). Single published image, `uc-app`.
- **Secrets survive updates.** `APP_ENCRYPTION_KEY`/`RELAY_TOKEN` resolve env → `uc_config`
  volume → generate-and-persist (`packages/core/src/bootstrap-secrets.ts`), so the stack file
  carries none. The key's fingerprint is stored in `app_setting` and checked at boot
  (`assertMasterKeyMatches`): a swapped key refuses to start rather than silently making every
  stored session undecryptable.
- **`GET /api/health`** (DB reachability, unauth) + app container healthcheck (long
  `start_period`: the first boot downloads ~200MB of browser before the portal listens).
- Root `verify` script (build + web typecheck + tests) — needs `pnpm` on PATH (CI/dev; this
  sandbox has no global pnpm so run steps individually via `corepack pnpm`).
- Self-host quickstart: README section + `docs/operations/deploy.md`.
- **119 unit/contract tests + build + web typecheck green.** DB-integration run was deferred
  (Docker was off in this session) → CI executes it. Branch chain: main → 002 → 003 → 004 →
  005 → 006.

## Legal / TOS reminder

Automating these platforms may violate their Terms of Service and can get the operator's own
accounts suspended. This is used at the operator's own risk; the UI must surface this per
service (Principle 6). Not legal advice.

---
_Last updated: 2026-07-17. Update this file whenever a decision or the spec-kit state changes._