meanwhile-cli 0.1.0__tar.gz → 0.1.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 (41) hide show
  1. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.claude-plugin/marketplace.json +1 -1
  2. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.claude-plugin/plugin.json +1 -1
  3. meanwhile_cli-0.1.1/PKG-INFO +169 -0
  4. meanwhile_cli-0.1.1/README.md +154 -0
  5. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/plugin.json +1 -1
  6. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/pyproject.toml +1 -1
  7. meanwhile_cli-0.1.1/src/meanwhile/__init__.py +1 -0
  8. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/app.py +33 -14
  9. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/strings.py +4 -6
  10. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_app.py +82 -28
  11. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_strings.py +1 -1
  12. meanwhile_cli-0.1.0/PKG-INFO +0 -265
  13. meanwhile_cli-0.1.0/README.md +0 -250
  14. meanwhile_cli-0.1.0/src/meanwhile/__init__.py +0 -1
  15. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.agents/plugins/marketplace.json +0 -0
  16. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.gitignore +0 -0
  17. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/LICENSE +0 -0
  18. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/commands/meanwhile-build.md +0 -0
  19. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/codex-hooks.json +0 -0
  20. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/hooks.json +0 -0
  21. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/meanwhile-state.sh +0 -0
  22. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/agents.py +0 -0
  23. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/app.tcss +0 -0
  24. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/build_prompt.md +0 -0
  25. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/cli.py +0 -0
  26. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/game.py +0 -0
  27. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/hooks_cli.py +0 -0
  28. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/meanwhile-state.sh +0 -0
  29. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/pool.py +0 -0
  30. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/repo.py +0 -0
  31. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/scores.py +0 -0
  32. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/state.py +0 -0
  33. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_agents.py +0 -0
  34. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_cli.py +0 -0
  35. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_game.py +0 -0
  36. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_hooks_cli.py +0 -0
  37. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_plugin_files.py +0 -0
  38. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_pool.py +0 -0
  39. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_repo.py +0 -0
  40. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_scores.py +0 -0
  41. {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_state.py +0 -0
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "meanwhile",
10
10
  "source": ".",
11
- "version": "0.1.0",
11
+ "version": "0.1.1",
12
12
  "description": "Play a quiz about your own repository while Claude Code is working, so your context stays loaded for the diff review that follows."
13
13
  }
