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.
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.claude-plugin/marketplace.json +1 -1
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.claude-plugin/plugin.json +1 -1
- meanwhile_cli-0.1.1/PKG-INFO +169 -0
- meanwhile_cli-0.1.1/README.md +154 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/plugin.json +1 -1
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/pyproject.toml +1 -1
- meanwhile_cli-0.1.1/src/meanwhile/__init__.py +1 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/app.py +33 -14
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/strings.py +4 -6
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_app.py +82 -28
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_strings.py +1 -1
- meanwhile_cli-0.1.0/PKG-INFO +0 -265
- meanwhile_cli-0.1.0/README.md +0 -250
- meanwhile_cli-0.1.0/src/meanwhile/__init__.py +0 -1
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.agents/plugins/marketplace.json +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/.gitignore +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/LICENSE +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/commands/meanwhile-build.md +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/codex-hooks.json +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/hooks.json +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/hooks/meanwhile-state.sh +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/agents.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/app.tcss +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/build_prompt.md +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/cli.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/game.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/hooks_cli.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/meanwhile-state.sh +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/pool.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/repo.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/scores.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/src/meanwhile/state.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_agents.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_cli.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_game.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_hooks_cli.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_plugin_files.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_pool.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_repo.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_scores.py +0 -0
- {meanwhile_cli-0.1.0 → meanwhile_cli-0.1.1}/tests/test_state.py +0 -0
|
@@ -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.
|
|
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"
|
|
@@ -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
|
|
16
|
-
#
|
|
17
|
-
|
|
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 =
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
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
|
|
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 = ("
|
|
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": "
|
|
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": "
|
|
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,
|
|
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:
|
|
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
|
|
226
|
-
"""
|
|
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 ==
|
|
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")
|
|
247
|
-
|
|
248
|
-
app.
|
|
249
|
-
|
|
250
|
-
|
|
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 ==
|
|
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
|
|
403
|
-
app = app_for([QUIZ],
|
|
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
|
|
459
|
+
await drive_to_done(app, pilot)
|
|
406
460
|
assert text_of(app.query_one("#status")) == strings.text(
|
|
407
|
-
"tr", "
|
|
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", "
|
|
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():
|
meanwhile_cli-0.1.0/PKG-INFO
DELETED
|
@@ -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.
|
meanwhile_cli-0.1.0/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|