Metadata-Version: 2.4
Name: ft-tui
Version: 0.2.2
Summary: Offline-first terminal workspace for École 42 students
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: textual[syntax]<9.0,>=8.2
Requires-Dist: tree-sitter<0.26,>=0.25
Requires-Dist: tree-sitter-markdown<0.6,>=0.5
Requires-Dist: rich<15.0,>=13.7
Requires-Dist: SQLAlchemy<3.0,>=2.0
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: Pillow<14,>=11
Requires-Dist: textual-image[textual]<0.15,>=0.14.1
Requires-Dist: keyring<26.0,>=25.0
Requires-Dist: cryptography<46.0,>=42.0

# 42TUI

## Refresh the Intra project graph

The `project_data.json` used by the local TypeScript graph tools is served by
the **Projects website**, which uses a browser session. An API OAuth bearer
token does not create that website session. To refresh the graph automatically:

1. Install dependencies with `npm install`, then install the browser with
   `npx playwright install chromium`. On a machine with a compatible Chromium
   already installed, set `BROWSER_EXECUTABLE` instead.
2. Copy `.env.example` to `.env` and set `INTRA_LOGIN`, `INTRA_PASSWORD`,
   `CURSUS_ID`, and `CAMPUS_ID`. Keep `.env` private; it is gitignored.
3. Run `bun fetch-graph-auto.ts`. The first login checks **Remember me** if
   the 42 login page offers it. Later runs reuse the saved browser profile in
   `.local/intra-graph-browser/`. If the session expires, the script logs in
   again with the credentials in `.env`.

The output is `graph.<cursus>.<campus>.json`, which is gitignored. A failed
login or non-JSON response leaves the previous graph file in place. For a
scheduled server refresh, run the same command from the repository root with
a private `.env`; do not run overlapping jobs against the same browser profile.
If 42 asks for MFA or presents a challenge, the unattended login stops and
needs a fresh interactive authentication. `HEADLESS=false` opens a browser
for diagnosing a changed login flow.

42TUI is an offline-first Python TUI for managing projects, work time, and notes for École 42
students. Local project data remains in SQLite. Optional 42 account connection uses a centralized
Bun API with PostgreSQL, Redis, browser-based OAuth, and device-bound sessions; the local app does
not receive the user's 42 password or OAuth client secret. See the [OAuth backend operations
runbook](docs/42-oauth-backend-operations.md) to deploy that service.

## Getting started

### Production installation

Once the installer service and first PyPI release are live, install on Linux/macOS:

```sh
curl -fsSL https://tui.42wip.in/install.sh | sh
```

The installer uses uv, obtains Python >=3.12 when needed (including on Ubuntu
22.04 with system Python 3.10), and installs `42tui` in an isolated user environment.
Restart your terminal if needed, then launch `42tui`. Optional Nerd Font installation
is available with `--font`; you must select the font in your terminal settings.

Windows support is experimental; a PowerShell installer, exact-version installs,
upgrades, uninstall and migration from pipx are described in the
[production deployment guide](docs/production-deployment.md).

### Development from this checkout

Python 3.12 or newer is required (including the textual-image dependency).

```sh
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
42tui
```

For editable pipx installation, use the repository's `AGENTS.md` instructions
with an explicitly compatible Python interpreter.

The database is created under `$XDG_DATA_HOME/42tui/42tui.sqlite3` (or
`~/.local/share/42tui/42tui.sqlite3`). Existing projects remain available locally without an
account connection. Connect a 42 account in Settings to synchronize real projects from the
centralized API. Choosing **Save + Set Up Checkout** can clone the development repository URL
entered for a project. Uninstalling the application preserves this local data.

## Project browsing

The catalogue uses official project IDs while SQLite keeps its own IDs for notes,
timers and checkout folders. Shared API metadata, the cursus/campus web graph and
account progression are stored separately. Choose the cursus and campus in Settings.
Common Core views display **old + shared** or **new + shared** exclusively. When the
available evidence cannot select a track, choose one in Settings. Empty quests do
not establish the old track or rank zero. Track preferences
are scoped to the account, cursus and campus.