14
14
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "meanwhile",
3
3
  "description": "Play a quiz about your own repository while Claude Code is working, so your context stays loaded for the diff review that follows.",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "author": {
6
6
  "name": "Ismail Bayram"
7
7
  },
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.5
2
+ Name: meanwhile-cli
3
+ Version: 0.1.1
4
+ Summary: Play a quiz about your own repository while your coding agent works
5
+ Project-URL: Homepage, https://github.com/ismailbayram/meanwhile
6
+ Project-URL: Repository, https://github.com/ismailbayram/meanwhile
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: textual>=0.80
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
13
+ Requires-Dist: pytest>=8.0; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # meanwhile
17
+
18
+ A quiz about your own repository that you play while your coding agent works,
19
+ so the code is still fresh in your head when the diff lands. Works with
20
+ Claude Code, Codex, Cursor and OpenCode.
21
+
22
+ <p align="center">
23
+ <img src="docs/images/card.svg" alt="A multiple-choice card about the repository, waiting for an answer" width="720">
24
+ </p>
25
+
26
+ <p align="center">
27
+ <img src="docs/images/done.svg" alt="The answered card, with a banner saying Claude Code is done" width="720">
28
+ </p>
29
+
30
+ ## Quick start
31
+
32
+ Three steps, once per machine and project:
33
+
34
+ 1. **Connect your agent** — see [Installation](#installation) for your agent.
35
+ 2. **Build the question pool**, once per project:
36
+ - Claude Code: run `/meanwhile-build`
37
+ - any other agent: run `uvx meanwhile-cli build-prompt --lang en --repo .`
38
+ and paste the output to your agent
39
+ 3. **Open a second terminal** in the same directory as your agent and run:
40
+
41
+ ```
42
+ uvx meanwhile-cli
43
+ ```
44
+
45
+ The pane starts asking questions right away. When your agent finishes a
46
+ prompt, the pane tells you so you can switch back to the diff.
47
+
48
+ ## Installation
49
+
50
+ You need [uv](https://docs.astral.sh/uv/) for the `uvx` commands. Then pick
51
+ your agent:
52
+
53
+ ### Claude Code
54
+
55
+ ```
56
+ /plugin marketplace add ismailbayram/meanwhile
57
+ /plugin install meanwhile@meanwhile
58
+ ```
59
+
60
+ Restart Claude Code.
61
+
62
+ ### Codex
63
+
64
+ ```
65
+ codex plugin marketplace add ismailbayram/meanwhile
66
+ ```
67
+
68
+ Install `meanwhile` from the Plugins Directory, then **review and trust its
69
+ hooks** — Codex skips plugin hooks until you do.
70
+
71
+ ### Cursor
72
+
73
+ ```
74
+ uvx meanwhile-cli hooks install --agent cursor
75
+ ```
76
+
77
+ ### OpenCode
78
+
79
+ ```
80
+ uvx meanwhile-cli hooks install --agent opencode
81
+ ```
82
+
83
+ Restart OpenCode.
84
+
85
+ ### Installing the command permanently (optional)
86
+
87
+ `uvx` runs the latest version without installing anything. If you prefer a
88
+ regular command:
89
+
90
+ ```
91
+ uv tool install meanwhile-cli # or: pipx install meanwhile-cli
92
+ ```
93
+
94
+ The command is then just `meanwhile` — use it wherever this README says
95
+ `uvx meanwhile-cli`.
96
+
97
+ ## Usage
98
+
99
+ | Key | Action |
100
+ | --- | --- |
101
+ | `1`–`9` | answer |
102
+ | `space` | next card |
103
+ | `q` | quit |
104
+
105
+ - Start the pane **from the same directory you started your agent in**,
106
+ otherwise it cannot tell when the agent is done. The quiz itself works
107
+ either way.
108
+ - Leave it open all day. Each new prompt after the agent finishes deals a
109
+ fresh round; when the pool runs out, `space` starts a new one.
110
+ - The interface follows the pool's language. Override it with
111
+ `--lang tr` or `--lang en`.
112
+
113
+ ## The question pool
114
+
115
+ `/meanwhile-build` (or `build-prompt`) has the agent read your repository and
116
+ write `.meanwhile/pool.json` — 30 to 50 multiple-choice questions. An entry
117
+ looks like this:
118
+
119
+ ```json
120
+ {
121
+ "type": "quiz",
122
+ "q": "What happens to an order's reserved stock when payment fails?",
123
+ "choices": [
124
+ "It stays reserved until an admin releases it",
125
+ "It is released back to inventory after 15 minutes",
126
+ "It is released immediately and the order is cancelled"
127
+ ],
128
+ "answer": 1,
129
+ "why": "release_expired_reservations runs every 15 minutes and returns unpaid stock to inventory.",
130
+ "source": "shop/orders/tasks.py"
131
+ }
132
+ ```
133
+
134
+ - **Commit `pool.json`** so the whole team plays the same pool and nobody pays
135
+ to build it again.
136
+ - **Language:** `--lang` takes a language code, not a name. `tr` and `en` are
137
+ the two the interface speaks; any other code (`de`, `fr`, …) writes the
138
+ questions in that language and keeps the interface in English.
139
+ - **Refreshing:** when the repository is about 50 commits past the pool, the
140
+ pane shows a banner. Run the build step again and commit the new file.
141
+ Nothing is regenerated automatically.
142
+ - Scores are kept in `.meanwhile/scores.json`, which is gitignored.
143
+
144
+ ## Commands
145
+
146
+ | Command | What it does |
147
+ | --- | --- |
148
+ | `uvx meanwhile-cli` | open the quiz pane |
149
+ | `uvx meanwhile-cli build-prompt --lang en --repo .` | print the pool-building instructions for any agent |
150
+ | `uvx meanwhile-cli validate --repo .` | check `pool.json` |
151
+ | `uvx meanwhile-cli hooks install --agent <agent>` | connect an agent without its plugin (`claude`, `codex`, `cursor`, `opencode`); add `--user` for every project (not Cursor) |
152
+ | `uvx meanwhile-cli hooks status` | show which agents are connected |
153
+ | `uvx meanwhile-cli hooks uninstall --agent <agent>` | disconnect |
154
+
155
+ `hooks install` writes an absolute path from your machine into the agent's
156
+ config, so don't commit that file for Codex, Cursor or OpenCode.
157
+
158
+ ## How it works
159
+
160
+ Hooks in your agent write `busy` or `idle` to a small file in
161
+ `~/.meanwhile/state/` when you send a prompt and when the agent finishes. The
162
+ pane watches that file and shows cards from `pool.json`. Only the build step
163
+ uses an LLM; the pane makes no network calls.
164
+
165
+ ## Development
166
+
167
+ From a checkout of this repository, use `uv run meanwhile` in place of
168
+ `uvx meanwhile-cli`, and `/plugin marketplace add /path/to/meanwhile` for the
169
+ Claude Code plugin. Tests: `uv run --extra dev python -m pytest`.
@@ -0,0 +1,154 @@
1
+ # meanwhile
2
+
3
+ A quiz about your own repository that you play while your coding agent works,
4
+ so the code is still fresh in your head when the diff lands. Works with
5
+ Claude Code, Codex, Cursor and OpenCode.
6
+
7
+ <p align="center">
8
+ <img src="docs/images/card.svg" alt="A multiple-choice card about the repository, waiting for an answer" width="720">
9
+ </p>
10
+
11
+ <p align="center">
12
+ <img src="docs/images/done.svg" alt="The answered card, with a banner saying Claude Code is done" width="720">
13
+ </p>
14
+
15
+ ## Quick start
16
+
17
+ Three steps, once per machine and project:
18
+
19
+ 1. **Connect your agent** — see [Installation](#installation) for your agent.
20
+ 2. **Build the question pool**, once per project:
21
+ - Claude Code: run `/meanwhile-build`
22
+ - any other agent: run `uvx meanwhile-cli build-prompt --lang en --repo .`
23
+ and paste the output to your agent
24
+ 3. **Open a second terminal** in the same directory as your agent and run:
25
+
26
+ ```
27
+ uvx meanwhile-cli
28
+ ```
29
+
30
+ The pane starts asking questions right away. When your agent finishes a
31
+ prompt, the pane tells you so you can switch back to the diff.
32
+
33
+ ## Installation
34
+
35
+ You need [uv](https://docs.astral.sh/uv/) for the `uvx` commands. Then pick
36
+ your agent:
37
+
38
+ ### Claude Code
39
+
40
+ ```
41
+ /plugin marketplace add ismailbayram/meanwhile
42
+ /plugin install meanwhile@meanwhile
43
+ ```
44
+
45
+ Restart Claude Code.
46
+
47
+ ### Codex
48
+
49
+ ```
50
+ codex plugin marketplace add ismailbayram/meanwhile
51
+ ```
52
+
53
+ Install `meanwhile` from the Plugins Directory, then **review and trust its
54
+ hooks** — Codex skips plugin hooks until you do.
55
+
56
+ ### Cursor
57
+
58
+ ```
59
+ uvx meanwhile-cli hooks install --agent cursor
60
+ ```
61
+
62
+ ### OpenCode
63
+
64
+ ```
65
+ uvx meanwhile-cli hooks install --agent opencode
66
+ ```
67
+
68
+ Restart OpenCode.
69
+
70
+ ### Installing the command permanently (optional)
71
+
72
+ `uvx` runs the latest version without installing anything. If you prefer a
73
+ regular command:
74
+
75
+ ```
76
+ uv tool install meanwhile-cli # or: pipx install meanwhile-cli
77
+ ```
78
+
79
+ The command is then just `meanwhile` — use it wherever this README says
80
+ `uvx meanwhile-cli`.
81
+
82
+ ## Usage
83
+
84
+ | Key | Action |
85
+ | --- | --- |
86
+ | `1`–`9` | answer |
87
+ | `space` | next card |
88
+ | `q` | quit |
89
+
90
+ - Start the pane **from the same directory you started your agent in**,
91
+ otherwise it cannot tell when the agent is done. The quiz itself works
92
+ either way.
93
+ - Leave it open all day. Each new prompt after the agent finishes deals a
94
+ fresh round; when the pool runs out, `space` starts a new one.
95
+ - The interface follows the pool's language. Override it with
96
+ `--lang tr` or `--lang en`.
97
+
98
+ ## The question pool
99
+
100
+ `/meanwhile-build` (or `build-prompt`) has the agent read your repository and
101
+ write `.meanwhile/pool.json` — 30 to 50 multiple-choice questions. An entry
102
+ looks like this:
103
+
104
+ ```json
105
+ {
106
+ "type": "quiz",
107
+ "q": "What happens to an order's reserved stock when payment fails?",
108
+ "choices": [
109
+ "It stays reserved until an admin releases it",
110
+ "It is released back to inventory after 15 minutes",
111
+ "It is released immediately and the order is cancelled"
112
+ ],
113
+ "answer": 1,
114
+ "why": "release_expired_reservations runs every 15 minutes and returns unpaid stock to inventory.",
115
+ "source": "shop/orders/tasks.py"
116
+ }
117
+ ```
118
+
119
+ - **Commit `pool.json`** so the whole team plays the same pool and nobody pays
120
+ to build it again.
121
+ - **Language:** `--lang` takes a language code, not a name. `tr` and `en` are
122
+ the two the interface speaks; any other code (`de`, `fr`, …) writes the
123
+ questions in that language and keeps the interface in English.
124
+ - **Refreshing:** when the repository is about 50 commits past the pool, the
125
+ pane shows a banner. Run the build step again and commit the new file.
126
+ Nothing is regenerated automatically.
127
+ - Scores are kept in `.meanwhile/scores.json`, which is gitignored.
128
+
129
+ ## Commands
130
+
131
+ | Command | What it does |
132
+ | --- | --- |
133
+ | `uvx meanwhile-cli` | open the quiz pane |
134
+ | `uvx meanwhile-cli build-prompt --lang en --repo .` | print the pool-building instructions for any agent |
135
+ | `uvx meanwhile-cli validate --repo .` | check `pool.json` |
136
+ | `uvx meanwhile-cli hooks install --agent <agent>` | connect an agent without its plugin (`claude`, `codex`, `cursor`, `opencode`); add `--user` for every project (not Cursor) |
137
+ | `uvx meanwhile-cli hooks status` | show which agents are connected |
138
+ | `uvx meanwhile-cli hooks uninstall --agent <agent>` | disconnect |
139
+
140
+ `hooks install` writes an absolute path from your machine into the agent's
141
+ config, so don't commit that file for Codex, Cursor or OpenCode.
142
+
143
+ ## How it works
144
+
145
+ Hooks in your agent write `busy` or `idle` to a small file in
146
+ `~/.meanwhile/state/` when you send a prompt and when the agent finishes. The
147
+ pane watches that file and shows cards from `pool.json`. Only the build step
148
+ uses an LLM; the pane makes no network calls.
149
+
150
+ ## Development
151
+
152
+ From a checkout of this repository, use `uv run meanwhile` in place of
153
+ `uvx meanwhile-cli`, and `/plugin marketplace add /path/to/meanwhile` for the
154
+ Claude Code plugin. Tests: `uv run --extra dev python -m pytest`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "meanwhile",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Play a quiz about your own repository while Claude Code is working, so your context stays loaded for the diff review that follows.",
6
6
  "author": {
7
7
  "name": "Ismail Bayram"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meanwhile-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Play a quiz about your own repository while your coding agent works"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -12,9 +12,9 @@ from textual.widgets import Footer, Static
12
12
  from . import agents, game, scores, strings
13
13
  from .pool import Pool, QuizItem
14
14
 
15
- # Lifecycle phases. The pane is opened once and left open: it waits for the
16
- # agent to start, plays for exactly as long as it is busy, then waits again.
17
- WAITING = "waiting"
15
+ # Lifecycle phases. The pane deals cards as soon as it opens — a pool is all
16
+ # it needs. The agent's hooks only add to that: an idle edge freezes the card
17
+ # and says the agent is done, and the next busy edge deals a fresh round.
18
18
  PLAYING = "playing"
19
19
  DONE = "done"
20
20
 
@@ -53,7 +53,7 @@ class MeanwhileApp(App):
53
53
  self.language = language
54
54
  self.agent_source = agent_source
55
55
  self.session = game.start_session(pool)
56
- self.phase = WAITING
56
+ self.phase = PLAYING
57
57
  self._last_status = "idle"
58
58
  self._answered = False
59
59
 
@@ -115,6 +115,10 @@ class MeanwhileApp(App):
115
115
  self._finish_playing()
116
116
 
117
117
  def _start_playing(self) -> None:
118
+ if self.phase == PLAYING:
119
+ # Already dealing — the pane was opened before the prompt. Keep the
120
+ # card on screen rather than reshuffling under the player.
121
+ return
118
122
  self.phase = PLAYING
119
123
  self.session = game.start_session(self.pool) # re-shuffled: the cards come back
120
124
  self._answered = False
@@ -128,14 +132,24 @@ class MeanwhileApp(App):
128
132
  self.phase = DONE
129
133
  self.refresh_bindings() # DONE takes no input: the card keys leave the footer
130
134
  self.query_one("#status").update(strings.text(self.language, "done", agent=self._agent_name()))
131
- # Exactly once per session: the `phase != PLAYING` guard above is what
132
- # guarantees it — the only way back into PLAYING is _start_playing,
133
- # which deals a new session.
135
+ self._record()
136
+
137
+ def _record(self) -> None:
138
+ # Exactly once per session: both callers leave PLAYING first, and the
139
+ # only way back into it is _start_playing, which deals a new session.
134
140
  if self.repo_dir is not None and self.session.answered:
135
141
  scores.record_session(self.repo_dir, self.session)
136
142
 
137
143
  # --- actions --------------------------------------------------------
138
144
 
145
+ async def action_quit(self) -> None:
146
+ # Without hooks there is never an idle edge, so quitting is the only
147
+ # moment a session ends. Score it here rather than lose it.
148
+ if self.phase == PLAYING:
149
+ self.phase = DONE
150
+ self._record()
151
+ self.exit()
152
+
139
153
  def action_choose(self, number: int) -> None:
140
154
  item = game.current(self.session)
141
155
  if self.phase != PLAYING or self._answered or not isinstance(item, QuizItem):
@@ -146,7 +160,18 @@ class MeanwhileApp(App):
146
160
  self._resolve(game.answer(self.session, index))
147
161
 
148
162
  def action_next(self) -> None:
149
- if self.phase != PLAYING or not self._answered:
163
+ if self.phase != PLAYING:
164
+ return
165
+ if game.current(self.session) is None:
166
+ # Out of cards. Without hooks no busy edge will ever come to deal
167
+ # another round, so `space` does it: bank this one, start afresh.
168
+ self._record()
169
+ self.session = game.start_session(self.pool)
170
+ self._answered = False
171
+ self.query_one("#feedback").update("")
172
+ self._render()
173
+ return
174
+ if not self._answered:
150
175
  return
151
176
  game.advance(self.session)
152
177
  self._answered = False
@@ -166,12 +191,6 @@ class MeanwhileApp(App):
166
191
  # `check_action` has too.
167
192
  self.refresh_bindings()
168
193
  card = self.query_one("#card")
169
- if self.phase == WAITING:
170
- self.query_one("#status").update(strings.text(self.language, "waiting", agent=self._agent_name()))
171
- card.update("")
172
- self.query_one("#score").update("")
173
- return
174
-
175
194
  item = game.current(self.session)
176
195
  if item is None:
177
196
  card.update(strings.text(self.language, "exhausted"))
@@ -1,4 +1,4 @@
1
- """The interface's six strings, in the languages meanwhile ships.
1
+ """The interface's strings, in the languages meanwhile ships.
2
2
 
3
3
  A dictionary, deliberately, and not an i18n framework: there is no catalog to
4
4
  extract, no compilation step and no locale negotiation. The cards themselves
@@ -10,23 +10,21 @@ from __future__ import annotations
10
10
 
11
11
  DEFAULT_LANGUAGE = "en"
12
12
 
13
- KEYS = ("waiting", "done", "exhausted", "stale", "correct", "wrong", "agent")
13
+ KEYS = ("done", "exhausted", "stale", "correct", "wrong", "agent")
14
14
 
15
15
  _TABLE = {
16
16
  "en": {
17
17
  "agent": "the agent",
18
- "waiting": "Waiting for {agent} to start working — submit a prompt and the game begins.",
19
18
  "done": "{agent} is done ^ — switch back to the diff; this pane keeps running.",
20
- "exhausted": "Pool exhausted for this wait — the next one deals a fresh round.",
19
+ "exhausted": "That was every question in the pool — press space for a fresh round.",
21
20
  "stale": "pool is {behind} commits behind — run the build command for fresh questions",
22
21
  "correct": "correct",
23
22
  "wrong": "wrong",
24
23
  },
25
24
  "tr": {
26
25
  "agent": "ajan",
27
- "waiting": "{agent} çalışmaya başlayınca oyun başlıyor — bir istem gönder.",
28
26
  "done": "{agent} bitirdi ^ — diff'e dön; bu pencere açık kalıyor.",
29
- "exhausted": "Bu bekleme için havuz bitti — sonraki bekleme yeni bir tur dağıtıyor.",
27
+ "exhausted": "Havuzdaki bütün sorular bitti — yeni tur için boşluğa bas.",
30
28
  "stale": "havuz {behind} commit geride — tazelemek için build komutunu çalıştır",
31
29
  "correct": "doğru",
32
30
  "wrong": "yanlış",
@@ -6,7 +6,7 @@ from textual.binding import Binding
6
6
  from textual.widgets import Footer
7
7
 
8
8
  from meanwhile import game, scores, state, strings
9
- from meanwhile.app import DONE, PLAYING, WAITING, MeanwhileApp
9
+ from meanwhile.app import DONE, PLAYING, MeanwhileApp
10
10
  from meanwhile.pool import Pool, QuizItem
11
11
  from meanwhile.scores import SCORES_RELPATH
12
12
 
@@ -207,7 +207,7 @@ async def test_going_idle_with_no_repo_dir_does_not_write_or_raise(tmp_path):
207
207
  assert not (tmp_path / SCORES_RELPATH).exists()
208
208
 
209
209
 
210
- # --- lifecycle: waiting -> playing -> done -> playing ----------------------
210
+ # --- lifecycle: playing -> done -> playing ---------------------------------
211
211
 
212
212
 
213
213
  def lifecycle_app(items, status, tmp_path, poll_seconds=60):
@@ -222,8 +222,10 @@ def lifecycle_app(items, status, tmp_path, poll_seconds=60):
222
222
  )
223
223
 
224
224
 
225
- async def test_launching_while_idle_with_no_state_file_waits(tmp_path):
226
- """The order the README documents: open the pane before submitting a prompt."""
225
+ async def test_launching_with_no_state_file_deals_a_card_at_once(tmp_path):
226
+ """A pool is all the pane needs. Hooks that never ran — not installed yet,
227
+ agent not restarted, pane opened in another directory — must not leave the
228
+ player staring at an empty screen."""
227
229
  project = tmp_path / "project"
228
230
  project.mkdir()
229
231
  empty_state_root = tmp_path / "no-state-here" # the hooks have never run
@@ -236,18 +238,32 @@ async def test_launching_while_idle_with_no_state_file_waits(tmp_path):
236
238
  poll_seconds=60,
237
239
  )
238
240
  async with app.run_test() as pilot:
239
- poll(app, times=5) # several polls: none of them may end the game
241
+ poll(app, times=5) # several idle polls: none of them may end the game
240
242
  await pilot.pause()
241
- assert app.phase == WAITING
243
+ assert app.phase == PLAYING
244
+ assert "passport_groups" in text_of(app.query_one("#card"))
242
245
  assert "done" not in text_of(app.query_one("#status")).lower()
243
- assert "waiting" in text_of(app.query_one("#status")).lower()
244
- assert text_of(app.query_one("#card")) == ""
245
246
 
246
- await pilot.press("2") # waiting: no card, no input
247
- assert app.session.answered == 0
248
- app.action_choose(2) # and the handler refuses it too
249
- assert app.session.answered == 0
250
- assert not (tmp_path / SCORES_RELPATH).exists()
247
+ await pilot.press("2")
248
+ await pilot.pause()
249
+ assert app.session.correct == 1
250
+
251
+
252
+ async def test_a_busy_edge_while_already_playing_keeps_the_card(tmp_path):
253
+ """Opened before the prompt, the pane is already dealing. The prompt that
254
+ follows must not reshuffle the card out from under the player."""
255
+ status = {"value": "idle"}
256
+ app = lifecycle_app([QUIZ, NARROW], status, tmp_path)
257
+ async with app.run_test() as pilot:
258
+ await pilot.pause()
259
+ session, card = app.session, text_of(app.query_one("#card"))
260
+
261
+ status["value"] = "busy"
262
+ poll(app)
263
+ await pilot.pause()
264
+ assert app.phase == PLAYING
265
+ assert app.session is session
266
+ assert text_of(app.query_one("#card")) == card
251
267
 
252
268
 
253
269
  async def test_a_second_busy_period_plays_a_fresh_session(tmp_path):
@@ -256,15 +272,15 @@ async def test_a_second_busy_period_plays_a_fresh_session(tmp_path):
256
272
  async with app.run_test() as pilot:
257
273
  poll(app)
258
274
  await pilot.pause()
259
- assert app.phase == WAITING
275
+ assert app.phase == PLAYING
276
+ first_session = app.session
260
277
 
261
278
  status["value"] = "busy"
262
279
  poll(app)
263
280
  await pilot.pause()
264
- assert app.phase == PLAYING
265
- first_session = app.session
266
281
  await pilot.press("2")
267
282
  await pilot.pause()
283
+ assert app.session is first_session
268
284
  assert app.session.answered == 1
269
285
 
270
286
  status["value"] = "idle"
@@ -291,6 +307,52 @@ async def test_a_second_busy_period_plays_a_fresh_session(tmp_path):
291
307
  assert saved["answered"] == 2 # both sessions banked, neither one twice
292
308
 
293
309
 
310
+ async def test_space_on_an_exhausted_pool_deals_a_fresh_round(tmp_path):
311
+ status = {"value": "idle"}
312
+ app = lifecycle_app([QUIZ], status, tmp_path)
313
+ async with app.run_test() as pilot:
314
+ await pilot.press("2")
315
+ await pilot.press("space")
316
+ await pilot.pause()
317
+ assert game.current(app.session) is None
318
+ assert "space" in text_of(app.query_one("#card"))
319
+
320
+ await pilot.press("space")
321
+ await pilot.pause()
322
+ assert game.current(app.session) is QUIZ
323
+ assert app.session.answered == 0
324
+ saved = json.loads((tmp_path / SCORES_RELPATH).read_text(encoding="utf-8"))
325
+ assert saved["answered"] == 1 # the finished round was banked, and only once
326
+
327
+
328
+ async def test_quitting_mid_session_banks_it(tmp_path):
329
+ """Without hooks there is never an idle edge, so `q` is where a session ends."""
330
+ status = {"value": "idle"}
331
+ app = lifecycle_app([QUIZ], status, tmp_path)
332
+ async with app.run_test() as pilot:
333
+ await pilot.press("2")
334
+ await pilot.pause()
335
+ await pilot.press("q")
336
+ await pilot.pause()
337
+ saved = json.loads((tmp_path / SCORES_RELPATH).read_text(encoding="utf-8"))
338
+ assert saved["answered"] == 1
339
+
340
+
341
+ async def test_quitting_after_done_does_not_bank_twice(tmp_path, monkeypatch):
342
+ calls = []
343
+ monkeypatch.setattr(scores, "record_session", lambda repo_dir, session: calls.append(session))
344
+ status = {"value": "busy"}
345
+ app = lifecycle_app([QUIZ], status, tmp_path)
346
+ async with app.run_test() as pilot:
347
+ await pilot.press("2")
348
+ status["value"] = "idle"
349
+ poll(app)
350
+ await pilot.pause()
351
+ await pilot.press("q")
352
+ await pilot.pause()
353
+ assert len(calls) == 1
354
+
355
+
294
356
  async def test_a_session_is_banked_once_no_matter_how_many_idle_polls(tmp_path, monkeypatch):
295
357
  calls = []
296
358
  monkeypatch.setattr(scores, "record_session", lambda repo_dir, session: calls.append(session))
@@ -353,14 +415,6 @@ async def test_the_footer_follows_the_card_not_the_widest_card_in_the_pool(tmp_p
353
415
  assert sorted(seen) == [2, 3] # both widths really were on screen
354
416
 
355
417
 
356
- async def test_the_waiting_screen_offers_no_card_keys(tmp_path):
357
- app = app_for([QUIZ, NARROW], status="idle", tmp_path=tmp_path)
358
- async with app.run_test() as pilot:
359
- await pilot.pause()
360
- assert app.phase == WAITING
361
- assert card_keys(app) == []
362
-
363
-
364
418
  async def test_the_done_screen_offers_no_card_keys(tmp_path):
365
419
  status = {"value": "busy"}
366
420
  app = lifecycle_app([QUIZ, NARROW], status, tmp_path)
@@ -399,12 +453,12 @@ async def test_space_does_nothing_before_the_card_is_answered(tmp_path):
399
453
  # --- language ---------------------------------------------------------
400
454
 
401
455
 
402
- async def test_the_waiting_line_is_turkish_when_the_pool_is(tmp_path):
403
- app = app_for([QUIZ], status="idle", tmp_path=tmp_path, language="tr")
456
+ async def test_the_done_line_is_turkish_when_the_pool_is(tmp_path):
457
+ app = app_for([QUIZ], tmp_path=tmp_path, language="tr")
404
458
  async with app.run_test() as pilot:
405
- await pilot.pause()
459
+ await drive_to_done(app, pilot)
406
460
  assert text_of(app.query_one("#status")) == strings.text(
407
- "tr", "waiting", agent=strings.text("tr", "agent")
461
+ "tr", "done", agent=strings.text("tr", "agent")
408
462
  )
409
463
 
410
464
 
@@ -14,7 +14,7 @@ def test_an_unknown_language_falls_back_to_english():
14
14
 
15
15
 
16
16
  def test_turkish_and_english_differ():
17
- assert strings.text("tr", "waiting", agent="Codex") != strings.text("en", "waiting", agent="Codex")
17
+ assert strings.text("tr", "done", agent="Codex") != strings.text("en", "done", agent="Codex")
18
18
 
19
19
 
20
20
  def test_the_agent_name_is_substituted():
@@ -1,265 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: meanwhile-cli
3
- Version: 0.1.0
4
- Summary: Play a quiz about your own repository while your coding agent works
5
- Project-URL: Homepage, https://github.com/ismailbayram/meanwhile
6
- Project-URL: Repository, https://github.com/ismailbayram/meanwhile
7
- License: MIT
8
- License-File: LICENSE
9
- Requires-Python: >=3.10
10
- Requires-Dist: textual>=0.80
11
- Provides-Extra: dev
12
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
13
- Requires-Dist: pytest>=8.0; extra == 'dev'
14
- Description-Content-Type: text/markdown
15
-
16
- # meanwhile
17
-
18
- Claude Code takes a while on real tasks — often ten or fifteen minutes. During
19
- that wait, attention drifts: a browser tab, a chat, something unrelated. When
20
- the diff finally lands, you no longer hold the task in your head, and the
21
- review that follows is slower and shallower than it should be. meanwhile fills
22
- that wait with a quiz built entirely from your own repository, so that
23
- instead of drifting away you stay warmed up on the code you're about to
24
- review. It works the same way alongside Codex, Cursor and OpenCode.
25
-
26
- ## Install
27
-
28
- ### Claude Code
29
-
30
- meanwhile is a Claude Code plugin, and this repository is also its own
31
- marketplace (`.claude-plugin/marketplace.json`). Add it as a marketplace,
32
- then install the plugin from it:
33
-
34
- ```
35
- /plugin marketplace add ismailbayram/meanwhile
36
- /plugin install meanwhile@meanwhile
37
- ```
38
-
39
- (While developing against a checkout of this repo, add the local path
40
- instead — `/plugin marketplace add /path/to/meanwhile`.) Restart Claude Code
41
- afterwards so the plugin's hooks are picked up.
42
-
43
- Installing the plugin adds two hooks and one slash command; it does not
44
- install the `meanwhile` command itself. That's a separate, ordinary Python
45
- package — see "Daily use" below.
46
-
47
- ### Codex
48
-
49
- This repository is a Codex plugin and marketplace too (`plugin.json`,
50
- `.agents/plugins/marketplace.json`). Add the marketplace:
51
-
52
- ```
53
- codex plugin marketplace add ismailbayram/meanwhile
54
- ```
55
-
56
- then install `meanwhile` from the Plugins Directory. Codex does not run a
57
- plugin's hooks until you have reviewed and trusted them, so do that once
58
- after installing — until then the pane never wakes.
59
-
60
- ### Cursor, OpenCode, or Codex without the plugin
61
-
62
- Cursor has no plugin system, so `meanwhile` writes the hook registration into
63
- the agent's config file directly. The same works for Codex if you would
64
- rather not install the plugin:
65
-
66
- ```
67
- uvx meanwhile-cli hooks install --agent cursor
68
- uvx meanwhile-cli hooks install --agent codex
69
- ```
70
-
71
- OpenCode has no shell hooks at all, only JavaScript plugins, so there the
72
- same command writes a small plugin file — `.opencode/plugins/meanwhile.js`,
73
- or `~/.config/opencode/plugins/meanwhile.js` with `--user` — that calls the
74
- hook script whenever a session goes busy or idle:
75
-
76
- ```
77
- uvx meanwhile-cli hooks install --agent opencode
78
- ```
79
-
80
- Restart OpenCode afterwards; plugins are loaded at startup. A file already
81
- at that path that `meanwhile` did not write is refused, never overwritten.
82
-
83
- (While developing against a checkout of this repo instead of the published
84
- package, use `uv run meanwhile hooks install --agent codex` from inside it.)
85
-
86
- Leaving off `--agent` (or passing `--agent auto` explicitly) installs for
87
- every agent whose config directory already exists in this project — it never
88
- creates one, so an agent you don't use is left alone. `meanwhile hooks status`
89
- reports what's registered and where — only the registrations meanwhile itself
90
- wrote, so a *plugin* install is invisible to it: Claude Code shows as `not
91
- installed (the plugin registers its own hooks separately)`, Codex as plain
92
- `not installed`. `meanwhile hooks
93
- uninstall --agent <agent>` takes a registration back out.
94
-
95
- Cursor only supports project-scope registration. `--user` is refused for it:
96
- Cursor runs its user-level hooks from `~/.cursor/`, while the hook keys the
97
- state file on its working directory, so a user-level registration could
98
- never wake the right pane.
99
-
100
- For Claude Code, a `hooks install` registration is written to
101
- `.claude/settings.local.json`, not `settings.json` — the entry embeds an
102
- absolute path to this machine's copy of the hook script, and `settings.json`
103
- is the file teams commit. A Codex, Cursor or OpenCode project registration
104
- has no such gitignored sibling to move to, so `hooks install` prints a note there — every
105
- time, whether or not git already tracks the file — saying that the path it
106
- just wrote is specific to this machine and that committing it would leave
107
- teammates running a script they do not have.
108
-
109
- If you both install the plugin *and* run `meanwhile hooks install` for the
110
- same agent, the two hooks fire twice per prompt. That's harmless — both copies
111
- are byte-identical (`hooks/meanwhile-state.sh` for the plugin,
112
- `~/.meanwhile/meanwhile-state.sh` for the standalone registration) and are run
113
- with the same arguments against the same state file, so it's one redundant
114
- run per event — but worth knowing so you don't go looking for a bug.
115
-
116
- `hooks` is handled before the normal argument parser is built, so it never
117
- appears in `meanwhile --help`. `build-prompt` (below) does appear there, but
118
- only as a bare choice with no explanation of what it does — so this README
119
- is where to learn about both.
120
-
121
- ## First run
122
-
123
- In a project you want to play against, build a pool once, up front — nothing
124
- regenerates it in the background.
125
-
126
- **Claude Code**: run
127
-
128
- ```
129
- /meanwhile-build
130
- ```
131
-
132
- This asks Claude to read the repository — models, routes, the README,
133
- `CHANGELOG.md`, recent commits — and write `.meanwhile/pool.json`: a set of
134
- multiple-choice questions drawn from that project's actual code.
135
-
136
- **Codex, Cursor, OpenCode, or any other agent**: `/meanwhile-build` is a Claude Code
137
- slash command, so run this instead and paste the printed instructions to
138
- your agent:
139
-
140
- ```
141
- uvx meanwhile-cli build-prompt --lang en --repo .
142
- ```
143
-
144
- It prints the exact same instructions the slash command uses, so the pool
145
- comes out the same either way.
146
-
147
- Commit `.meanwhile/pool.json`. It's checked into the repository on purpose, so
148
- everyone on the team plays the same pool and nobody pays to regenerate it.
149
-
150
- `--lang` takes a short language code — `tr` or `en` — not a language name:
151
- `--lang Turkish` is not recognised and silently leaves the interface in
152
- English. Those two codes are the only ones the interface itself speaks.
153
-
154
- A pool built by the build prompt records the language it was written in, and
155
- the playing interface follows it, with `--lang` overriding that for just your
156
- pane. `--lang` on `build-prompt` controls what language the cards themselves
157
- get written in — any code is allowed there, so `--lang de` writes German cards
158
- and leaves the interface in English. `pool.json` carries the same code in a
159
- top-level `language` field (it defaults to `en` when absent); the playing
160
- interface's own chrome — the waiting banner, "correct"/"wrong", and so on —
161
- follows that field, in `tr` or `en` and in English for anything else.
162
-
163
- ## Daily use
164
-
165
- Open a second terminal pane next to the one running your agent — a tmux
166
- split, an iTerm pane, or just another window — sitting in the *same directory
167
- you started it in*, and run:
168
-
169
- ```
170
- uvx meanwhile-cli
171
- ```
172
-
173
- `uvx` runs it without installing anything. To install it for good instead,
174
- `uv tool install meanwhile-cli` (or `pipx install meanwhile-cli`) — the
175
- command that leaves on your path is plain `meanwhile`, and it takes the same
176
- arguments everywhere this README writes `uvx meanwhile-cli`.
177
-
178
- (While developing against a checkout of this repo instead of the published
179
- package, use `uv run meanwhile` from inside it.)
180
-
181
- Open it whenever you like — before your first prompt is fine. The pane sits
182
- waiting until your agent starts working, wakes up within about a second of
183
- you submitting a prompt, and serves multiple-choice cards about the
184
- codebase one at a time. Answer with the number key for the choice you want;
185
- the footer only ever offers the keys the card on screen can actually take.
186
- Answering shows the explanation, `space` deals the next card, and a streak
187
- counter tracks that wait. If a wait runs long enough to reach the end of the
188
- pool, the pane says so and sits out the rest of it rather than repeating
189
- questions.
190
-
191
- The moment your agent finishes, the current card freezes and the pane shows
192
- `<Agent> is done ^` (or `the agent is done ^` if it can't tell which one),
193
- which is your cue to switch back.
194
-
195
- Then leave it open. It stays on that line — it does not clear itself or
196
- return to the waiting message — until your next prompt, which deals a fresh,
197
- re-shuffled round, so a long wait never exhausts the pool for the next one.
198
- You only ever press `q` when you are done for the day. Each wait is scored as
199
- its own session and added to your totals when it ends; a wait you answered
200
- nothing in is not recorded.
201
-
202
- The interface follows the language recorded in the pool. Override it for
203
- just your pane with `--lang` and a short code, for example `uvx
204
- meanwhile-cli --lang en`, regardless of what the pool says. `tr` and `en`
205
- are the two the interface speaks; any other code leaves it in English.
206
-
207
- ## How it works
208
-
209
- meanwhile is three independent parts that only ever talk to each other
210
- through two files on disk — nothing imports anything else, and each part can
211
- be tested alone:
212
-
213
- ```
214
- /meanwhile-build (Claude Code slash command)
215
- build-prompt (same instructions, any other agent)
216
- │ writes
217
- ▼
218
- .meanwhile/pool.json ← committed to the repo, shared by the team
219
- │ reads
220
- ▼
221
- meanwhile TUI ──watches──► ~/.meanwhile/state/<project-hash>.json
222
- (uvx, separate pane) ▲
223
- │ writes
224
- hooks: plugin (Claude Code, Codex), or `meanwhile
225
- hooks install` (those two, Cursor, OpenCode)
226
- ```
227
-
228
- - **`/meanwhile-build`** (or `meanwhile build-prompt` for every other agent) is
229
- the only part that costs tokens or touches an LLM. It reads the repository
230
- and writes `.meanwhile/pool.json`.
231
- - **The hooks** are a few lines of pure bash (`hooks/meanwhile-state.sh` for
232
- the plugin; the same script, packaged inside the wheel and copied to
233
- `~/.meanwhile/meanwhile-state.sh`, for `hooks install`), fired on the moment
234
- each agent starts and stops working — `UserPromptSubmit`/`Stop` for
235
- Claude Code and Codex, `beforeSubmitPrompt`/`stop` for Cursor, and the
236
- `session.status` event for OpenCode, whose plugin file does nothing but
237
- call the same script. They write nothing but a
238
- `{"status": "busy"|"idle", "ts": ..., "agent": ...}` file under
239
- `~/.meanwhile/state/`, keyed by a hash of the directory the agent was
240
- started in (that is what `CLAUDE_PROJECT_DIR` holds for Claude Code — not
241
- necessarily the git root, which is why the TUI must be launched from the
242
- same directory). No interpreter, no network, no measurable delay added to
243
- your prompt.
244
- - **The `meanwhile` TUI**, run separately as `uvx meanwhile-cli`, polls
245
- that state file and renders cards from the pool. It finds
246
- `.meanwhile/pool.json` at the git root above wherever you launched it. It makes no LLM calls and
247
- parses no source code at play time — it only reads the two JSON files
248
- above.
249
-
250
- ## Refreshing a stale pool
251
-
252
- `pool.json` records the commit it was built from. If the repository has
253
- moved on more than about 50 commits since then, the TUI shows a banner —
254
- `pool is N commits behind — run the build command for fresh questions` — but
255
- it never regenerates the pool on its own. Silent background regeneration
256
- would spend tokens you didn't ask for, which is exactly what this design
257
- avoids. Run `/meanwhile-build` (or `meanwhile build-prompt`) again when you see
258
- the banner (or whenever the pool just feels stale) and commit the updated
259
- file.
260
-
261
- ## Scores
262
-
263
- Your running total and best streak are written to `.meanwhile/scores.json`.
264
- That file is gitignored — scores are local to your machine and never shared
265
- or synced between teammates.
@@ -1,250 +0,0 @@
1
- # meanwhile
2
-
3
- Claude Code takes a while on real tasks — often ten or fifteen minutes. During
4
- that wait, attention drifts: a browser tab, a chat, something unrelated. When
5
- the diff finally lands, you no longer hold the task in your head, and the
6
- review that follows is slower and shallower than it should be. meanwhile fills
7
- that wait with a quiz built entirely from your own repository, so that
8
- instead of drifting away you stay warmed up on the code you're about to
9
- review. It works the same way alongside Codex, Cursor and OpenCode.
10
-
11
- ## Install
12
-
13
- ### Claude Code
14
-
15
- meanwhile is a Claude Code plugin, and this repository is also its own
16
- marketplace (`.claude-plugin/marketplace.json`). Add it as a marketplace,
17
- then install the plugin from it:
18
-
19
- ```
20
- /plugin marketplace add ismailbayram/meanwhile
21
- /plugin install meanwhile@meanwhile
22
- ```
23
-
24
- (While developing against a checkout of this repo, add the local path
25
- instead — `/plugin marketplace add /path/to/meanwhile`.) Restart Claude Code
26
- afterwards so the plugin's hooks are picked up.
27
-
28
- Installing the plugin adds two hooks and one slash command; it does not
29
- install the `meanwhile` command itself. That's a separate, ordinary Python
30
- package — see "Daily use" below.
31
-
32
- ### Codex
33
-
34
- This repository is a Codex plugin and marketplace too (`plugin.json`,
35
- `.agents/plugins/marketplace.json`). Add the marketplace:
36
-
37
- ```
38
- codex plugin marketplace add ismailbayram/meanwhile
39
- ```
40
-
41
- then install `meanwhile` from the Plugins Directory. Codex does not run a
42
- plugin's hooks until you have reviewed and trusted them, so do that once
43
- after installing — until then the pane never wakes.
44
-
45
- ### Cursor, OpenCode, or Codex without the plugin
46
-
47
- Cursor has no plugin system, so `meanwhile` writes the hook registration into
48
- the agent's config file directly. The same works for Codex if you would
49
- rather not install the plugin:
50
-
51
- ```
52
- uvx meanwhile-cli hooks install --agent cursor
53
- uvx meanwhile-cli hooks install --agent codex
54
- ```
55
-
56
- OpenCode has no shell hooks at all, only JavaScript plugins, so there the
57
- same command writes a small plugin file — `.opencode/plugins/meanwhile.js`,
58
- or `~/.config/opencode/plugins/meanwhile.js` with `--user` — that calls the
59
- hook script whenever a session goes busy or idle:
60
-
61
- ```
62
- uvx meanwhile-cli hooks install --agent opencode
63
- ```
64
-
65
- Restart OpenCode afterwards; plugins are loaded at startup. A file already
66
- at that path that `meanwhile` did not write is refused, never overwritten.
67
-
68
- (While developing against a checkout of this repo instead of the published
69
- package, use `uv run meanwhile hooks install --agent codex` from inside it.)
70
-
71
- Leaving off `--agent` (or passing `--agent auto` explicitly) installs for
72
- every agent whose config directory already exists in this project — it never
73
- creates one, so an agent you don't use is left alone. `meanwhile hooks status`
74
- reports what's registered and where — only the registrations meanwhile itself
75
- wrote, so a *plugin* install is invisible to it: Claude Code shows as `not
76
- installed (the plugin registers its own hooks separately)`, Codex as plain
77
- `not installed`. `meanwhile hooks
78
- uninstall --agent <agent>` takes a registration back out.
79
-
80
- Cursor only supports project-scope registration. `--user` is refused for it:
81
- Cursor runs its user-level hooks from `~/.cursor/`, while the hook keys the
82
- state file on its working directory, so a user-level registration could
83
- never wake the right pane.
84
-
85
- For Claude Code, a `hooks install` registration is written to
86
- `.claude/settings.local.json`, not `settings.json` — the entry embeds an
87
- absolute path to this machine's copy of the hook script, and `settings.json`
88
- is the file teams commit. A Codex, Cursor or OpenCode project registration
89
- has no such gitignored sibling to move to, so `hooks install` prints a note there — every
90
- time, whether or not git already tracks the file — saying that the path it
91
- just wrote is specific to this machine and that committing it would leave
92
- teammates running a script they do not have.
93
-
94
- If you both install the plugin *and* run `meanwhile hooks install` for the
95
- same agent, the two hooks fire twice per prompt. That's harmless — both copies
96
- are byte-identical (`hooks/meanwhile-state.sh` for the plugin,
97
- `~/.meanwhile/meanwhile-state.sh` for the standalone registration) and are run
98
- with the same arguments against the same state file, so it's one redundant
99
- run per event — but worth knowing so you don't go looking for a bug.
100
-
101
- `hooks` is handled before the normal argument parser is built, so it never
102
- appears in `meanwhile --help`. `build-prompt` (below) does appear there, but
103
- only as a bare choice with no explanation of what it does — so this README
104
- is where to learn about both.
105
-
106
- ## First run
107
-
108
- In a project you want to play against, build a pool once, up front — nothing
109
- regenerates it in the background.
110
-
111
- **Claude Code**: run
112
-
113
- ```
114
- /meanwhile-build
115
- ```
116
-
117
- This asks Claude to read the repository — models, routes, the README,
118
- `CHANGELOG.md`, recent commits — and write `.meanwhile/pool.json`: a set of
119
- multiple-choice questions drawn from that project's actual code.
120
-
121
- **Codex, Cursor, OpenCode, or any other agent**: `/meanwhile-build` is a Claude Code
122
- slash command, so run this instead and paste the printed instructions to
123
- your agent:
124
-
125
- ```
126
- uvx meanwhile-cli build-prompt --lang en --repo .
127
- ```
128
-
129
- It prints the exact same instructions the slash command uses, so the pool
130
- comes out the same either way.
131
-
132
- Commit `.meanwhile/pool.json`. It's checked into the repository on purpose, so
133
- everyone on the team plays the same pool and nobody pays to regenerate it.
134
-
135
- `--lang` takes a short language code — `tr` or `en` — not a language name:
136
- `--lang Turkish` is not recognised and silently leaves the interface in
137
- English. Those two codes are the only ones the interface itself speaks.
138
-
139
- A pool built by the build prompt records the language it was written in, and
140
- the playing interface follows it, with `--lang` overriding that for just your
141
- pane. `--lang` on `build-prompt` controls what language the cards themselves
142
- get written in — any code is allowed there, so `--lang de` writes German cards
143
- and leaves the interface in English. `pool.json` carries the same code in a
144
- top-level `language` field (it defaults to `en` when absent); the playing
145
- interface's own chrome — the waiting banner, "correct"/"wrong", and so on —
146
- follows that field, in `tr` or `en` and in English for anything else.
147
-
148
- ## Daily use
149
-
150
- Open a second terminal pane next to the one running your agent — a tmux
151
- split, an iTerm pane, or just another window — sitting in the *same directory
152
- you started it in*, and run:
153
-
154
- ```
155
- uvx meanwhile-cli
156
- ```
157
-
158
- `uvx` runs it without installing anything. To install it for good instead,
159
- `uv tool install meanwhile-cli` (or `pipx install meanwhile-cli`) — the
160
- command that leaves on your path is plain `meanwhile`, and it takes the same
161
- arguments everywhere this README writes `uvx meanwhile-cli`.
162
-
163
- (While developing against a checkout of this repo instead of the published
164
- package, use `uv run meanwhile` from inside it.)
165
-
166
- Open it whenever you like — before your first prompt is fine. The pane sits
167
- waiting until your agent starts working, wakes up within about a second of
168
- you submitting a prompt, and serves multiple-choice cards about the
169
- codebase one at a time. Answer with the number key for the choice you want;
170
- the footer only ever offers the keys the card on screen can actually take.
171
- Answering shows the explanation, `space` deals the next card, and a streak
172
- counter tracks that wait. If a wait runs long enough to reach the end of the
173
- pool, the pane says so and sits out the rest of it rather than repeating
174
- questions.
175
-
176
- The moment your agent finishes, the current card freezes and the pane shows
177
- `<Agent> is done ^` (or `the agent is done ^` if it can't tell which one),
178
- which is your cue to switch back.
179
-
180
- Then leave it open. It stays on that line — it does not clear itself or
181
- return to the waiting message — until your next prompt, which deals a fresh,
182
- re-shuffled round, so a long wait never exhausts the pool for the next one.
183
- You only ever press `q` when you are done for the day. Each wait is scored as
184
- its own session and added to your totals when it ends; a wait you answered
185
- nothing in is not recorded.
186
-
187
- The interface follows the language recorded in the pool. Override it for
188
- just your pane with `--lang` and a short code, for example `uvx
189
- meanwhile-cli --lang en`, regardless of what the pool says. `tr` and `en`
190
- are the two the interface speaks; any other code leaves it in English.
191
-
192
- ## How it works
193
-
194
- meanwhile is three independent parts that only ever talk to each other
195
- through two files on disk — nothing imports anything else, and each part can
196
- be tested alone:
197
-
198
- ```
199
- /meanwhile-build (Claude Code slash command)
200
- build-prompt (same instructions, any other agent)
201
- │ writes
202
- ▼
203
- .meanwhile/pool.json ← committed to the repo, shared by the team
204
- │ reads
205
- ▼
206
- meanwhile TUI ──watches──► ~/.meanwhile/state/<project-hash>.json
207
- (uvx, separate pane) ▲
208
- │ writes
209
- hooks: plugin (Claude Code, Codex), or `meanwhile
210
- hooks install` (those two, Cursor, OpenCode)
211
- ```
212
-
213
- - **`/meanwhile-build`** (or `meanwhile build-prompt` for every other agent) is
214
- the only part that costs tokens or touches an LLM. It reads the repository
215
- and writes `.meanwhile/pool.json`.
216
- - **The hooks** are a few lines of pure bash (`hooks/meanwhile-state.sh` for
217
- the plugin; the same script, packaged inside the wheel and copied to
218
- `~/.meanwhile/meanwhile-state.sh`, for `hooks install`), fired on the moment
219
- each agent starts and stops working — `UserPromptSubmit`/`Stop` for
220
- Claude Code and Codex, `beforeSubmitPrompt`/`stop` for Cursor, and the
221
- `session.status` event for OpenCode, whose plugin file does nothing but
222
- call the same script. They write nothing but a
223
- `{"status": "busy"|"idle", "ts": ..., "agent": ...}` file under
224
- `~/.meanwhile/state/`, keyed by a hash of the directory the agent was
225
- started in (that is what `CLAUDE_PROJECT_DIR` holds for Claude Code — not
226
- necessarily the git root, which is why the TUI must be launched from the
227
- same directory). No interpreter, no network, no measurable delay added to
228
- your prompt.
229
- - **The `meanwhile` TUI**, run separately as `uvx meanwhile-cli`, polls
230
- that state file and renders cards from the pool. It finds
231
- `.meanwhile/pool.json` at the git root above wherever you launched it. It makes no LLM calls and
232
- parses no source code at play time — it only reads the two JSON files
233
- above.
234
-
235
- ## Refreshing a stale pool
236
-
237
- `pool.json` records the commit it was built from. If the repository has
238
- moved on more than about 50 commits since then, the TUI shows a banner —
239
- `pool is N commits behind — run the build command for fresh questions` — but
240
- it never regenerates the pool on its own. Silent background regeneration
241
- would spend tokens you didn't ask for, which is exactly what this design
242
- avoids. Run `/meanwhile-build` (or `meanwhile build-prompt`) again when you see
243
- the banner (or whenever the pool just feels stale) and commit the updated
244
- file.
245
-
246
- ## Scores
247
-
248
- Your running total and best streak are written to `.meanwhile/scores.json`.
249
- That file is gitignored — scores are local to your machine and never shared
250
- or synced between teammates.
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"
File without changes
File without changes