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.
- typefaster_cli-0.3.1/.github/CODEOWNERS +3 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/workflows/ci.yml +10 -10
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/workflows/codeql.yml +3 -3
- typefaster_cli-0.3.1/.github/workflows/release.yml +145 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.gitignore +1 -0
- typefaster_cli-0.3.1/CLAUDE.md +110 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/CONTRIBUTING.md +19 -0
- typefaster_cli-0.3.1/PKG-INFO +155 -0
- typefaster_cli-0.3.1/README.md +123 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/__init__.py +1 -1
- typefaster_cli-0.3.1/client/typefaster/domain/drills.py +39 -0
- typefaster_cli-0.3.1/client/typefaster/domain/keyboard.py +74 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/models.py +2 -0
- typefaster_cli-0.3.1/client/typefaster/domain/text_modifiers.py +34 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/typing_engine.py +19 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/config.py +3 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/migrations.py +13 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/quote_loader.py +13 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/repository.py +5 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/sqlite_repository.py +34 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/api.py +27 -2
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/commands.py +1 -2
- typefaster_cli-0.3.1/client/typefaster/net/token_store.py +93 -0
- typefaster_cli-0.3.1/client/typefaster/services/coach_service.py +52 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/container.py +9 -1
- typefaster_cli-0.3.1/client/typefaster/services/daily_service.py +46 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/race_service.py +60 -5
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/app.py +13 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/_base.py +5 -1
- typefaster_cli-0.3.1/client/typefaster/ui/screens/account.py +193 -0
- typefaster_cli-0.3.1/client/typefaster/ui/screens/coach.py +112 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/daily.py +12 -6
- typefaster_cli-0.3.1/client/typefaster/ui/screens/lobby_browser.py +150 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/main_menu.py +19 -1
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/online_race.py +17 -7
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/results.py +16 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/settings.py +13 -0
- typefaster_cli-0.3.1/docs/DEEPDIVE.md +305 -0
- typefaster_cli-0.3.1/docs/SECURITY-REVIEW.md +74 -0
- typefaster_cli-0.3.1/docs/ddos-protection.md +94 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/pyproject.toml +1 -1
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/Dockerfile +5 -2
- typefaster_cli-0.3.1/server/app/abuse.py +81 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/config.py +7 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/main.py +31 -12
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/repositories.py +14 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/lobbies.py +10 -3
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/oauth.py +1 -1
- typefaster_cli-0.3.1/server/tests/test_abuse.py +87 -0
- typefaster_cli-0.3.1/tests/integration/test_daily_streak.py +53 -0
- typefaster_cli-0.3.1/tests/integration/test_migrations.py +49 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_race_service.py +51 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_sqlite_repository.py +33 -0
- typefaster_cli-0.3.1/tests/integration/test_ui_smoke.py +165 -0
- typefaster_cli-0.3.1/tests/unit/test_coach.py +63 -0
- typefaster_cli-0.3.1/tests/unit/test_drills.py +44 -0
- typefaster_cli-0.3.1/tests/unit/test_net.py +153 -0
- typefaster_cli-0.3.1/tests/unit/test_text_modifiers.py +36 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_typing_engine.py +31 -0
- typefaster_cli-0.1.4/.github/workflows/release.yml +0 -82
- typefaster_cli-0.1.4/PKG-INFO +0 -168
- typefaster_cli-0.1.4/README.md +0 -136
- typefaster_cli-0.1.4/client/typefaster/net/token_store.py +0 -49
- typefaster_cli-0.1.4/client/typefaster/services/daily_service.py +0 -23
- typefaster_cli-0.1.4/tests/integration/test_migrations.py +0 -26
- typefaster_cli-0.1.4/tests/integration/test_ui_smoke.py +0 -55
- typefaster_cli-0.1.4/tests/unit/test_net.py +0 -43
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.dockerignore +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.env.example +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/.github/dependabot.yml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/LICENSE +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/Makefile +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/SECURITY.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/Dockerfile +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/__main__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/assets/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/assets/quotes.json +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/cli.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/anti_cheat.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/calculators.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/errors.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/domain/ghost.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/clock.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/db.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/paths.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/infra/replay_store.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/net/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/ghost_service.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/profile_service.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/services/stats_service.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/online_app.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/help.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/history.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/leaderboard.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/practice.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/profile.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/race.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/screens/stats.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/theme.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/bigtext.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/live_stats.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/progress_bars.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/client/typefaster/ui/widgets/typing_field.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docker-compose.yml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/INSTALL.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/RELEASING.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/api-spec.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/architecture.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deploy-fly.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deploy-oracle.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/deployment.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/online-setup.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/redis-schema.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/roadmap.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/sqlite-schema.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/ui-design.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/docs/websocket-protocol.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/fly.toml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/README.md +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/nginx/nginx.conf +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/infra/redis/redis.conf +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/packaging/homebrew/typefaster.rb +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/scripts/deploy-oracle.sh +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/scripts/seed_quotes.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/deps.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/logging_config.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/quotes.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/redis_keys.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/auth.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/health.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/routers/leaderboards.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/security.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/ws/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/app/ws/manager.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/pyproject.toml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/conftest.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_auth.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_health_leaderboards.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_lobbies.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_oauth.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_scoring_anticheat.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/server/tests/test_ws_race.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/pyproject.toml +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/__init__.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/anti_cheat.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/dto.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/events.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/shared/typefaster_shared/scoring.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/conftest.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/fixtures/.gitkeep +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_container_and_cli.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_profile_stats.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/integration/test_race_timing.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_anti_cheat.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_calculators.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_config.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_ghost.py +0 -0
- {typefaster_cli-0.1.4 → typefaster_cli-0.3.1}/tests/unit/test_quote_loader.py +0 -0
|
@@ -18,10 +18,10 @@ jobs:
|
|
|
18
18
|
matrix:
|
|
19
19
|
python-version: ["3.11", "3.12"]
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
22
|
|
|
23
23
|
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
-
uses: actions/setup-python@
|
|
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@
|
|
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@
|
|
56
|
-
- uses: actions/setup-python@
|
|
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@
|
|
74
|
-
- uses: actions/setup-python@
|
|
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@
|
|
90
|
-
- uses: actions/setup-python@
|
|
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@
|
|
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@
|
|
20
|
-
- uses: github/codeql-action/init@
|
|
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@
|
|
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 }}
|
|
@@ -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
|
+
[](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml)
|
|
36
|
+
[](https://pypi.org/project/typefaster-cli/)
|
|
37
|
+
[](https://www.python.org/downloads/)
|
|
38
|
+
[](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
|
+
[](https://github.com/Anoshor/typefaster-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/typefaster-cli/)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](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**.
|