**My 42 projects** shows account projects in the selected track and the known current
and next Common Core ranks. Rank resolution is automatic and uses quest evidence
first. When rank quests are absent, an active, unvalidated rank-0 project can infer
rank 0 from personal progress (`source: project`). This requires fresh personal
sources and a fresh graph for the selected cursus/campus, a resolved track, and
no validated rank-0 project, higher-rank attempt, specialization attempt or missing
classification. Empty history alone never implies rank 0. Saved manual ranks are
ignored. Project ranks are inferred from graph rules, not official API levels.
**All projects** shows every Common Core project in the selected track until a
quest confirms Common Core completion, then shows specialization projects. An
unknown personal rank still permits browsing the full Common Core catalogue.
**Registerable projects**
uses only the account's registration API response, including ID-only projects whose
catalogue description is unavailable. **Specializations** groups branches within the
selected track. **Active and local projects** keeps local work accessible when the
track is unknown. Search, type filters and pagination apply to the selected view.
In **My 42 projects**, choosing a specific type browses all matching projects in
the selected track, including later ranks and specializations, even when your
personal rank is unknown. Returning to **All types** restores the current/next
rank view. Other views keep their section, registration or local-work limits.
The browser starts in **My 42 projects**. Loaded projects are sorted by ascending
rank, with exams first within each rank, then by name.
Unranked projects appear last. Exams have a dedicated badge and accent border.
Click a rank header or press Enter on it to collapse or expand the group.
Group state is kept during the session
for each account, cursus/campus and view.
Rank headers show completed/total counts for loaded projects matching the filters.
Headers and project names use status colors: green for completed, accent for
active, amber for partial progress or available, red for failed, and muted for
inactive. Project status `unknown` is displayed as **INACTIVE**.

Details show exams, web difficulty as XP (including zero), nullable duration,
classification provenance, structured requirements, recommendations and raw
unparsed rules. Drawing links are visual relationships, not prerequisites.
Project details show Solo or Group when matching session metadata is available,
including in cached views. Known group sizes appear as **Group · 3 members**;
missing or conflicting session counts remain unspecified. **Register** submits directly through the API; 42 validates
eligibility even when team mode is unknown. API registration requires the `projects`
scope and fresh project history and catalog data. Registration refreshes status and
prevents duplicate submissions; creating or joining a team is a separate step.
The Intra project link remains available for the complete requirements.

The backend publishes validated API/browser snapshots through a separate ingestion
worker. TUI reads never start a browser. Catalogue and branch caches are scoped by
cursus, campus and snapshot; personal observations are isolated by account. Offline
views show saved data with its collection time and availability. Registration is
fresh for five minutes, progression/quests for fifteen minutes and catalogue for
24 hours. An unavailable source preserves its prior observation as stale.

The TUI downloads all catalogue pages for both tracks and the unclassified section
of the selected cursus/campus into the existing SQLite database. Reopening the app
reuses that complete local copy for 24 hours; searches and project filters run
locally. Project details are saved on first selection and reused for the same
snapshot, including offline. Account progress still refreshes separately at startup.
Expired catalogues remain visible while a replacement downloads; failed or
interrupted downloads preserve the previous complete snapshot. No database reset
is needed for this cache.

The last known dashboard profile and goals are also saved locally. After a successful
sync, reopening with the same device credentials restores the selected cursus,
track, rank, Journey and account details before network refreshes complete. The
profile snapshot is bound to the backend and credential fingerprints; tokens and
private keys are never stored in this cache. Saved goals are marked stale while
refreshing, and pending or failed observations preserve the last known display.

To check the cache against synthetic HTTP responses, run
`postman simulation run postman/simulations/catalog-cache.sim.yaml --port 4901`
and, in another terminal,
`CATALOG_CACHE_SIMULATION_URL=http://127.0.0.1:4901/backend PYTHONPATH=src .venv/bin/python -m unittest tests.test_catalog_cache_simulation`.


The new SQLite schema refuses older databases with an explicit reset instruction.
Back up local notes and activity before intentionally deleting an old database;
the application never resets it automatically.

## Project testers

