AGENTS.md 4.5 KB

AGENTS.md — guidelines for coding agents

Project

cheapo — CLI in Go (Cobra) to fetch and observe current free game offers. Later planned: exposing the tools as an MCP server (see README.md, section "Roadmap / MCP").

Reference project

free-games-claimer — https://github.com/DoSpamu/free-games-claimer (fork of https://github.com/vogler/free-games-claimer). How it gets the free-games info:

  • The fork (DoSpamu) is only an overlay (runner run.js, Discord notify, logger) on the upstream Docker image. The actual logic lives upstream in vogler/free-games-claimer (epic-games.js, prime-gaming.js, gog.js, steam-games.js, aliexpress.js).
  • Upstream uses Playwright (Firefox) with a persistent session: it opens the store page "Free Games", detects games via the locator a:has(span:text-is("Free Now")), follows the links and claims them in the browser (login per cookie session, 2FA via OTP key, captcha handling).
  • Important (comment in epic-games.js, ~line 151): the page is built from the JSON of the public backend: https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions (filter data.Catalog.searchStore.elements on active promotions.promotionalOffers with 100 % discount; slug preferably from catalogNs.mappings[0].pageSlug, since urlSlug is sometimes wrong).
  • Our approach (Go): we query this endpoint directly — no browser, no login needed, as long as we only want to detect (list + watch). The actual claiming of the games (like in the reference project) is deliberate not part of this project.

Second reference project

free-games-notifier — https://github.com/jaixmario/free-games-notifier (Python, GitHub-Actions cron every 6 h; notifications via e-mail, Telegram, Discord, WhatsApp; state in free.json/free-steam.json).

How it gets the info:

  • Epic (epic.py): same public endpoint store-site-backend-static.ak.epicgames.com/freeGamesPromotions?locale=en-US (HTTP GET, no login) — confirms our Go approach. Filter: discountSetting.discountPercentage == 0; distinguishes current (promotionalOffers) and upcoming (upcomingPromotionalOffers — preview window for future free games). Change detection via signature (CURRENT:t1,t2|UPCOMING:...) + detail comparison per (title, link) keys; only notify on new offers, removals are silently written to state.
  • Steam (steam.py), two sources:
    1. Free to Keep: HTML scrape of https://store.steampowered.com/search/?maxprice=free&specials=1 (BeautifulSoup; .search_result_row, price "Free"/100 %).
    2. Free Weekend: JSON API https://store.steampowered.com/api/featuredcategories/ — items with "Free Weekend"/"play for free" in name/body; title fallback via store.steampowered.com/api/appdetails?appids=<id>.
  • Useful patterns for us: state signature + diff (notify only on real additions, removals silently into state) and current vs. upcoming separation. Both could be adopted.

Toolchain & linting

  • Go ≥ 1.27 (go version).
  • Formatting: gofmt -w . (mandatory before every commit; CI checks gofmt -l . = empty).
  • Linting/analysis: gopls (install: go install golang.org/x/tools/gopls@latest). Use in editors as LSP; additionally go vet ./... before every commit.
  • Build & test: bash go build ./... && go vet ./... && go test ./... gofmt -l . # must be empty ## Conventions - Module path: gogs.netdome.biz/paul/cheapo. - CLI framework: Cobra (github.com/spf13/cobra), commands in cmd/cheapo/main.go. - Internal code under internal/ (epic: store client, watch: polling/eventing). - Wrap errors with context: fmt.Errorf("epic: ...: %w", err). - User-facing strings in German (CLI output, help texts, error messages), code comments in English. - Provider pattern: every store lives in internal/<store>/ and implements game.Provider (Name() + GetFreeGames(ctx) ([]game.Game, error)); the shared type is game.Game (internal/game). New stores (e.g. GOG) follow this pattern and are registered in newProviders() (cmd/cheapo/main.go). - Chatto client: internal/chatto is a hand-built Connect-JSON client (Chatto's generated code is internal/ and not importable). Credentials ONLY via CHATTO_BASE_URL/CHATTO_TOKEN env from the operator — the tool never fetches credentials itself, and the agent does not use its own plugin credentials for it. - New store platforms (Prime, GOG, …) follow the provider pattern (above).