{"id":51699110,"url":"https://github.com/edycutjong/the-commons","last_synced_at":"2026-07-16T08:33:49.559Z","repository":{"id":371336774,"uuid":"1300532912","full_name":"edycutjong/the-commons","owner":"edycutjong","description":"✉️ Sealed daily social-dilemma game — commit one hidden choice, learn the collective verdict only tomorrow (Devvit)","archived":false,"fork":false,"pushed_at":"2026-07-15T07:31:49.000Z","size":818,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-16T08:33:42.936Z","etag":null,"topics":["commit-reveal","daily-game","devvit","game-theory","games-with-a-hook","hackathon","reddit","reddit-games","social-deduction","typescript"],"latest_commit_sha":null,"homepage":"https://edycutjong.github.io/the-commons/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edycutjong.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-14T13:36:16.000Z","updated_at":"2026-07-15T07:31:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/edycutjong/the-commons","commit_stats":null,"previous_names":["edycutjong/the-commons"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/edycutjong/the-commons","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edycutjong%2Fthe-commons","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edycutjong%2Fthe-commons/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edycutjong%2Fthe-commons/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edycutjong%2Fthe-commons/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edycutjong","download_url":"https://codeload.github.com/edycutjong/the-commons/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edycutjong%2Fthe-commons/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35537751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["commit-reveal","daily-game","devvit","game-theory","games-with-a-hook","hackathon","reddit","reddit-games","social-deduction","typescript"],"created_at":"2026-07-16T08:33:46.767Z","updated_at":"2026-07-16T08:33:49.541Z","avatar_url":"https://github.com/edycutjong.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/icon.svg\" alt=\"The Commons\" width=\"144\" height=\"144\" /\u003e\n  \u003ch1\u003eThe Commons 🕯️\u003c/h1\u003e\n  \u003cp\u003e\u003cem\u003eOne secret choice a night. The whole subreddit lives with it.\u003c/em\u003e\u003c/p\u003e\n  \u003cimg src=\"docs/readme-hero.svg\" alt=\"The Commons — a nightly sealed social dilemma\" width=\"100%\" /\u003e\n\n  \u003cbr/\u003e\u003cbr/\u003e\n\n  [![Play on Reddit](https://img.shields.io/badge/Play_on-Reddit-8B5CF6?style=for-the-badge\u0026labelColor=0B0A07)](https://www.reddit.com/r/the_commons_game_dev/)\n  [![Watch Demo](https://img.shields.io/badge/Watch-Demo-F43F5E?style=for-the-badge\u0026labelColor=0B0A07)](https://www.youtube.com/watch?v=mgUb0PFsIW0)\n  [![Devpost](https://img.shields.io/badge/View_on-Devpost-003E54?style=for-the-badge\u0026labelColor=0B0A07)](https://devpost.com/software/the-commons)\n  [![Landing Page](https://img.shields.io/badge/Landing-Page-10B981?style=for-the-badge\u0026labelColor=0B0A07)](https://edycutjong.github.io/the-commons/)\n  [![Pitch Deck](https://img.shields.io/badge/Pitch-Deck-10B981?style=for-the-badge\u0026labelColor=0B0A07)](https://edycutjong.github.io/the-commons/pitch/)\n  [![Games with a Hook](https://img.shields.io/badge/Reddit-Games_with_a_Hook-8B5CF6?style=for-the-badge\u0026labelColor=0B0A07)](https://redditgameswithahook.devpost.com)\n\n  \u003cbr/\u003e\n\n  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat\u0026logo=typescript\u0026logoColor=white)\n  ![Devvit Web](https://img.shields.io/badge/Devvit_Web-0.13-FF4500?style=flat\u0026logo=reddit\u0026logoColor=white)\n  ![Hono](https://img.shields.io/badge/Hono-4-E36002?style=flat\u0026logo=hono\u0026logoColor=white)\n  ![Vite](https://img.shields.io/badge/Vite-8-646CFF?style=flat\u0026logo=vite\u0026logoColor=white)\n  ![Vitest](https://img.shields.io/badge/Vitest-222_passing-6E9F18?style=flat\u0026logo=vitest\u0026logoColor=white)\n  ![Coverage](https://img.shields.io/badge/coverage-100%25-6E9F18?style=flat\u0026logo=vitest\u0026logoColor=white)\n  [![CI](https://github.com/edycutjong/the-commons/actions/workflows/ci.yml/badge.svg)](https://github.com/edycutjong/the-commons/actions/workflows/ci.yml)\n\u003c/div\u003e\n\n---\n\n**The problem.** Most \"daily\" Reddit games close the loop the instant you play — there's nothing to come back for — and most \"community\" games are a thousand people playing alone in the same post.\n**The solution.** The Commons runs **one sealed social dilemma per night**; nobody — not even the UI — sees the crowd's split until a single midnight transaction settles it, and the morning **Reckoning** reveals what the community did to itself.\n**What's built (green).** A Devvit Web app: a pure percent-threshold **payoff engine** (5 archetypes), a **structurally-sealed** commit protocol, one **idempotent `watch/multi/exec` settle**, a deterministic six-round **preseason seed**, and a playable webview that **fires the reveal on cold open** (no waiting for real midnight). **222 passing tests, 100% coverage** on the shared/core/routes gate, settle **10k-commit p95 ~50ms** (measured; \u003c 2s budget).\n\n---\n\nEvery night the app posts one dilemma — _\"THE BLACKOUT POT: if 70%+ **FEED**, every stake multiplies; **HOARDERS** take 3× — but if the hoarding tips the line, the pot burns.\"_ Players commit **one sealed choice**. Nobody — not other players, not the leaderboard, **not even the UI** — can see the split until the midnight settle. At 00:00 UTC a single idempotent transaction resolves everyone's fate; **The Reckoning** posts the outcome, streaks pay out, and the week's most saintly and most treacherous players wear **Saint / Serpent flair** in the subreddit. You cannot learn what happened to you without coming back tomorrow. Retention is the skeleton, not a bolt-on.\n\nA Reddit Devvit Web game (Hono server + framework-free webview). **Zero runtime AI. Verified Devvit APIs only.**\n\n---\n\n## 📜 Rules\n\n1. **READ** tonight's dilemma: the payoff structure is public (plain sentences), and a live `souls · pot` ticker climbs as players seal — the server broadcasts those two integers on the realtime `pot_ticker` channel and the webview reflects the count (polled) — **but never the split.**\n2. **COMMIT** one sealed choice + a stake of season points (optionally buy **streak insurance**). One commit per account, ever — no take-backs. The envelope seals; a countdown to the reveal begins.\n3. **RETURN** after midnight: the Reckoning replays — split bars, the verdict line (_\"58% hoarded. The pot burned.\"_), your personal result, and the night's new Saints \u0026 Serpents. Tonight's dilemma is already open.\n\n- **Season economy:** everyone starts at 100 points; each settled night pays your dilemma delta + a flat participation reward (floored at 0). Max stake 50.\n- **Streaks \u0026 insurance:** consecutive settled nights build a streak; **insurance** (40 pts) forgives one losing night so your streak survives.\n- **Reputation ladders:** **Saint** score rewards the cooperative choice (conviction, not luck — a burned feeder still earns it); **Serpent** score rewards _profitable_ betrayal. Weekly ceremony crowns #1 Saint, #1 Serpent, and a seeded Wildcard with flair, then decays both ladders ×0.8 so titles circulate.\n- **Dilemma Forge (UGC):** players submit a parameterized dilemma (payoff sliders + ≤140-char flavor through a wordlist filter) → moderator approval queue → rotation, author credited on the post.\n\n---\n\n## 🌅 The cold open (what a lone judge sees first)\n\nA judge often arrives mid-cycle, alone, on a phone — they can't wait for a real midnight. So the webview **leads with the reveal, not a form**: on load it animates the seeded catastrophe — the biggest self-inflicted collapse in the feed, **\"58.0% hoarded. The pot burned.\"** (bars sweep in, the verdict punches, a red flash) — then a **TONIGHT** divider and the same mechanic *open right now*, then the rest of the seeded feed. The \"oh\" lands in the first few seconds from the seeded state; the judge's own sealed commit then teaches the retention hook (_\"I can't learn my fate without coming back\"_). The featured round is chosen deterministically (`ruin` whose plurality choice was non-cooperative, ranked by crowd size) and clearly badged **PRESEASON**. See `DEMO.md`.\n\n---\n\n## 🎲 Payoff archetypes (all 5)\n\nAll thresholds are **percent-based**, so the drama is identical at 8 players or 8,000. All deltas are integers. Every resolver is a pure function of `(params, commits)` — no randomness, no clocks.\n\n| Archetype | Choices | Cooperative | Group wins when… | Winners get | Losers get |\n|---|---|---|---|---|---|\n| **Public Pot** | FEED / HOARD | FEED | `feed% ≥ threshold` (def **0.70**) | feeders ×`feedMult` (2), hoarders ×`hoardMult` (3) | otherwise the **pot burns** — every stake lost |\n| **Stag Hunt** | STAG / HARE | STAG | `stag% ≥ threshold` (def **0.80**) | stags ×`stagMult` (3); hares **always** bank ×`hareMult` (1.5) | if the hunt fails, stags lose their whole stake |\n| **Chicken** | SWERVE / DARE | SWERVE | `dare% ≤ crashFrac` (def **0.50**) | darers ×`dareMult` (3), swervers ×`swerveMult` (1.25) | on a crash, darers lose all; swervers merely keep theirs (push) |\n| **Lowest Unique Bid** | BID_1…BID_5 | _(none)_ | mixed by design | the **rarest** bid (ties → lower) wins ×`winMult` (4) | everyone else burns `loseFrac` (0.5) of stake |\n| **Exact-N Heist** | GUARD / HEIST | GUARD | alarm holds (no valid crew) | heist inside `targetFrac ± band` (0.125 ± 0.05) → heisters ×`heistMult` (5) | wrong-size crew → heisters lose all, guards collect ×`guardMult` (1.5); a cracked vault costs guards 25% |\n\nForge submissions clamp every param onto its slider grid (`min/max/step`), so no player-authored dilemma can escape the tested envelope.\n\n---\n\n## 🏗️ Architecture\n\nA Devvit Web app: a **Hono server** over a pure, platform-free payoff engine, with a\n**React webview** client. Full Redis schema + endpoint tables in\n[`ARCHITECTURE.md`](ARCHITECTURE.md).\n\n```mermaid\nflowchart TD\n  subgraph Client [React webview]\n    A[Dilemma card] --\u003e|/api/commit| S\n    B[Reckoning replay] --\u003e|/api/history| S\n    L[Ladders] --\u003e|/api/ladders| S\n    F[Forge form] --\u003e|/api/forge| S\n    A \u003c--\u003e|pot_ticker| RT[realtime]\n  end\n  subgraph Server [Devvit Node]\n    S[Hono routes] --\u003e R[(Redis)]\n    C1[cron settle 00:00] --\u003e R\n    C2[cron open 00:05] --\u003e RED[Reddit API]\n    C3[cron ceremony weekly] --\u003e R\n    C1 --\u003e|Reckoning comment| RED\n    TR[onPostCreate] --\u003e R\n    M[menu: Seed Preseason / Void Round / Force Settle / Approve Forge] --\u003e R\n  end\n```\n\n---\n\n## 🔒 Sealed-secrecy architecture (invariant I1)\n\nThe secret split is the whole game. Secrecy is **structural**, not a filter you can forget to apply:\n\n- **Commits live only in `commit:{day}` (a Redis hash), server-side.** They are read for the first time inside the settle transaction. No endpoint returns them pre-settle.\n- **The pre-settle projections cannot carry a split.** `RoundView` (`/api/round`) exposes only `participants` and `pot` — no per-choice counts, no per-user data. `/api/history` returns open rounds **not at all**. `tests/endpoints.test.ts` asserts these shapes key-by-key and sweeps the raw JSON for split-shaped leakage for both logged-in and logged-out viewers.\n- **Realtime broadcasts exactly two integers** (`{ participants, pot }`) on `pot_ticker`. Broadcasting the split would kill the game, so the type literally cannot express it.\n- **Settle is one idempotent `watch/multi/exec` pass** (invariant I3). A commit racing the settle either lands before the flip or is cleanly told _\"the envelope is sealed\"_ — nothing partial ever lands (invariant I2: one commit per account via `HSETNX`). Re-running settle (scheduler retry or **Force Settle** menu) is byte-identical: the engine is a pure function of `(commits, params)` and `settledAt` is pinned for seeded rounds.\n\n**Redis uses sorted-sets + hashes + strings only** — no plain lists/sets (Devvit Redis has no key-scan; `round:index` is a zset so history can enumerate). Schema: `round:{day}`, `commit:{day}`, `outcome:{day}`, `streak:{userId}`, `rep:saint`, `rep:serpent`, `season:points`, `forge:queue`, `forge:approved`, `round:current`, `round:index`, `pot:{day}`, `settled:last`, `post:{postId}`, `flair:templates`, `ceremony:last`.\n\n---\n\n## ✅ Compliance notes\n\n- **In-app ballots only.** Every choice is a sealed in-app commit through `POST /api/commit`. The game **never** uses Reddit up/down-votes as ballots or game input — Reddit voting is not a mechanic here. (Enforced by `scripts/check_submission_readiness.mjs`, which greps for any vote calls.)\n- **Verified Devvit APIs only.** Reddit surface used: `getCurrentUsername`, `submitCustomPost`, `submitComment` + `Comment.distinguish(makeSticky)`, `setUserFlair`, `createUserFlairTemplate` — all verified in `@devvit/reddit@0.13.6` `.d.ts` (see `docs-cache/VERIFIED.md`). Redis: `zAdd/zRange/zScore/zRem/zCard`, `hSet/hGet/hGetAll/hSetNX/hDel/hLen/hKeys`, `get/set/incrBy/del/exists`, `watch/multi/exec`. Realtime: `realtime.send`.\n- **Empty fetch allowlist.** `devvit.json` declares no external `http` domains; the client only calls same-origin `/api/*`.\n- **Zero runtime AI.** No LLM SDK is imported anywhere in `src/`. The payoff engine _is_ the content generator.\n\n### Honest limitation — sybil / alt-accounts\n\nOne commit per Reddit account is enforced transactionally (`HSETNX` keyed by `userId`), and the economy is **siloed per subreddit** with seasonal resets. But a determined player can still run alt-accounts; there is **no** device/IP fingerprinting (Devvit doesn't expose it, and we won't fake it). Mitigations are social and operational: per-sub economy, seasons, moderator **Void Round** / **Force Settle** tools, and constrained UGC (sliders + filtered flavor, mod-approved). We call this out rather than pretend it's solved.\n\n---\n\n## 🧪 Test \u0026 build\n\n```bash\nnpm install\nnpm run lint          # eslint (flat config, typescript-eslint) — clean\nnpm run type-check    # tsc --noEmit — clean\nnpm run test          # vitest — 222 tests across 21 files, all passing\nnpm run test:coverage # vitest --coverage — 100% statements/branches/functions/lines\n                      # on src/shared/** + src/server/core/** + src/server/routes/**\nnpm run build         # vite build → dist/client/{splash,game}.html + dist/server/index.cjs\nnpm run bench         # settle-latency table (100/1k/10k, p50/p95); PRD budget: 10k \u003c 2s\nnpm run check         # submission-readiness gate (files + config + compliance + tsc/test/build)\nnpm run seed:local    # dry-run the deterministic seed and print the six Reckonings\n```\n\n**Exact test count: 222 tests, 21 files.** Coverage: the pure payoff engine (per-archetype + property tests), reputation/streak/economy updaters, the settle transaction (idempotency, the commit-vs-settle race, void, insurance, malformed-data resilience), the sealed-shape endpoint protocol, the deterministic Seed Preseason, the weekly flair ceremony, and a settle-latency smoke test. `npm run test:coverage` holds **100% statements/branches/functions/lines** on the pure/mockable business logic (`src/shared/**`, `src/server/core/**`, `src/server/routes/**`) — `src/client/**` (React webview) and `src/server/index.ts` (process bootstrap) are excluded, same rationale as the Honest Limitations below. 15 `/* v8 ignore */` comments mark genuinely-unreachable-by-construction defensive branches (unique-key sort tie-breaks, shipped-fixture data-integrity guards, always-populated map lookups) — never a shortcut for an untested real path.\n\n---\n\n## ⚙️ Engineering harness\n\nEvery push and PR runs the real gates on Node 20 via GitHub Actions (`.github/workflows/ci.yml`): `npm ci` → `eslint .` → `tsc --noEmit` → `vitest run --coverage` (100% gate) → `vite build` → `node scripts/bench.mjs` (the settle-latency budget gate). CodeQL (`javascript-typescript`) and Dependabot (npm + github-actions) run on their own schedules.\n\n| Layer | Tool | Status |\n|---|---|---|\n| Lint | `eslint .` (flat config, typescript-eslint) | ✅ clean |\n| Type safety | `tsc --noEmit` (strict, `noUncheckedIndexedAccess`) | ✅ clean |\n| Unit / integration | Vitest — **222 tests, 21 files** | ✅ passing |\n| Coverage | Vitest `--coverage` (v8) — **100%** statements/branches/functions/lines on shared/core/routes | ✅ gate enforced |\n| Build | Vite → `dist/client/{splash,game}.html` + `game.js` + `dist/server/index.cjs` | ✅ |\n| Performance gate | `scripts/bench.mjs` — settle **10k p95 ~50ms** (budget 2s) | ✅ PASS |\n| Submission compliance | `scripts/check_submission_readiness.mjs` — no lists/sets, zero AI, no Reddit votes, empty fetch allowlist, verified-Reddit-surface-only | ✅ |\n| SAST | CodeQL (`javascript-typescript`) | ✅ |\n| Supply chain | Dependabot (npm + github-actions) | ✅ |\n\n**N/A for this project (Devvit Web, no served localhost):** Lighthouse CI and Playwright-against-`localhost` are intentionally omitted — the client is a Devvit webview rendered inside the Reddit post sandbox, not a Next.js app served on a port, so there is no local URL to point a headless browser at. The equivalent quality gates here are the sealed-shape endpoint tests and the settle bench. The one step no CI can do is `devvit playtest` (see below).\n\n---\n\n## 🚀 First playtest checklist\n\nYou cannot `devvit playtest` from CI — this is the human step. In order:\n\n1. **Front-load the subreddit ban round-trip (do this DAY ONE).** New hackathon subreddits are being **auto-banned** by Reddit safety automation (\"Rule #2\"), **including re-bans immediately after you install a Devvit app** (see `docs-cache/VERIFIED.md`). From your aged main account: create **r/the_commons_game_dev** (and any spare test subs), add a **normal pinned post before installing anything**, and keep the unban forum thread handy — `redditgameswithahook.devpost.com/forum_topics/44175-…`. Expect a re-ban at first app install and request the manual unban there. Install `dr-admin-approve` per the rules.\n2. **Green locally first:** `npm run check` should print `READY.` (runs type-check + 222 tests + build).\n3. **`npm run login`** (`devvit login`) with the account that owns the app.\n4. **`npm run dev`** (`devvit playtest r/the_commons_game_dev`) and open the installed post.\n5. In the subreddit, run the mod menu **\"The Commons: Seed Preseason\"** — six labeled Reckonings + tonight's live round appear (deterministic; re-runnable).\n6. Walk the judge path in **[DEMO.md](./DEMO.md)**.\n\nModerator menu actions: **Seed Preseason · Void Round · Force Settle · Approve Forge Dilemma**. Cron: settle `0 0 * * *`, open `5 0 * * *`, weekly ceremony `0 1 * * 1`.\n\n---\n\n## 📮 Submission checklist\n\nRun `npm run check:submission` before submitting. The demo-post URL stays a\nplaceholder (and the gate stays red) until the post is live.\n\n\u003e **The remaining `[ ]` items need no app approval — do them now.** The Reddit\n\u003e review only unlocks the *public* App Directory listing + icon; judging happens on\n\u003e your (public) test sub, which is entirely in your control.\n\n- App listing: https://developers.reddit.com/apps/the-commons-game\n- Devpost project: https://devpost.com/software/the-commons\n- Demo video: https://www.youtube.com/watch?v=mgUb0PFsIW0\n- Demo post: https://www.reddit.com/r/the_commons_game_dev/s/VqzGS9aO6m\n- [x] `npm run lint` clean\n- [x] `npm run type-check` clean\n- [x] `npm test` green (222/222), `npm run test:coverage` at 100%\n- [x] `npm run build` succeeds\n- [x] Published to the App Directory (`devvit publish`, in review)\n- [x] 60-second demo video recorded \u0026 published (link above)\n- [x] Public repo + Devpost project page linked\n- [x] `r/the_commons_game_dev` set to **Public**, demo post seeded (`Seed Preseason`) and verified against `DEMO.md`\n- [ ] Demo-post URL filled above + Devpost form submitted\n\n## 🚧 What this is not (yet)\n\nThe webview client (`src/client/`) is the honest, playable **core loop** — dilemma → sealed commit → Reckoning — in the midnight-violet design language. It **now includes** the Reckoning reveal choreography (bars sweep in, verdict punches, `ruin` flash), the reveal-first cold open, the live-ticker poll, and the in-app feed of past Reckonings. It is still **not** the full `UI.md` choreography: the commit-side **envelope/wax-seal animation** is unbuilt, and the **Forge** and **Ladders** screens have complete, tested servers (`/api/forge`, `/api/ladders`) but no dedicated screen yet — post-hackathon polish. See `docs/friction-log.md`.\n\n---\n\n## 🔖 Versioning\n\nAutomatic semantic versioning via [semantic-release](https://semantic-release.gitbook.io/):\nevery push to `main` parses [Conventional Commits](https://www.conventionalcommits.org/)\n(`fix:` → patch, `feat:` → minor, `BREAKING CHANGE:` → major) and, when warranted,\nbumps `package.json`, updates `CHANGELOG.md`, tags the commit, and publishes a\nGitHub Release with generated notes (`.github/workflows/release.yml`). No manual\nversion bumps, no npm registry publish (private app).\n\n## 📄 License\n\n[MIT](LICENSE) © 2026 Edy Cu. Built for Reddit's *Games with a Hook* hackathon.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedycutjong%2Fthe-commons","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedycutjong%2Fthe-commons","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedycutjong%2Fthe-commons/lists"}