In **Projects**, use the **Testers** button beside the project name or press `t`
to reveal and focus the tester controls. The **Testers** section offers **Install Francinette**, then
**Run Francinette**, for `libft`, `ft_printf` and `get_next_line` (including their
`42cursus-` slugs). One image serves all three projects. Installation needs no
project folder; running needs an existing local folder configured with `e`.
Born2beroot has no bundled tester yet.

42TUI fetches a pinned commit of
[WaRtr0/francinette-image](https://github.com/WaRtr0/francinette-image) and builds
its Dockerfile, without running its installer or editing shell configuration.
Docker uses `linux/amd64` or `linux/arm64` on Linux and macOS with x86_64
(Intel/AMD) or ARM64 processors. Francinette prefers the Docker engine's native
architecture, avoiding x86_64 emulation on ARM64. This also respects the CPU of
a remote Docker engine. 32-bit x86 is unsupported.
Docker Engine, Docker Desktop and other compatible local engines use the standard
Docker CLI. The active context and `DOCKER_*` variables are respected and kept
for the entire operation, including cleanup. A Linux engine is required.
Before running, 42TUI checks the selected container architecture and reads/writes a temporary probe
inside the project folder to verify local file sharing and permissions.
On Linux, a regular daemon runs testers with your UID/GID; rootless Docker retains
its mapped container root. Francinette uses a private writable copy of its code
for logs and caches, so generated project files remain owned by your account.
During each run, private Clang wrappers default debug information to DWARF 4,
compatible with the upstream Ubuntu 22.04 image's Valgrind. Memory checks remain
enabled; compiler arguments and explicit debug-format options are preserved.
The wrappers and writable tester copy are removed when the run finishes.
The upstream Dockerfile fetches dependencies at build time; pinning the wrapper
repository does not freeze those dependencies. The temporary container mounts the
project read/write at `/project` and runs Francinette from its writable copy,
keeping `/project` as the working directory.
Tester output appears in the terminal; press any key to return to 42TUI. Ctrl+C
interrupts the operation and removes its temporary container. An exit status of
zero means the tester finished; inspect its output for individual test results.
Logs are saved under `$XDG_DATA_HOME/42tui/testers/logs`, or
`~/.local/share/42tui/testers/logs`. Failed operations offer **Retry**.

The small **×** button beside a tester removes its Docker images (all installed
versions and CPU architectures) and stopped 42TUI test/probe containers. It also
works without a configured project folder. Since the installation is shared,
removing Francinette affects all three projects; use **Install Francinette** to
restore it. Running containers, project files, other testers and Docker volumes
are left in place. Docker can retain shared layers and build cache, so removing
the images does not guarantee reclaiming their entire displayed size. See
[Docker's resource cleanup documentation](https://docs.docker.com/engine/manage-resources/pruning/).

42TUI reuses working Docker installations. Otherwise it distinguishes a missing
CLI, a stopped engine and denied access. **Check Docker access again** retries
detection after setup. On macOS, **Install Docker** offers Homebrew when available,
or downloads and opens the official installer for the host processor. Finish
[Docker Desktop setup](https://docs.docker.com/desktop/setup/install/mac-install/)
yourself and start Docker before rechecking. On supported Ubuntu and Debian
releases, the confirmation dialog lists the commands for Docker's official apt
repository before any administrator command runs. Conflicting system packages
must be resolved manually; 42TUI does not remove them. Other distributions link to
the official installation instructions. For access problems, follow the
[Linux post-install steps](https://docs.docker.com/engine/install/linux-postinstall/),
then sign out and back in; never run the entire application with `sudo`.

The bundled `ft_tui/testers.toml` is loaded at startup and extended by
`$XDG_CONFIG_HOME/42tui/testers.toml`, falling back to `~/.config/42tui/testers.toml`.
Each user entry replaces the entire entry with the same `id`; multiple entries
may target a project. Disable a bundled tester with:

```toml
[[testers]]
id = "francinette"
enabled = false
```

To add a tester, copy this template and replace the repository, full commit SHA,
Docker build context and command with real values. The command is an argument
array executed inside the container, without shell expansion. Repository URLs must
use HTTPS, context paths must remain inside the repository, and platforms are
`linux` and/or `darwin`:

```toml
[[testers]]
id = "my-tester"
name = "My tester"
projects = ["libft", "42cursus-ft_printf"]
platforms = ["linux", "darwin"]
repository = "https://github.com/your-account/your-tester.git"
ref = "0123456789abcdef0123456789abcdef01234567"
context = "."
command = ["/tester/run", "--verbose"]
```

Restart 42TUI to reload changes. Invalid user TOML or entries display an error and
leave the complete bundled catalog usable. Image identities include the tester,
commit, repository and context; availability is always checked against Docker.
ARM64 images have a separate tag, so existing amd64 images are preserved.
On ARM64, install the native image once from the tester controls after upgrading.
Optional `architectures = ["amd64", "arm64"]` allows native execution for custom
testers that support both CPUs. Existing custom entries default to `["amd64"]`;
a tester restricted to one CPU still requires emulation on other CPUs. When the
engine's CPU is unavailable, the first configured architecture is used.
See the [verification report](docs/testers-validation.md) for tested environments
and remaining platform checks.

## Project checkout setup

In **Projects**, select a project and press `e`. Choose its local folder and optional
development remote, then select **Save + Set Up Checkout**. 42TUI clones the development remote
into a missing or empty folder, or initializes an empty local Git repository when no development
remote is set. Existing repositories are preserved; non-empty non-Git folders and conflicting
remote URLs are rejected.

When the checkout has commits, setup ensures local `dev` and `master` branches. A cloned checkout
switches to `dev`, associated with `origin/dev`. `master` is reserved for Vogsphere, whose accepted
submission branch is `master`; once the Intra API supplies its URL, 42TUI configures
`vogsphere/master`. No branch is pushed automatically. Add a real development URL yourself if you
want setup to clone it. For a new empty local repository, Git cannot store a
`master` branch before the first commit; run setup again after creating that commit to create
`master`.

In project details, **Start timer** records a manual work session in SQLite. A running timer
survives closing and reopening 42TUI; starting a timer for another project stops the previous
one. Subject checks run at the interval selected in Settings (six hours by default). 42TUI looks
for `subject.pdf` at the project root, calculates its SHA-256, and asks the configured provider
for a reference fingerprint. The backend queues subject work for the unified Intra worker,
which uses the same authenticated browser profile as Graph and Pace. It discovers the subject
link on the project page, prefers English, validates the PDF, and caches it with its SHA-256
in Redis. The HTTP handler never opens a browser or calls the restricted attachments endpoint.
The TUI shows `SUBJECT · QUEUED` while waiting and checks for the result every 30 seconds.
Missing or different PDFs are atomically installed as `subject.pdf`; failed downloads preserve
the existing file. Configured folders work with or without a Git repository. Worker snapshots
are fresh for six hours and retained for 24 hours. PDF contents are not extracted.
Project setup adds `*.pdf` to `.gitignore` so a downloaded untracked
subject isn't accidentally committed; PDFs already tracked by Git remain tracked until removed manually.
Use **Open subject** to launch the PDF with the OS default application or TDF, selected in Settings.
TDF must be installed separately and available on `PATH`.

## Keyboard shortcuts

- `j` / `k`: move down or up through projects
- `h` / `l`: switch to the previous or next tab
- `e`: set up or edit the selected project's local path and development remote
- `Home` / `End`: select the first or last project
- `Ctrl+D` / `Ctrl+U`: move down or up one page
- `F1`–`F5`: help, projects, notes, Git Sync, and settings
- `q`: quit

In **Settings**, choose a favorite editor (Neovim, Vim, Visual Studio Code, or Zed) and a favorite
Textual theme. From **Projects**, select a configured project and press `o` or use **Open in editor**
to open its folder. Terminal editors temporarily take over the terminal; GUI editors open separately.

In **Settings → Appearance**, image rendering defaults to **Automatic**: 42TUI uses
the Sixel or Kitty graphics backend selected by `textual-image`, with colored Unicode
blocks as a fallback. You can force either protocol, select colored blocks, or turn
images off. Forced protocols require support in the terminal and any multiplexer
between it and 42TUI. Rendering is selected by protocol support, not CPU/GPU hardware.

Icons have independent **Automatic**, **Nerd Font**, **Simple Unicode**, and **Text only**
modes, with a preview. Automatic mode uses terminal/environment hints, not a scan of
installed fonts: those do not establish which font the terminal uses, especially over
SSH. `NERDFONT=1` or `NERDFONT=0` overrides the automatic estimate. An explicit icon
choice in Settings takes precedence. These preferences are saved locally and apply
without restarting; font shape and pixel size remain controlled by the terminal.

## `42goto` shell navigation

`42goto` changes the current shell directory to a project's configured local folder. Enable the
shell function and project completion in your shell startup file:

```sh
# Bash: add to ~/.bashrc
eval "$(command 42goto --shell-init bash)"

# Zsh: add to ~/.zshrc
eval "$(command 42goto --shell-init zsh)"
```

Then run `42goto` to jump to the currently active project, or `42goto <project>` to open a project
by its name or slug. Press Tab to complete configured project slugs. A fresh editor heartbeat
defines the active project; when there is no fresh heartbeat, the most recently tracked project
is used. If there is no activity history, a single configured in-progress project is used; otherwise
42goto asks you to specify the project. Project folders are read from the same local SQLite database
as 42TUI. After updating a source checkout, reinstall the package to refresh the CLI entry points:

```sh
pip install -e .
```

## Editor activity tracking

42TUI accepts local activity heartbeats from editor plugins, even when the TUI is closed. Configure
the project folder in 42TUI first, then have an editor integration call the shared CLI command when
the active file changes, is edited, or is saved:

```sh
42tui heartbeat --file /absolute/path/to/project/src/main.c --editor nvim --quiet
42tui heartbeat --file /absolute/path/to/project/src/main.c --editor vscode --write --quiet
```

The CLI finds the deepest configured project folder containing the file. It stores the project,
editor, event time, and save flag in SQLite; it does not store the file path or read file contents.
Elapsed time is attributed between recent heartbeats, with a 120-second idle cutoff and a shared
timeline across editors to prevent double counting. Files outside configured project folders are
ignored with an error. Editor-specific plugins can call this command, so each integration only
needs to report its active file and editor ID; installing the app itself requires no network access.
See [`integrations/`](integrations/README.md) for the current VS Code, Zed, Vim, and Neovim adapters.

## Roadmap

Implemented for the local MVP:

- Local project catalog, dashboard, and SQLite persistence; centralized API project sync after
  OAuth connection
- Profile settings, default projects folder, and per-project local path / development remote setup
- Project navigation, Git hygiene, manual timer, PDF fingerprinting, and Markdown dev log
- Local editor heartbeat ingestion and project-level coding time aggregation
- Dashboard activity streaks, four-week heatmap, peak coding day/hour, and rolling validation pace against Black Hole deadlines
- Editor integrations for VS Code, Zed, Vim, and Neovim
- First-run onboarding for profile, projects folder, editor, theme, and language
- English, French, and Spanish interface with JSON catalogs and English fallback

Available with the optional OAuth backend:

- Browser-based 42 account pairing and profile/project synchronization, while preserving local SQLite data

Planned:
- Fetch and display achievement stars for outstanding project evaluations
- Automatically fetch the official Vogsphere remote from 42 Intra
- **Peer-Review Matcher:** use the 42 API to show available defense slots, consider correction points and availability, notify about useful openings, and keep an evaluation history with grades and feedback
- **Cluster Map:** simulate the campus's three clusters as a seat map, showing per-seat occupancy and available seats, plus the 42 profile and project information available for each seated student
- **Friends & Study Groups:** use the 42 API to automatically sync friends' project enrollment and progress, with activity and completion feeds, Black Hole pace comparisons, and cluster presence

## Source layout

Run the service regression tests from the project root after installing the package:

```sh
python -m unittest discover -s tests -v
```

```text
src/ft_tui/
├── api/          # centralized API client and provider contract
├── config/       # local paths and settings
├── db/           # models and SQLite access
├── services/     # business logic independent from the UI
├── ui/           # Textual application and views
├── locales/      # English-fallback JSON catalogs (French and Spanish)
└── main.py       # CLI entry point
```

To add another interface language, add `src/ft_tui/locales/<code>.json` with
English source messages as keys, then register its code and display name in
`ft_tui/i18n.py`. Any untranslated message falls back to English.

### Project attempts and evaluations

For a connected account, select a project and open **Attempts & evaluations**
from Project Details. The dedicated screen selects the current team and lets you
browse previous attempts, members, scores, repository and lock/closure dates.
Expand peer corrections to read comments and available feedback; expand automated
evaluations to read their output. Scheduled, overdue/unfilled and completed
corrections have separate labels. Missing marks remain distinct from zero.

Use arrow keys to select an attempt, Tab/Enter to expand an evaluation, `r` to
refresh, `i` to open Intra, and Escape to return to the selected project. The
layout stacks on terminals narrower than 90 columns. Activity, evaluation details,
automated output and embedded team results are cached by account for up to 30 days. Reopening the screen reuses these observations;
press Refresh to fetch changes from Intra. Cached sources show their collection
time separately; unavailable fields and hidden participants stay explicit.
Team management, booking and feedback submission remain planned in `TODO.md`.

Both the updated backend and TUI are required. Live 42 response parity remains
unverified. See [the HTTP simulation instructions](postman/PROJECT_ACTIVITY_SIMULATION.md)
for local integration verification and the sandbox limitation.

## Friends and rankings

Connect your 42 account and select a cursus in Settings, then open Friends.
Add friends by their 42 login to see their level, ongoing projects and recent
cluster location. Open a friend for project history or “Compare with me” to find
shared projects and opportunities to help each other. Rankings compares cursus
working curriculum ranks among your friends and yourself, with equal places for tied ranks. Published old/new cursus project mappings determine the estimate; level remains visible.

Search and filters cover shared projects, campus membership and active cluster
sessions. Enter opens a profile; Escape returns to the list. Narrow terminals
show one pane at a time. Saved progression remains available offline, while
cluster presence always requires a recent successful lookup. Refresh runs in the
background while the tab is open; progression caches refresh every 15 minutes
and presence every minute. The updated backend must be deployed before using
online Friends. Campus rankings and historical growth remain follow-up work in
[TODO.md](TODO.md).

Search in Friends and Projects accepts abbreviations and small typos, ignores
case, accents and separators, and supports multiple words in any order. For
example, `pswp` or `puhs swap` finds `push_swap`. Friends search also matches
display names, campuses and ongoing projects; rankings retain their original
positions when filtered. Project search behaves consistently offline and in
catalog results. Deploy the updated backend to enable fuzzy catalog queries.

The Add friend field searches a shared student directory stored in the backend.
Type at least two characters, press Down to browse matches and Enter to choose
a login, then select Add friend. It matches names, abbreviations and small typos,
excludes existing friends and your account, and works across campuses. Results
show when the index is building or stale. Selecting Add verifies the numeric user
ID and handles renamed logins. Exact-login entry remains available. Deploy the
new migration and run the [directory worker](backend/README.md#add-friend-suggestions)
to populate and refresh the index; typing never scans the 42 API.


## Clubs

Open Clubs to create an invite-only campus group, add registered 42TUI students,
and recruit players with Babyfoot, Smash or custom templates. Members join or
waitlist; organizers start ready activities manually. Club managers and configured
campus representatives can publish announcements.

The global DND button (`Ctrl+Shift+D`) suspends Clubs notifications. Each club
has its own bell; added members and the campus group start muted. Invitations
appear at the top right without taking focus (`Ctrl+Shift+N` focuses them).
Activities and announcements remain browsable while muted. Spontaneous popups
require fresh campus presence on an unlocked workstation.

Apply the Clubs database migration and restart both backend and TUI before use.
See the [MVP guide](docs/clubs-mvp.md) for deployment, current limitations and
verification, and the [roadmap](docs/clubs-roadmap.md) for later phases.
