Projects
Marquee watches nothing on your Mac: gproj pushes the state of your working windows, over POST /etat. The dependency runs one way only; the screen keeps living while your Mac sleeps.
What the page shows
One card per open working window, tinted in its own colour, with the foreground one set apart. Each one carries:
- the project's name, and its branch;
- its tabs (agent, tests, git…), each marked busy or not, and since when;
- three gauges: the session's context, the five-hour quota, the seven-day quota;
- at the foot of the card: the age of the session snapshot, the model and effort, the duration, the cost, and the verdict of the latest tests.
A gauge with no value says “non communiqué” (not reported) rather than a zero that would look like an established fact. A badge, next to the Projects button, counts the agent tabs at work: it falls back to nothing as soon as the state is no longer fresh (see the Troubleshooting chapter).
The gproj integration
gproj is the session tool of Marquee's author, and it isn't distributed: the real contract is POST /etat, which any tool can call.
gproj pushes the full state of your windows at short intervals: each call replaces what the screen knows, it doesn't add to it. A window missing from one push disappears from the page at the next. With no new call for a minute, the screen stops presenting that state as current.
gproj is optional: any other tool can push the same format, on the same port as the page (8099) — see the Settings chapter.
The format of POST /etat
A JSON body, sent as-is: a field Marquee doesn't know is ignored, not rejected.
| Field | Role |
|---|---|
machine | Your Mac's name, not shown anywhere yet: reserved. |
fenetres | The list of open windows: empty or missing, the page says “aucune fenêtre de travail ouverte” (no working window open). |
fenetres[].projet | The project's name, exactly as shown. |
fenetres[].teinte | A CSS colour, for the card's dot and rule. |
fenetres[].chemin | Optional: the project's folder, not shown yet. |
fenetres[].branche | Optional: the current git branch. |
fenetres[].devant | True if it's the foreground window: its card stands apart from the others. |
fenetres[].onglets | A role (role), busy or not (occupe), since when (depuis, optional) and what's running (commande, optional), per tab. |
fenetres[].contexte | Percentage of context used; -1 for “non communiqué” (not reported). |
fenetres[].cinq_heures | Percentage of the five-hour quota; -1 for “non communiqué” (not reported). |
fenetres[].sept_jours | Percentage of the seven-day quota; -1 for “non communiqué” (not reported). |
fenetres[].cout | The session's cost, in dollars. |
fenetres[].duree | Optional: the session's duration, as already-formatted text. |
fenetres[].modele, effort | Optional: the current model and reasoning effort. |
fenetres[].session_vue | Optional: the age of these figures, as text (“12 min”); missing, they pass for fresh. |
fenetres[].tests_rouges | Optional: true or false; missing, no verdict is shown. |
fenetres[].tests_quand | Optional: when that verdict was reached, as text. |
messagerie | Optional: the Dock's messaging badge, if it should be shown. |
messagerie.app | The application's name. |
messagerie.non_lus | The number of unread messages; -1 if unknown. |
messagerie.ouverte | False if the app is closed: the page then says “app fermé” (closed) rather than a stale count. |
A complete, fictional example:
{
"machine": "MacBook Pro",
"fenetres": [
{
"projet": "my-site",
"teinte": "#7aa2f7",
"branche": "feat/search",
"devant": true,
"onglets": [
{ "role": "agent", "occupe": true, "depuis": "4 min" },
{ "role": "tests", "occupe": false },
{ "role": "git", "occupe": false, "commande": "git status" }
],
"contexte": 42,
"cinq_heures": 18,
"sept_jours": 6,
"cout": 1.37,
"duree": "23 min",
"modele": "Sonnet",
"effort": "medium",
"session_vue": "12 min",
"tests_rouges": false,
"tests_quand": "8 min ago"
}
],
"messagerie": { "app": "Mail", "non_lus": 2, "ouverte": true }
}
What the route requires
Nothing that checks where the call comes from: POST /etat answers any device that reaches the page's port, with no token or special header — unlike /musique, /touche and /visualiseur, reserved to the screen itself.
Two limits, only: the body doesn't exceed 1 MiB, and it must be valid JSON. An unreadable body answers 400 without touching the state already known: a failed send leaves the screen on its last figures, rather than on a half-overwritten state.