typefaster-cli 0.1.4__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. typefaster_cli-0.3.1/.github/CODEOWNERS +3 -0
  2. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/workflows/ci.yml +10 -10
  3. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/workflows/codeql.yml +3 -3
  4. typefaster_cli-0.3.1/.github/workflows/release.yml +145 -0
  5. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.gitignore +1 -0
  6. typefaster_cli-0.3.1/CLAUDE.md +110 -0
  7. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/CONTRIBUTING.md +19 -0
  8. typefaster_cli-0.3.1/PKG-INFO +155 -0
  9. typefaster_cli-0.3.1/README.md +123 -0
  10. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/__init__.py +1 -1
  11. typefaster_cli-0.3.1/client/typefaster/domain/drills.py +39 -0
  12. typefaster_cli-0.3.1/client/typefaster/domain/keyboard.py +74 -0
  13. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/models.py +2 -0
  14. typefaster_cli-0.3.1/client/typefaster/domain/text_modifiers.py +34 -0
  15. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/typing_engine.py +19 -0
  16. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/config.py +3 -0
  17. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/migrations.py +13 -0
  18. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/quote_loader.py +13 -0
  19. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/repository.py +5 -0
  20. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/sqlite_repository.py +34 -0
  21. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/api.py +27 -2
  22. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/commands.py +1 -2
  23. typefaster_cli-0.3.1/client/typefaster/net/token_store.py +93 -0
  24. typefaster_cli-0.3.1/client/typefaster/services/coach_service.py +52 -0
  25. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/container.py +9 -1
  26. typefaster_cli-0.3.1/client/typefaster/services/daily_service.py +46 -0
  27. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/race_service.py +60 -5
  28. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/app.py +13 -0
  29. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/_base.py +5 -1
  30. typefaster_cli-0.3.1/client/typefaster/ui/screens/account.py +193 -0
  31. typefaster_cli-0.3.1/client/typefaster/ui/screens/coach.py +112 -0
  32. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/daily.py +12 -6
  33. typefaster_cli-0.3.1/client/typefaster/ui/screens/lobby_browser.py +150 -0
  34. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/main_menu.py +19 -1
  35. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/online_race.py +17 -7
  36. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/results.py +16 -0
  37. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/settings.py +13 -0
  38. typefaster_cli-0.3.1/docs/DEEPDIVE.md +305 -0
  39. typefaster_cli-0.3.1/docs/SECURITY-REVIEW.md +74 -0
  40. typefaster_cli-0.3.1/docs/ddos-protection.md +94 -0
  41. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/pyproject.toml +1 -1
  42. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/Dockerfile +5 -2
  43. typefaster_cli-0.3.1/server/app/abuse.py +81 -0
  44. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/config.py +7 -0
  45. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/main.py +31 -12
  46. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/repositories.py +14 -0
  47. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/lobbies.py +10 -3
  48. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/oauth.py +1 -1
  49. typefaster_cli-0.3.1/server/tests/test_abuse.py +87 -0
  50. typefaster_cli-0.3.1/tests/integration/test_daily_streak.py +53 -0
  51. typefaster_cli-0.3.1/tests/integration/test_migrations.py +49 -0
  52. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_race_service.py +51 -0
  53. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_sqlite_repository.py +33 -0
  54. typefaster_cli-0.3.1/tests/integration/test_ui_smoke.py +165 -0
  55. typefaster_cli-0.3.1/tests/unit/test_coach.py +63 -0
  56. typefaster_cli-0.3.1/tests/unit/test_drills.py +44 -0
  57. typefaster_cli-0.3.1/tests/unit/test_net.py +153 -0
  58. typefaster_cli-0.3.1/tests/unit/test_text_modifiers.py +36 -0
  59. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_typing_engine.py +31 -0
  60. typefaster_cli-0.1.4/.github/workflows/release.yml +0 -82
  61. typefaster_cli-0.1.4/PKG-INFO +0 -168
  62. typefaster_cli-0.1.4/README.md +0 -136
  63. typefaster_cli-0.1.4/client/typefaster/net/token_store.py +0 -49
  64. typefaster_cli-0.1.4/client/typefaster/services/daily_service.py +0 -23
  65. typefaster_cli-0.1.4/tests/integration/test_migrations.py +0 -26
  66. typefaster_cli-0.1.4/tests/integration/test_ui_smoke.py +0 -55
  67. typefaster_cli-0.1.4/tests/unit/test_net.py +0 -43
  68. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.dockerignore +0 -0
  69. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.env.example +0 -0
  70. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  71. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  72. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  73. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  74. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/dependabot.yml +0 -0
  75. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/LICENSE +0 -0
  76. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/Makefile +0 -0
  77. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/SECURITY.md +0 -0
  78. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/Dockerfile +0 -0
  79. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/__main__.py +0 -0
  80. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/assets/__init__.py +0 -0
  81. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/assets/quotes.json +0 -0
  82. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/cli.py +0 -0
  83. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/__init__.py +0 -0
  84. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/anti_cheat.py +0 -0
  85. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/calculators.py +0 -0
  86. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/errors.py +0 -0
  87. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/ghost.py +0 -0
  88. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/__init__.py +0 -0
  89. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/clock.py +0 -0
  90. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/db.py +0 -0
  91. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/paths.py +0 -0
  92. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/replay_store.py +0 -0
  93. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/__init__.py +0 -0
  94. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/__init__.py +0 -0
  95. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/ghost_service.py +0 -0
  96. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/profile_service.py +0 -0
  97. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/stats_service.py +0 -0
  98. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/__init__.py +0 -0
  99. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/online_app.py +0 -0
  100. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/__init__.py +0 -0
  101. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/help.py +0 -0
  102. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/history.py +0 -0
  103. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/leaderboard.py +0 -0
  104. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/practice.py +0 -0
  105. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/profile.py +0 -0
  106. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/race.py +0 -0
  107. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/stats.py +0 -0
  108. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/theme.py +0 -0
  109. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/__init__.py +0 -0
  110. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/bigtext.py +0 -0
  111. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/live_stats.py +0 -0
  112. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/progress_bars.py +0 -0
  113. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/typing_field.py +0 -0
  114. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docker-compose.yml +0 -0
  115. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/INSTALL.md +0 -0
  116. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/RELEASING.md +0 -0
  117. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/api-spec.md +0 -0
  118. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/architecture.md +0 -0
  119. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deploy-fly.md +0 -0
  120. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deploy-oracle.md +0 -0
  121. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deployment.md +0 -0
  122. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/online-setup.md +0 -0
  123. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/redis-schema.md +0 -0
  124. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/roadmap.md +0 -0
  125. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/sqlite-schema.md +0 -0
  126. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/ui-design.md +0 -0
  127. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/websocket-protocol.md +0 -0
  128. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/fly.toml +0 -0
  129. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/README.md +0 -0
  130. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/nginx/nginx.conf +0 -0
  131. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/redis/redis.conf +0 -0
  132. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/packaging/homebrew/typefaster.rb +0 -0
  133. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/scripts/deploy-oracle.sh +0 -0
  134. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/scripts/seed_quotes.py +0 -0
  135. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/__init__.py +0 -0
  136. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/deps.py +0 -0
  137. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/logging_config.py +0 -0
  138. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/quotes.py +0 -0
  139. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/redis_keys.py +0 -0
  140. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/__init__.py +0 -0
  141. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/auth.py +0 -0
  142. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/health.py +0 -0
  143. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/leaderboards.py +0 -0
  144. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/security.py +0 -0
  145. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/ws/__init__.py +0 -0
  146. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/ws/manager.py +0 -0
  147. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/pyproject.toml +0 -0
  148. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/__init__.py +0 -0
  149. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/conftest.py +0 -0
  150. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_auth.py +0 -0
  151. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_health_leaderboards.py +0 -0
  152. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_lobbies.py +0 -0
  153. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_oauth.py +0 -0
  154. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_scoring_anticheat.py +0 -0
  155. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_ws_race.py +0 -0
  156. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/pyproject.toml +0 -0
  157. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/__init__.py +0 -0
  158. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/anti_cheat.py +0 -0
  159. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/dto.py +0 -0
  160. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/events.py +0 -0
  161. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/scoring.py +0 -0
  162. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/conftest.py +0 -0
  163. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/fixtures/.gitkeep +0 -0
  164. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_container_and_cli.py +0 -0
  165. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_profile_stats.py +0 -0
  166. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_race_timing.py +0 -0
  167. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_anti_cheat.py +0 -0
  168. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_calculators.py +0 -0
  169. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_config.py +0 -0
  170. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_ghost.py +0 -0
  171. {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_quote_loader.py +0 -0
@@ -0,0 +1,3 @@
1
+ # Every change requests a review from the project owner by default.
2
+ # https://docs.github.com/articles/about-code-owners
3
+ * @Anoshor
@@ -18,10 +18,10 @@ jobs:
18
18
  matrix:
19
19
  python-version: ["3.11", "3.12"]
20
20
  steps:
21
- - uses: actions/checkout@v4
21
+ - uses: actions/checkout@v7
22
22
 
23
23
  - name: Set up Python ${{ matrix.python-version }}
24
- uses: actions/setup-python@v5
24
+ uses: actions/setup-python@v6
25
25
  with:
26
26
  python-version: ${{ matrix.python-version }}
27
27
  cache: pip
@@ -43,7 +43,7 @@ jobs:
43
43
 
44
44
  - name: Upload coverage artifact
45
45
  if: matrix.python-version == '3.12'
46
- uses: actions/upload-artifact@v4
46
+ uses: actions/upload-artifact@v7
47
47
  with:
48
48
  name: coverage
49
49
  path: coverage.xml
@@ -52,8 +52,8 @@ jobs:
52
52
  name: server lint · test
53
53
  runs-on: ubuntu-latest
54
54
  steps:
55
- - uses: actions/checkout@v4
56
- - uses: actions/setup-python@v5
55
+ - uses: actions/checkout@v7
56
+ - uses: actions/setup-python@v6
57
57
  with:
58
58
  python-version: "3.12"
59
59
  cache: pip
@@ -70,8 +70,8 @@ jobs:
70
70
  name: dependency audit (pip-audit)
71
71
  runs-on: ubuntu-latest
72
72
  steps:
73
- - uses: actions/checkout@v4
74
- - uses: actions/setup-python@v5
73
+ - uses: actions/checkout@v7
74
+ - uses: actions/setup-python@v6
75
75
  with:
76
76
  python-version: "3.12"
77
77
  cache: pip
@@ -86,14 +86,14 @@ jobs:
86
86
  runs-on: ubuntu-latest
87
87
  needs: [quality, server]
88
88
  steps:
89
- - uses: actions/checkout@v4
90
- - uses: actions/setup-python@v5
89
+ - uses: actions/checkout@v7
90
+ - uses: actions/setup-python@v6
91
91
  with:
92
92
  python-version: "3.12"
93
93
  cache: pip
94
94
  - run: python -m pip install build
95
95
  - run: python -m build
96
- - uses: actions/upload-artifact@v4
96
+ - uses: actions/upload-artifact@v7
97
97
  with:
98
98
  name: dist
99
99
  path: dist/*
@@ -16,8 +16,8 @@ jobs:
16
16
  actions: read
17
17
  contents: read
18
18
  steps:
19
- - uses: actions/checkout@v4
20
- - uses: github/codeql-action/init@v3
19
+ - uses: actions/checkout@v7
20
+ - uses: github/codeql-action/init@v4
21
21
  with:
22
22
  languages: python
23
- - uses: github/codeql-action/analyze@v3
23
+ - uses: github/codeql-action/analyze@v4
@@ -0,0 +1,145 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI and creates a GitHub Release whenever you push a version tag
4
+ # (e.g. `git tag v0.1.0 && git push origin v0.1.0`).
5
+ #
6
+ # Uses PyPI Trusted Publishing (OIDC) — no API tokens stored in the repo. You
7
+ # must configure the trusted publisher once on PyPI (see docs/RELEASING.md).
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ permissions:
14
+ contents: write # create the GitHub Release
15
+ id-token: write # OIDC token for PyPI Trusted Publishing
16
+ packages: write # push the server image to GHCR
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+ - uses: actions/setup-python@v6
24
+ with:
25
+ python-version: "3.12"
26
+ - run: python -m pip install build
27
+ - run: python -m build # creates dist/*.whl and dist/*.tar.gz
28
+ - run: python -m pip install twine && twine check dist/*
29
+ - uses: actions/upload-artifact@v7
30
+ with:
31
+ name: dist
32
+ path: dist/*
33
+
34
+ publish-pypi:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment: pypi # optional: add manual approval in repo settings
38
+ steps:
39
+ - uses: actions/download-artifact@v8
40
+ with:
41
+ name: dist
42
+ path: dist
43
+ - uses: pypa/gh-action-pypi-publish@release/v1
44
+
45
+ github-release:
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/download-artifact@v8
50
+ with:
51
+ name: dist
52
+ path: dist
53
+ - uses: softprops/action-gh-release@v3
54
+ with:
55
+ generate_release_notes: true
56
+ files: dist/*
57
+
58
+ bump-homebrew:
59
+ name: bump Homebrew tap formula
60
+ needs: publish-pypi # only bump once the sdist is live on PyPI
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - name: Check out the tap repo
64
+ uses: actions/checkout@v7
65
+ with:
66
+ repository: Anoshor/homebrew-typefaster
67
+ token: ${{ secrets.HOMEBREW_TAP_TOKEN }} # fine-grained PAT, contents:write on the tap
68
+ path: tap
69
+
70
+ - name: Update url + sha256 from PyPI
71
+ env:
72
+ VERSION: ${{ github.ref_name }} # e.g. v0.2.1
73
+ run: |
74
+ python3 - <<'PY'
75
+ import json, os, re, sys, time, urllib.request
76
+
77
+ version = os.environ["VERSION"].lstrip("v")
78
+ api = f"https://pypi.org/pypi/typefaster-cli/{version}/json"
79
+
80
+ # PyPI's JSON index can lag a few seconds behind the upload; retry.
81
+ data = None
82
+ for attempt in range(10):
83
+ try:
84
+ with urllib.request.urlopen(api) as r:
85
+ data = json.load(r)
86
+ break
87
+ except Exception as e: # noqa: BLE001
88
+ print(f"attempt {attempt+1}: {e}; retrying in 6s")
89
+ time.sleep(6)
90
+ if data is None:
91
+ sys.exit(f"could not fetch {api}")
92
+
93
+ sdist = next(u for u in data["urls"] if u["packagetype"] == "sdist")
94
+ url, sha = sdist["url"], sdist["digests"]["sha256"]
95
+ print(f"new url: {url}\nnew sha: {sha}")
96
+
97
+ path = "tap/Formula/typefaster.rb"
98
+ text = open(path, encoding="utf-8").read()
99
+ # Only touch the top-level url/sha256 (everything before the first resource).
100
+ head, sep, rest = text.partition("\n resource ")
101
+ head = re.sub(r'url "[^"]*"', f'url "{url}"', head, count=1)
102
+ head = re.sub(r'sha256 "[^"]*"', f'sha256 "{sha}"', head, count=1)
103
+ open(path, "w", encoding="utf-8").write(head + sep + rest)
104
+ PY
105
+
106
+ - name: Commit & push the bump
107
+ working-directory: tap
108
+ env:
109
+ VERSION: ${{ github.ref_name }}
110
+ run: |
111
+ git config user.name "github-actions[bot]"
112
+ git config user.email "github-actions[bot]@users.noreply.github.com"
113
+ if git diff --quiet; then
114
+ echo "Formula already up to date — nothing to bump."
115
+ exit 0
116
+ fi
117
+ git add Formula/typefaster.rb
118
+ git commit -m "typefaster ${VERSION#v}"
119
+ git push
120
+
121
+ server-image:
122
+ name: build & push server image (GHCR)
123
+ needs: build
124
+ runs-on: ubuntu-latest
125
+ steps:
126
+ - uses: actions/checkout@v7
127
+ - uses: docker/login-action@v4
128
+ with:
129
+ registry: ghcr.io
130
+ username: ${{ github.actor }}
131
+ password: ${{ secrets.GITHUB_TOKEN }}
132
+ - id: meta
133
+ uses: docker/metadata-action@v6
134
+ with:
135
+ images: ghcr.io/${{ github.repository_owner }}/typefaster-server
136
+ tags: |
137
+ type=semver,pattern={{version}}
138
+ type=raw,value=latest
139
+ - uses: docker/build-push-action@v7
140
+ with:
141
+ context: .
142
+ file: server/Dockerfile
143
+ push: true
144
+ tags: ${{ steps.meta.outputs.tags }}
145
+ labels: ${{ steps.meta.outputs.labels }}
@@ -30,3 +30,4 @@ typefaster.db*
30
30
  .vscode/
31
31
  .DS_Store
32
32
  .env
33
+ docs/MARKETING.md
@@ -0,0 +1,110 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for working in this repo. TYPEFASTER is a terminal-first typing game:
4
+ an offline TUI plus an online real-time multiplayer server. Distributed via PyPI
5
+ (`typefaster-cli`) and a Homebrew tap. Hosted at $0 on an Oracle Always-Free VM.
6
+
7
+ ## Repo layout
8
+
9
+ ```
10
+ client/typefaster/ the CLI/TUI app (domain · services · infra · ui · net · assets)
11
+ server/app/ online server (FastAPI · WebSockets · Redis · routers · ws)
12
+ shared/typefaster_shared/ schemas (dto), WS events, scoring, anti-cheat — used by both
13
+ tests/ client tests (unit/ + integration/)
14
+ server/tests/ server tests (fakeredis + Starlette TestClient)
15
+ docs/ architecture, schemas, WS protocol, deploy, security, releasing
16
+ Formula lives in a SEPARATE repo: Anoshor/homebrew-typefaster (the tap).
17
+ ```
18
+
19
+ ## Architecture & layering (do not violate)
20
+
21
+ Clean layering, enforced by convention:
22
+
23
+ - **`domain/`** is pure: dataclasses, enums, and deterministic logic. It must NOT
24
+ import Rich/Textual/SQLite/httpx. Time is injected (`Clock`), so logic is
25
+ unit-testable without a real clock.
26
+ - **`services/`** orchestrate domain + repositories. UI-free, reusable headlessly.
27
+ - **`infra/`** (SQLite, config, paths, quote loader) and **`ui/`** (Textual)
28
+ depend inward only.
29
+ - **Repository port**: `infra/repository.py` is a `typing.Protocol`; the SQLite
30
+ impl satisfies it. Services depend on the Protocol, not SQLite.
31
+ - **Server is authoritative**: the WS server re-scores every race from raw inputs
32
+ using `shared/typefaster_shared/scoring.py` (the SAME math as the client's
33
+ `domain/calculators.py`) and never trusts client-reported WPM/accuracy.
34
+
35
+ When adding code, put it in the right layer and keep `domain/` pure.
36
+
37
+ ## Commands
38
+
39
+ Client (from repo root; uses `client/` on PYTHONPATH via the Makefile):
40
+ ```bash
41
+ make install # editable install + dev deps into a venv
42
+ make play # launch the TUI (offline)
43
+ make check # ruff + mypy --strict + pytest ← run before every PR
44
+ make test # pytest only
45
+ make up / down # start/stop the online stack (redis + server) via Docker
46
+ ```
47
+
48
+ Server (separate package; mirror CI exactly):
49
+ ```bash
50
+ cd server && pytest # server suite (fakeredis)
51
+ ruff check server shared --select E,F,I,UP,B --ignore UP042 # the server lint gate
52
+ ```
53
+
54
+ Notes:
55
+ - The package lives at `client/typefaster`; `make` sets `PYTHONPATH=client`. If
56
+ running tools directly, prefix with `PYTHONPATH=client`.
57
+ - Client gate = ruff + black + mypy --strict + pytest. **Server CI runs ruff +
58
+ pytest only — no mypy** (the server has known pre-existing mypy noise from the
59
+ untyped `typefaster_shared` import; don't chase it).
60
+ - Tests use markers: `unit`, `integration`, `ui` (Textual pilot smoke tests).
61
+
62
+ ## Conventions
63
+
64
+ - Add/adjust tests for any behavior change. UI gets Textual pilot smoke tests in
65
+ `tests/integration/test_ui_smoke.py`; logic gets unit/integration tests.
66
+ - Adding a TUI screen: create it under `ui/screens/`, register it in `_PANELS`
67
+ in `ui/app.py`, and add a menu row in `ui/screens/main_menu.py`. Read-only
68
+ panels subclass `PanelScreen` (`ui/screens/_base.py`); override `body()`.
69
+ - Adding a SQLite migration: append a `(version, sql)` tuple to `_MIGRATIONS` in
70
+ `infra/migrations.py` (use `IF NOT EXISTS`; never edit a shipped migration).
71
+ - Adding quotes: edit `scripts/seed_quotes.py`, run it, commit the regenerated
72
+ `client/typefaster/assets/quotes.json`.
73
+ - Conventional, focused commits. Don't commit generated artifacts (git-ignored).
74
+
75
+ ## Release flow (automated)
76
+
77
+ Per release, edit the version in **two** files that must stay in sync:
78
+ `pyproject.toml` and `client/typefaster/__init__.py`. Then:
79
+ ```bash
80
+ git tag vX.Y.Z && git push origin vX.Y.Z
81
+ ```
82
+ The tag triggers `.github/workflows/release.yml`, which: builds → publishes to
83
+ PyPI (OIDC Trusted Publishing, gated by the `pypi` environment approval) →
84
+ creates a GitHub Release → **auto-bumps the Homebrew tap formula** (needs the
85
+ `HOMEBREW_TAP_TOKEN` secret) → pushes the server image to GHCR.
86
+
87
+ ## Git / PR workflow
88
+
89
+ `main` is protected: PRs require a passing CI + a CODEOWNERS review before merge;
90
+ no force-push/deletion. As repo admin you can bypass for your own pushes, but
91
+ prefer feature branches. Branch from `main`; do not commit directly unless asked.
92
+
93
+ ## Key facts / gotchas
94
+
95
+ - **Public server**: client default `server_url` lives in
96
+ `client/typefaster/net/token_store.py` (`DEFAULT_SERVER_URL`). It self-heals a
97
+ dead `*.trycloudflare.com` URL to the default on load.
98
+ - **Online is server-authoritative** with per-IP rate limiting on auth/oauth and
99
+ app-layer flood guards (see `docs/ddos-protection.md`). A true volumetric DDoS
100
+ needs an upstream edge (Cloudflare) — app code can't stop it.
101
+ - **Daily challenge** is keyed by UTC date (`datetime.now(UTC).date()`), matching
102
+ how results are filed — don't switch it to local `date.today()`.
103
+ - **Secrets are never committed.** JWT secret + OAuth client IDs/secret live only
104
+ in `/opt/typefaster/.env` on the VM. `.env` and `docs/MARKETING.md` are
105
+ git-ignored.
106
+ - **Two repos by design**: the code repo and the mandatory `homebrew-typefaster`
107
+ tap (one formula file, auto-bumped by CI). The tap is not a second project to
108
+ maintain.
109
+ - The TUI footer credits "Anoshor Paul" intentionally; keep it.
110
+ - $0 hosting is a project goal — prefer free tiers; flag anything that adds cost.
@@ -48,6 +48,25 @@ Quality gates (all must pass):
48
48
  - **mypy --strict** — types (the `typefaster` package)
49
49
  - **pytest** — tests; add/adjust tests for any behavior change
50
50
 
51
+ ## Pull request process
52
+
53
+ `main` is a protected branch — you can't push to it directly. Work on a fork or
54
+ a feature branch and open a PR:
55
+
56
+ 1. **Fork** the repo (or branch, if you're a collaborator) and create a topic
57
+ branch: `git checkout -b fix/ghost-timing`.
58
+ 2. Make your change with tests, then run `make check` locally.
59
+ 3. **Open a PR** against `main`. CI runs automatically.
60
+ 4. **All checks must be green** — `lint · type · test (3.11/3.12)`,
61
+ `server lint · test`, and `dependency audit (pip-audit)`.
62
+ 5. A **maintainer review is required** (you'll be auto-requested via
63
+ `CODEOWNERS`), and any review conversations must be resolved before merge.
64
+ 6. Keep PRs small and focused; squash-friendly, conventional commit messages
65
+ help.
66
+
67
+ Dependency and GitHub-Actions updates are proposed automatically by Dependabot —
68
+ no action needed from contributors.
69
+
51
70
  ## Guidelines
52
71
 
53
72
  - **Keep the domain pure** and deterministic (time is injected via `Clock`).
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: typefaster-cli
3
+ Version: 0.3.1
4
+ Summary: A terminal-first typing game inspired by MonkeyType and TypeRacer.
5
+ Project-URL: Homepage, https://github.com/Anoshor/typefaster-cli
6
+ Project-URL: Repository, https://github.com/Anoshor/typefaster-cli
7
+ Author: Anoshor Paul
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: cli,game,monkeytype,terminal,tui,typeracer,typing,wpm
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Games/Entertainment
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: platformdirs>=4.2
19
+ Requires-Dist: rich>=13.7
20
+ Requires-Dist: textual>=0.60
21
+ Requires-Dist: typer>=0.12
22
+ Requires-Dist: websockets>=12.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: black>=24.0; extra == 'dev'
25
+ Requires-Dist: mypy>=1.10; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.5; extra == 'dev'
30
+ Requires-Dist: textual-dev>=1.5; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # ⌨ TYPEFASTER
34
+
35
+ [![CI](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/typefaster-cli)](https://pypi.org/project/typefaster-cli/)
37
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
38
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
39
+
40
+ A **terminal-first** typing game inspired by MonkeyType and TypeRacer — race
41
+ quotes, beat your ghost, climb leaderboards, and play **live multiplayer** with
42
+ friends. Pure TUI, no browser, no GUI.
43
+
44
+ ```bash
45
+ brew install Anoshor/typefaster/typefaster # or: pipx install typefaster-cli
46
+ typefaster
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Install
52
+
53
+ **Homebrew** (macOS / Linux)
54
+ ```bash
55
+ brew install Anoshor/typefaster/typefaster
56
+ # or tap once, then use the short name:
57
+ brew tap anoshor/typefaster && brew install typefaster
58
+ ```
59
+
60
+ **pipx** (any OS with Python 3.11+) — fastest install
61
+ ```bash
62
+ pipx install typefaster-cli
63
+ ```
64
+
65
+ Verify: `typefaster version` · Upgrade: `brew upgrade typefaster` or `pipx upgrade typefaster-cli`
66
+
67
+ ## Play (offline — no account, no internet)
68
+
69
+ Just run it:
70
+ ```bash
71
+ typefaster
72
+ ```
73
+ Keyboard-only menu:
74
+ - **Quick Race** — a fresh random quote each time; race your personal-best ghost.
75
+ - **Time Attack** — type for 30 / 60 / 120s (←/→ to change the duration inline).
76
+ - **Practice** — pick a mode/ghost.
77
+ - **Daily Challenge** — same quote for everyone each day, local leaderboard.
78
+ - **🎯 Typing Coach** — see the keys you miss most, a per-key accuracy heatmap, and
79
+ finger-position tips; press **d** to launch a drill weighted toward your weak
80
+ keys. All computed locally — no account, no internet, no AI service.
81
+ - **Stats / History / Profile / Leaderboard / Settings**.
82
+
83
+ **Practice modes (MonkeyType-style):** in **Settings**, toggle **Lowercase only**
84
+ or **Words only** (pure a–z, no punctuation or numbers) to strip the text down.
85
+
86
+ Live WPM, accuracy, progress, and an animated ghost bar. Backspace corrects
87
+ mistakes (original errors still count, MonkeyType-style). All progress is saved
88
+ locally in SQLite.
89
+
90
+ Direct commands too:
91
+ ```bash
92
+ typefaster race # quote race
93
+ typefaster race --mode time --time 60
94
+ typefaster daily
95
+ typefaster stats | typefaster history
96
+ ```
97
+
98
+ ## Play online (multiplayer lobbies)
99
+
100
+ It works out of the box against the public server — **no setup**. From the main
101
+ menu pick **Account** to register/login, then **Play Online**:
102
+
103
+ ```text
104
+ Account → Register / Login (password, GitHub, or Google)
105
+ Play Online → ➕ Create a lobby → share the join code
106
+ → or type a friend's code + Enter to join
107
+ ```
108
+ In the waiting room press **R** to ready; the **server** runs the countdown,
109
+ sends everyone the same quote, shows live progress bars, and scores results
110
+ authoritatively (with anti-cheat). **Esc** leaves.
111
+
112
+ Prefer the CLI?
113
+ ```bash
114
+ typefaster register <name> # or: typefaster login --github / --google
115
+ typefaster lobby create --name "Friday" --time 60
116
+ typefaster lobby join ABC123
117
+ typefaster leaderboard global # global | daily | weekly
118
+ ```
119
+
120
+ ## Self-host the server (optional)
121
+
122
+ The game ships pointing at a public server, but you can run your own:
123
+ ```bash
124
+ git clone https://github.com/Anoshor/typefaster-cli && cd typefaster-cli
125
+ cp .env.example .env # set TYPEFASTER_JWT_SECRET
126
+ make up # Redis + FastAPI server on :8000 (Docker)
127
+ typefaster config set-server http://localhost:8000
128
+ ```
129
+ Deploy guides: [`docs/deploy-oracle.md`](docs/deploy-oracle.md) (free 24/7 VM) ·
130
+ [`docs/deploy-fly.md`](docs/deploy-fly.md) · TLS + hardening in
131
+ [`docs/SECURITY-REVIEW.md`](docs/SECURITY-REVIEW.md).
132
+
133
+ ## How it works
134
+
135
+ - **Client**: Python · Typer (CLI) · Textual + Rich (TUI) · SQLite (local
136
+ progress) · httpx + websockets (online).
137
+ - **Server**: FastAPI · WebSockets · Redis · Pydantic — **server-authoritative**
138
+ race timing and scoring.
139
+ - Deep dive: [`docs/DEEPDIVE.md`](docs/DEEPDIVE.md) · architecture:
140
+ [`docs/architecture.md`](docs/architecture.md).
141
+
142
+ ## Develop
143
+
144
+ ```bash
145
+ make install # editable install + dev deps
146
+ make play # run it
147
+ make check # ruff + mypy + pytest (CI parity)
148
+ ```
149
+ Contributions welcome! `main` is protected — fork, branch, and open a PR; CI must
150
+ pass and a maintainer review is required before merge. Full guide:
151
+ [CONTRIBUTING.md](CONTRIBUTING.md).
152
+
153
+ ## License
154
+
155
+ MIT — see [LICENSE](LICENSE). Crafted by **Anoshor Paul**.
@@ -0,0 +1,123 @@
1
+ # ⌨ TYPEFASTER
2
+
3
+ [![CI](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/typefaster-cli)](https://pypi.org/project/typefaster-cli/)
5
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+
8
+ A **terminal-first** typing game inspired by MonkeyType and TypeRacer — race
9
+ quotes, beat your ghost, climb leaderboards, and play **live multiplayer** with
10
+ friends. Pure TUI, no browser, no GUI.
11
+
12
+ ```bash
13
+ brew install Anoshor/typefaster/typefaster # or: pipx install typefaster-cli
14
+ typefaster
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ **Homebrew** (macOS / Linux)
22
+ ```bash
23
+ brew install Anoshor/typefaster/typefaster
24
+ # or tap once, then use the short name:
25
+ brew tap anoshor/typefaster && brew install typefaster
26
+ ```
27
+
28
+ **pipx** (any OS with Python 3.11+) — fastest install
29
+ ```bash
30
+ pipx install typefaster-cli
31
+ ```
32
+
33
+ Verify: `typefaster version` · Upgrade: `brew upgrade typefaster` or `pipx upgrade typefaster-cli`
34
+
35
+ ## Play (offline — no account, no internet)
36
+
37
+ Just run it:
38
+ ```bash
39
+ typefaster
40
+ ```
41
+ Keyboard-only menu:
42
+ - **Quick Race** — a fresh random quote each time; race your personal-best ghost.
43
+ - **Time Attack** — type for 30 / 60 / 120s (←/→ to change the duration inline).
44
+ - **Practice** — pick a mode/ghost.
45
+ - **Daily Challenge** — same quote for everyone each day, local leaderboard.
46
+ - **🎯 Typing Coach** — see the keys you miss most, a per-key accuracy heatmap, and
47
+ finger-position tips; press **d** to launch a drill weighted toward your weak
48
+ keys. All computed locally — no account, no internet, no AI service.
49
+ - **Stats / History / Profile / Leaderboard / Settings**.
50
+
51
+ **Practice modes (MonkeyType-style):** in **Settings**, toggle **Lowercase only**
52
+ or **Words only** (pure a–z, no punctuation or numbers) to strip the text down.
53
+
54
+ Live WPM, accuracy, progress, and an animated ghost bar. Backspace corrects
55
+ mistakes (original errors still count, MonkeyType-style). All progress is saved
56
+ locally in SQLite.
57
+
58
+ Direct commands too:
59
+ ```bash
60
+ typefaster race # quote race
61
+ typefaster race --mode time --time 60
62
+ typefaster daily
63
+ typefaster stats | typefaster history
64
+ ```
65
+
66
+ ## Play online (multiplayer lobbies)
67
+
68
+ It works out of the box against the public server — **no setup**. From the main
69
+ menu pick **Account** to register/login, then **Play Online**:
70
+
71
+ ```text
72
+ Account → Register / Login (password, GitHub, or Google)
73
+ Play Online → ➕ Create a lobby → share the join code
74
+ → or type a friend's code + Enter to join
75
+ ```
76
+ In the waiting room press **R** to ready; the **server** runs the countdown,
77
+ sends everyone the same quote, shows live progress bars, and scores results
78
+ authoritatively (with anti-cheat). **Esc** leaves.
79
+
80
+ Prefer the CLI?
81
+ ```bash
82
+ typefaster register <name> # or: typefaster login --github / --google
83
+ typefaster lobby create --name "Friday" --time 60
84
+ typefaster lobby join ABC123
85
+ typefaster leaderboard global # global | daily | weekly
86
+ ```
87
+
88
+ ## Self-host the server (optional)
89
+
90
+ The game ships pointing at a public server, but you can run your own:
91
+ ```bash
92
+ git clone https://github.com/Anoshor/typefaster-cli && cd typefaster-cli
93
+ cp .env.example .env # set TYPEFASTER_JWT_SECRET
94
+ make up # Redis + FastAPI server on :8000 (Docker)
95
+ typefaster config set-server http://localhost:8000
96
+ ```
97
+ Deploy guides: [`docs/deploy-oracle.md`](docs/deploy-oracle.md) (free 24/7 VM) ·
98
+ [`docs/deploy-fly.md`](docs/deploy-fly.md) · TLS + hardening in
99
+ [`docs/SECURITY-REVIEW.md`](docs/SECURITY-REVIEW.md).
100
+
101
+ ## How it works
102
+
103
+ - **Client**: Python · Typer (CLI) · Textual + Rich (TUI) · SQLite (local
104
+ progress) · httpx + websockets (online).
105
+ - **Server**: FastAPI · WebSockets · Redis · Pydantic — **server-authoritative**
106
+ race timing and scoring.
107
+ - Deep dive: [`docs/DEEPDIVE.md`](docs/DEEPDIVE.md) · architecture:
108
+ [`docs/architecture.md`](docs/architecture.md).
109
+
110
+ ## Develop
111
+
112
+ ```bash
113
+ make install # editable install + dev deps
114
+ make play # run it
115
+ make check # ruff + mypy + pytest (CI parity)
116
+ ```
117
+ Contributions welcome! `main` is protected — fork, branch, and open a PR; CI must
118
+ pass and a maintainer review is required before merge. Full guide:
119
+ [CONTRIBUTING.md](CONTRIBUTING.md).
120
+
121
+ ## License
122
+
123
+ MIT — see [LICENSE](LICENSE). Crafted by **Anoshor Paul**.
@@ -1,3 +1,3 @@
1
1
  """TYPEFASTER-CLI — a terminal-first typing game."""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.3.1"