meterhouse-rotor 0.2.2__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 (37) hide show
  1. meterhouse_rotor-0.2.2/PKG-INFO +312 -0
  2. meterhouse_rotor-0.2.2/README.md +283 -0
  3. meterhouse_rotor-0.2.2/meterhouse/__init__.py +111 -0
  4. meterhouse_rotor-0.2.2/meterhouse/__main__.py +4 -0
  5. meterhouse_rotor-0.2.2/meterhouse/account.py +203 -0
  6. meterhouse_rotor-0.2.2/meterhouse/cli.py +345 -0
  7. meterhouse_rotor-0.2.2/meterhouse/config.py +141 -0
  8. meterhouse_rotor-0.2.2/meterhouse/daemon.py +219 -0
  9. meterhouse_rotor-0.2.2/meterhouse/health.py +86 -0
  10. meterhouse_rotor-0.2.2/meterhouse/identity.py +84 -0
  11. meterhouse_rotor-0.2.2/meterhouse/logging_setup.py +60 -0
  12. meterhouse_rotor-0.2.2/meterhouse/parser.py +313 -0
  13. meterhouse_rotor-0.2.2/meterhouse/pricing.py +66 -0
  14. meterhouse_rotor-0.2.2/meterhouse/reports.py +69 -0
  15. meterhouse_rotor-0.2.2/meterhouse/scanner.py +108 -0
  16. meterhouse_rotor-0.2.2/meterhouse/store.py +281 -0
  17. meterhouse_rotor-0.2.2/meterhouse/sync.py +146 -0
  18. meterhouse_rotor-0.2.2/meterhouse/ws_client.py +175 -0
  19. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/PKG-INFO +312 -0
  20. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/SOURCES.txt +35 -0
  21. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/dependency_links.txt +1 -0
  22. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/entry_points.txt +2 -0
  23. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/requires.txt +12 -0
  24. meterhouse_rotor-0.2.2/meterhouse_rotor.egg-info/top_level.txt +1 -0
  25. meterhouse_rotor-0.2.2/pyproject.toml +49 -0
  26. meterhouse_rotor-0.2.2/setup.cfg +4 -0
  27. meterhouse_rotor-0.2.2/tests/test_account.py +288 -0
  28. meterhouse_rotor-0.2.2/tests/test_config.py +56 -0
  29. meterhouse_rotor-0.2.2/tests/test_daemon.py +84 -0
  30. meterhouse_rotor-0.2.2/tests/test_health.py +50 -0
  31. meterhouse_rotor-0.2.2/tests/test_identity.py +25 -0
  32. meterhouse_rotor-0.2.2/tests/test_parser.py +124 -0
  33. meterhouse_rotor-0.2.2/tests/test_pricing.py +28 -0
  34. meterhouse_rotor-0.2.2/tests/test_prompts.py +159 -0
  35. meterhouse_rotor-0.2.2/tests/test_scanner.py +93 -0
  36. meterhouse_rotor-0.2.2/tests/test_sdk.py +25 -0
  37. meterhouse_rotor-0.2.2/tests/test_store.py +70 -0
@@ -0,0 +1,312 @@
1
+ Metadata-Version: 2.4
2
+ Name: meterhouse-rotor
3
+ Version: 0.2.2
4
+ Summary: Rotor — the Meterhouse metering agent. Reads Claude Code usage on this machine and reports it to your Meterhouse dashboard.
5
+ Author: Meterhouse
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/Aniruth-Sakthivel/claude-code-uusage
8
+ Project-URL: Repository, https://github.com/Aniruth-Sakthivel/claude-code-uusage
9
+ Project-URL: Documentation, https://github.com/Aniruth-Sakthivel/claude-code-uusage#readme
10
+ Keywords: meterhouse,rotor,claude-code,agent,usage,sync,dashboard
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: sync
22
+ Requires-Dist: httpx>=0.27; extra == "sync"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8.0; extra == "dev"
25
+ Provides-Extra: build
26
+ Requires-Dist: pyinstaller>=6.0; extra == "build"
27
+ Provides-Extra: realtime
28
+ Requires-Dist: websockets>=12.0; extra == "realtime"
29
+
30
+ # Rotor — Install & Scan Guide
31
+
32
+ **Rotor** is the Meterhouse metering agent: the part that sits on each machine
33
+ and turns as work happens. It scans Claude Code's local transcript files, stores
34
+ usage in a local SQLite database, and (optionally) syncs it to the central
35
+ server. **Scanning works fully offline — no server required.**
36
+
37
+ Installed as `meterhouse-rotor`; the command it provides is `meterhouse`.
38
+
39
+ > **Tracked activity ≠ official quota.** All numbers are token counts parsed from
40
+ > local transcripts — an estimate, not your Claude Max/Pro billing or quota.
41
+
42
+ ---
43
+
44
+ ## 1. Prerequisites
45
+
46
+ - **Python 3.10+** (check with `python --version`)
47
+ - **Claude Code** installed and used at least once on this machine, so transcripts
48
+ exist under `~/.claude/projects/` (Windows: `%USERPROFILE%\.claude\projects\`).
49
+
50
+ The agent uses **only the Python standard library** — there is nothing to
51
+ `pip install` for scanning.
52
+
53
+ > For **central mode** (sending usage to the dashboard) you install the
54
+ > `meterhouse` command from the repo, which needs **git** on `PATH` — see §7.
55
+
56
+ ---
57
+
58
+ ## 2. Install
59
+
60
+ Clone the repository and move into the agent folder:
61
+
62
+ ```powershell
63
+ git clone <your-repo-url> meterhouse
64
+ cd meterhouse\agent
65
+ ```
66
+
67
+ You can run it three ways:
68
+
69
+ **A. Install from PyPI (public release)** — once published, users can install:
70
+ ```powershell
71
+ pip install meterhouse-rotor
72
+ ```
73
+
74
+ **B. No install (simplest)** — run it as a module from the `agent/` folder:
75
+ ```powershell
76
+ python -m meterhouse --help
77
+ ```
78
+
79
+ **C. Install the `meterhouse` command locally** (so you can run it from anywhere):
80
+ ```powershell
81
+ pip install -e .
82
+ meterhouse --help
83
+ ```
84
+
85
+ Both install options are equivalent for command usage; the rest of this guide uses `python -m meterhouse`.
86
+
87
+ > The package is now public-ready as `meterhouse-rotor`. If the package has already been published on PyPI, use the `pip install meterhouse-rotor` command above.
88
+
89
+ ---
90
+
91
+ ## 3. Use the agent as a Python SDK
92
+
93
+ The package now exposes a simple SDK interface via `meterhouse.Agent`.
94
+
95
+ ### Install the package
96
+
97
+ ```powershell
98
+ pip install -e .
99
+ ```
100
+
101
+ ### Example usage
102
+
103
+ ```python
104
+ from meterhouse import Agent
105
+
106
+ agent = Agent(display_name="PC-01")
107
+ agent.register(
108
+ server_url="http://127.0.0.1:8000",
109
+ api_key="cfk_...",
110
+ display_name="PC-01",
111
+ )
112
+ print(agent.scan())
113
+ print(agent.sync())
114
+ print(agent.health())
115
+ ```
116
+
117
+ The SDK also supports running the daemon programmatically:
118
+
119
+ ```python
120
+ agent = Agent(display_name="PC-01")
121
+ agent.daemon()
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 4. Run a scan
127
+
128
+ Give this machine a name (once), then scan:
129
+
130
+ ```powershell
131
+ python -m meterhouse identity --display-name PC-01
132
+ python -m meterhouse scan
133
+ ```
134
+
135
+ Example output:
136
+
137
+ ```
138
+ [NEW] C:\Users\you\.claude\projects\my-proj\<uuid>.jsonl (+266 events)
139
+ ...
140
+ scan complete: new=18 updated=0 skipped=0 events+=1787
141
+ ```
142
+
143
+ The scan is **incremental and idempotent**:
144
+ - Unchanged files are skipped; changed files are read only from where they left off.
145
+ - Running it again processes nothing new (`events+=0`) — it never double-counts.
146
+
147
+ Run `scan` whenever you want fresh data (or schedule it — see §6).
148
+
149
+ ---
150
+
151
+ ## 4. View your usage
152
+
153
+ ```powershell
154
+ python -m meterhouse today # today's tokens by model
155
+ python -m meterhouse week # last 7 days
156
+ python -m meterhouse stats # all-time totals, by model, top projects
157
+ ```
158
+
159
+ `stats` example:
160
+
161
+ ```
162
+ Meterhouse - all-time tracked usage
163
+ Sessions: 13
164
+ Input tokens: 6.2M Output tokens: 1.1M Cache read: 280M ...
165
+ By model: claude-opus-4-8 ... claude-sonnet-5 ...
166
+ Top projects: Github/hotel-demo ...
167
+ (estimate only - not official Max/Pro quota)
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 5. Where your data lives
173
+
174
+ | What | Default location | Override with |
175
+ |---|---|---|
176
+ | Usage database | `~/.claude/meterhouse/usage.db` | `METERHOUSE_DB` |
177
+ | Machine identity/config | `~/.claude/meterhouse/agent.json` | `METERHOUSE_CONFIG` |
178
+ | Transcripts it reads (read-only) | `~/.claude/projects/**/*.jsonl` | auto-discovered |
179
+
180
+ Example with a custom DB path (PowerShell):
181
+ ```powershell
182
+ $env:METERHOUSE_DB = "D:\data\usage.db"
183
+ python -m meterhouse scan
184
+ ```
185
+
186
+ The agent **never modifies Claude Code's files** and never stores prompts,
187
+ responses, or source code — only token counts and metadata.
188
+
189
+ ---
190
+
191
+ ## 6. Automatic scanning (Windows Task Scheduler)
192
+
193
+ Run a scan every 15 minutes without thinking about it (one line):
194
+
195
+ ```powershell
196
+ schtasks /Create /SC MINUTE /MO 15 /TN "Meterhouse Scan" /TR "python -m meterhouse scan --quiet" /ST 00:00
197
+ ```
198
+
199
+ If you installed via the "Connect PC" flow (§7) or `deploy/install.ps1`, the
200
+ task is instead named **"Meterhouse Scan+Sync"** and also pushes data to the
201
+ central server every 15 minutes.
202
+
203
+ ### Stop the agent from scanning
204
+
205
+ Remove whichever scheduled task applies to how you installed:
206
+
207
+ ```powershell
208
+ schtasks /Delete /TN "Meterhouse Scan" /F # local scan-only task
209
+ schtasks /Delete /TN "Meterhouse Scan+Sync" /F # Connect PC / install.ps1 task
210
+ ```
211
+
212
+ To pause it instead of deleting it (keeps run history, easy to re-enable):
213
+
214
+ ```powershell
215
+ schtasks /Change /TN "Meterhouse Scan+Sync" /DISABLE
216
+ schtasks /Change /TN "Meterhouse Scan+Sync" /ENABLE # resume later
217
+ ```
218
+
219
+ > `schtasks /TR` does not go through `cmd.exe`, so a raw `scan && sync`
220
+ > command line will not chain correctly — it's run via a small
221
+ > `meterhouse-scan-sync.cmd` wrapper batch file instead. If you set the task
222
+ > up by hand, point `/TR` at a `.cmd` wrapper rather than an inline `&&`.
223
+
224
+ ---
225
+
226
+ ## 7. Send data to the central server (optional)
227
+
228
+ Local scanning is enough for one machine. To feed a central dashboard:
229
+
230
+ 1. Sign in to the web app and open **Connect PC** (or ask your admin for an API key).
231
+ 2. The web app generates a one-line setup command for the machine you want to track.
232
+ This is the command you run in PowerShell on that PC.
233
+ 3. The command installs the agent, registers the machine with the server, scans local
234
+ Claude Code transcripts, and syncs the results back to the dashboard.
235
+
236
+ If you only have the website link, that is enough. The site does not scan your PC
237
+ from the browser; it only generates the install/connect command and gives you the
238
+ server URL and API key to use.
239
+
240
+ ```powershell
241
+ pip install meterhouse-rotor
242
+ meterhouse register --server https://YOUR-API-URL --api-key cfk_... --display-name PC-01
243
+ meterhouse scan
244
+ meterhouse sync
245
+ ```
246
+
247
+ Or use the Windows installer from the repo (`deploy/install.ps1`), which does
248
+ all four steps above and sets up the recurring scan+sync task automatically.
249
+
250
+ > `--server` must be the **API** server (e.g. `http://localhost:8000` in dev),
251
+ > not the dashboard's frontend URL (`http://localhost:5173/...`). Pointing it
252
+ > at the frontend URL will fail to register.
253
+
254
+ The agent connects by calling the server at the given API URL and authenticating
255
+ with the supplied API key. After registration, it reads local transcript files,
256
+ aggregates token events, and sends only usage metadata to the dashboard.
257
+
258
+ An administrator can also create an **API key** under Admin → Agent API keys, then share the key and API URL with each user.
259
+
260
+ `sync` only sends events the server hasn't seen; if the server is down it simply
261
+ retries next time (nothing is lost, nothing is double-counted). The dashboard
262
+ shows a system as **"Never synced"** until the first successful `sync` call —
263
+ registering alone, or running `scan` without `sync`, is not enough.
264
+
265
+ ---
266
+
267
+ ## 8. Troubleshooting
268
+
269
+ | Symptom | Fix |
270
+ |---|---|
271
+ | `scan complete: new=0 ... events+=0` on first run | No transcripts found. Confirm `%USERPROFILE%\.claude\projects\` exists and you've used Claude Code. |
272
+ | `python` not found | Install Python 3.10+ and ensure it's on `PATH` (`py -3` also works on Windows). |
273
+ | Want a clean re-scan | Delete the usage DB (`%USERPROFILE%\.claude\meterhouse\usage.db`) and run `scan` again. |
274
+ | `sync` says "Central mode not configured" | Run `register` first with `--server` and `--api-key`. |
275
+ | `sync` fails / offline | Expected when the server is unreachable; it retries on the next run. |
276
+
277
+ ---
278
+
279
+ ## Command reference
280
+
281
+ ```
282
+ python -m meterhouse scan [--display-name NAME] [--quiet]
283
+ python -m meterhouse today | week | stats
284
+ python -m meterhouse identity [--display-name NAME] [--set-display-name NAME]
285
+ python -m meterhouse register --server URL --api-key KEY [--display-name NAME]
286
+ python -m meterhouse sync [--quiet]
287
+ python -m meterhouse heartbeat
288
+ python -m meterhouse account [show | enable | disable]
289
+ python -m meterhouse --version
290
+ ```
291
+
292
+ ### Claude account reporting (optional, off by default)
293
+
294
+ `account` controls whether this machine also reports which Claude
295
+ subscription it is signed into, so an admin can see who is on which plan and
296
+ how much of its rate limit is used.
297
+
298
+ ```
299
+ python -m meterhouse account show # print the exact payload - sends nothing
300
+ python -m meterhouse account enable
301
+ python -m meterhouse account disable
302
+ ```
303
+
304
+ Enabled, the agent reads a fixed allowlist of fields from `~/.claude.json`:
305
+ account UUID, email, display name, organisation, plan tier, and the cached
306
+ rate-limit percentages. **OAuth tokens and credentials are never read**, and
307
+ `.credentials.json` is never opened. See `meterhouse/account.py` for the
308
+ allowlist and `tests/test_account.py` for the tests that enforce it.
309
+
310
+ Equivalent env var: `METERHOUSE_ACCOUNT_REPORTING=true`.
311
+
312
+ Run the test suite with `pip install pytest && python -m pytest`.
@@ -0,0 +1,283 @@
1
+ # Rotor — Install & Scan Guide
2
+
3
+ **Rotor** is the Meterhouse metering agent: the part that sits on each machine
4
+ and turns as work happens. It scans Claude Code's local transcript files, stores
5
+ usage in a local SQLite database, and (optionally) syncs it to the central
6
+ server. **Scanning works fully offline — no server required.**
7
+
8
+ Installed as `meterhouse-rotor`; the command it provides is `meterhouse`.
9
+
10
+ > **Tracked activity ≠ official quota.** All numbers are token counts parsed from
11
+ > local transcripts — an estimate, not your Claude Max/Pro billing or quota.
12
+
13
+ ---
14
+
15
+ ## 1. Prerequisites
16
+
17
+ - **Python 3.10+** (check with `python --version`)
18
+ - **Claude Code** installed and used at least once on this machine, so transcripts
19
+ exist under `~/.claude/projects/` (Windows: `%USERPROFILE%\.claude\projects\`).
20
+
21
+ The agent uses **only the Python standard library** — there is nothing to
22
+ `pip install` for scanning.
23
+
24
+ > For **central mode** (sending usage to the dashboard) you install the
25
+ > `meterhouse` command from the repo, which needs **git** on `PATH` — see §7.
26
+
27
+ ---
28
+
29
+ ## 2. Install
30
+
31
+ Clone the repository and move into the agent folder:
32
+
33
+ ```powershell
34
+ git clone <your-repo-url> meterhouse
35
+ cd meterhouse\agent
36
+ ```
37
+
38
+ You can run it three ways:
39
+
40
+ **A. Install from PyPI (public release)** — once published, users can install:
41
+ ```powershell
42
+ pip install meterhouse-rotor
43
+ ```
44
+
45
+ **B. No install (simplest)** — run it as a module from the `agent/` folder:
46
+ ```powershell
47
+ python -m meterhouse --help
48
+ ```
49
+
50
+ **C. Install the `meterhouse` command locally** (so you can run it from anywhere):
51
+ ```powershell
52
+ pip install -e .
53
+ meterhouse --help
54
+ ```
55
+
56
+ Both install options are equivalent for command usage; the rest of this guide uses `python -m meterhouse`.
57
+
58
+ > The package is now public-ready as `meterhouse-rotor`. If the package has already been published on PyPI, use the `pip install meterhouse-rotor` command above.
59
+
60
+ ---
61
+
62
+ ## 3. Use the agent as a Python SDK
63
+
64
+ The package now exposes a simple SDK interface via `meterhouse.Agent`.
65
+
66
+ ### Install the package
67
+
68
+ ```powershell
69
+ pip install -e .
70
+ ```
71
+
72
+ ### Example usage
73
+
74
+ ```python
75
+ from meterhouse import Agent
76
+
77
+ agent = Agent(display_name="PC-01")
78
+ agent.register(
79
+ server_url="http://127.0.0.1:8000",
80
+ api_key="cfk_...",
81
+ display_name="PC-01",
82
+ )
83
+ print(agent.scan())
84
+ print(agent.sync())
85
+ print(agent.health())
86
+ ```
87
+
88
+ The SDK also supports running the daemon programmatically:
89
+
90
+ ```python
91
+ agent = Agent(display_name="PC-01")
92
+ agent.daemon()
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 4. Run a scan
98
+
99
+ Give this machine a name (once), then scan:
100
+
101
+ ```powershell
102
+ python -m meterhouse identity --display-name PC-01
103
+ python -m meterhouse scan
104
+ ```
105
+
106
+ Example output:
107
+
108
+ ```
109
+ [NEW] C:\Users\you\.claude\projects\my-proj\<uuid>.jsonl (+266 events)
110
+ ...
111
+ scan complete: new=18 updated=0 skipped=0 events+=1787
112
+ ```
113
+
114
+ The scan is **incremental and idempotent**:
115
+ - Unchanged files are skipped; changed files are read only from where they left off.
116
+ - Running it again processes nothing new (`events+=0`) — it never double-counts.
117
+
118
+ Run `scan` whenever you want fresh data (or schedule it — see §6).
119
+
120
+ ---
121
+
122
+ ## 4. View your usage
123
+
124
+ ```powershell
125
+ python -m meterhouse today # today's tokens by model
126
+ python -m meterhouse week # last 7 days
127
+ python -m meterhouse stats # all-time totals, by model, top projects
128
+ ```
129
+
130
+ `stats` example:
131
+
132
+ ```
133
+ Meterhouse - all-time tracked usage
134
+ Sessions: 13
135
+ Input tokens: 6.2M Output tokens: 1.1M Cache read: 280M ...
136
+ By model: claude-opus-4-8 ... claude-sonnet-5 ...
137
+ Top projects: Github/hotel-demo ...
138
+ (estimate only - not official Max/Pro quota)
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 5. Where your data lives
144
+
145
+ | What | Default location | Override with |
146
+ |---|---|---|
147
+ | Usage database | `~/.claude/meterhouse/usage.db` | `METERHOUSE_DB` |
148
+ | Machine identity/config | `~/.claude/meterhouse/agent.json` | `METERHOUSE_CONFIG` |
149
+ | Transcripts it reads (read-only) | `~/.claude/projects/**/*.jsonl` | auto-discovered |
150
+
151
+ Example with a custom DB path (PowerShell):
152
+ ```powershell
153
+ $env:METERHOUSE_DB = "D:\data\usage.db"
154
+ python -m meterhouse scan
155
+ ```
156
+
157
+ The agent **never modifies Claude Code's files** and never stores prompts,
158
+ responses, or source code — only token counts and metadata.
159
+
160
+ ---
161
+
162
+ ## 6. Automatic scanning (Windows Task Scheduler)
163
+
164
+ Run a scan every 15 minutes without thinking about it (one line):
165
+
166
+ ```powershell
167
+ schtasks /Create /SC MINUTE /MO 15 /TN "Meterhouse Scan" /TR "python -m meterhouse scan --quiet" /ST 00:00
168
+ ```
169
+
170
+ If you installed via the "Connect PC" flow (§7) or `deploy/install.ps1`, the
171
+ task is instead named **"Meterhouse Scan+Sync"** and also pushes data to the
172
+ central server every 15 minutes.
173
+
174
+ ### Stop the agent from scanning
175
+
176
+ Remove whichever scheduled task applies to how you installed:
177
+
178
+ ```powershell
179
+ schtasks /Delete /TN "Meterhouse Scan" /F # local scan-only task
180
+ schtasks /Delete /TN "Meterhouse Scan+Sync" /F # Connect PC / install.ps1 task
181
+ ```
182
+
183
+ To pause it instead of deleting it (keeps run history, easy to re-enable):
184
+
185
+ ```powershell
186
+ schtasks /Change /TN "Meterhouse Scan+Sync" /DISABLE
187
+ schtasks /Change /TN "Meterhouse Scan+Sync" /ENABLE # resume later
188
+ ```
189
+
190
+ > `schtasks /TR` does not go through `cmd.exe`, so a raw `scan && sync`
191
+ > command line will not chain correctly — it's run via a small
192
+ > `meterhouse-scan-sync.cmd` wrapper batch file instead. If you set the task
193
+ > up by hand, point `/TR` at a `.cmd` wrapper rather than an inline `&&`.
194
+
195
+ ---
196
+
197
+ ## 7. Send data to the central server (optional)
198
+
199
+ Local scanning is enough for one machine. To feed a central dashboard:
200
+
201
+ 1. Sign in to the web app and open **Connect PC** (or ask your admin for an API key).
202
+ 2. The web app generates a one-line setup command for the machine you want to track.
203
+ This is the command you run in PowerShell on that PC.
204
+ 3. The command installs the agent, registers the machine with the server, scans local
205
+ Claude Code transcripts, and syncs the results back to the dashboard.
206
+
207
+ If you only have the website link, that is enough. The site does not scan your PC
208
+ from the browser; it only generates the install/connect command and gives you the
209
+ server URL and API key to use.
210
+
211
+ ```powershell
212
+ pip install meterhouse-rotor
213
+ meterhouse register --server https://YOUR-API-URL --api-key cfk_... --display-name PC-01
214
+ meterhouse scan
215
+ meterhouse sync
216
+ ```
217
+
218
+ Or use the Windows installer from the repo (`deploy/install.ps1`), which does
219
+ all four steps above and sets up the recurring scan+sync task automatically.
220
+
221
+ > `--server` must be the **API** server (e.g. `http://localhost:8000` in dev),
222
+ > not the dashboard's frontend URL (`http://localhost:5173/...`). Pointing it
223
+ > at the frontend URL will fail to register.
224
+
225
+ The agent connects by calling the server at the given API URL and authenticating
226
+ with the supplied API key. After registration, it reads local transcript files,
227
+ aggregates token events, and sends only usage metadata to the dashboard.
228
+
229
+ An administrator can also create an **API key** under Admin → Agent API keys, then share the key and API URL with each user.
230
+
231
+ `sync` only sends events the server hasn't seen; if the server is down it simply
232
+ retries next time (nothing is lost, nothing is double-counted). The dashboard
233
+ shows a system as **"Never synced"** until the first successful `sync` call —
234
+ registering alone, or running `scan` without `sync`, is not enough.
235
+
236
+ ---
237
+
238
+ ## 8. Troubleshooting
239
+
240
+ | Symptom | Fix |
241
+ |---|---|
242
+ | `scan complete: new=0 ... events+=0` on first run | No transcripts found. Confirm `%USERPROFILE%\.claude\projects\` exists and you've used Claude Code. |
243
+ | `python` not found | Install Python 3.10+ and ensure it's on `PATH` (`py -3` also works on Windows). |
244
+ | Want a clean re-scan | Delete the usage DB (`%USERPROFILE%\.claude\meterhouse\usage.db`) and run `scan` again. |
245
+ | `sync` says "Central mode not configured" | Run `register` first with `--server` and `--api-key`. |
246
+ | `sync` fails / offline | Expected when the server is unreachable; it retries on the next run. |
247
+
248
+ ---
249
+
250
+ ## Command reference
251
+
252
+ ```
253
+ python -m meterhouse scan [--display-name NAME] [--quiet]
254
+ python -m meterhouse today | week | stats
255
+ python -m meterhouse identity [--display-name NAME] [--set-display-name NAME]
256
+ python -m meterhouse register --server URL --api-key KEY [--display-name NAME]
257
+ python -m meterhouse sync [--quiet]
258
+ python -m meterhouse heartbeat
259
+ python -m meterhouse account [show | enable | disable]
260
+ python -m meterhouse --version
261
+ ```
262
+
263
+ ### Claude account reporting (optional, off by default)
264
+
265
+ `account` controls whether this machine also reports which Claude
266
+ subscription it is signed into, so an admin can see who is on which plan and
267
+ how much of its rate limit is used.
268
+
269
+ ```
270
+ python -m meterhouse account show # print the exact payload - sends nothing
271
+ python -m meterhouse account enable
272
+ python -m meterhouse account disable
273
+ ```
274
+
275
+ Enabled, the agent reads a fixed allowlist of fields from `~/.claude.json`:
276
+ account UUID, email, display name, organisation, plan tier, and the cached
277
+ rate-limit percentages. **OAuth tokens and credentials are never read**, and
278
+ `.credentials.json` is never opened. See `meterhouse/account.py` for the
279
+ allowlist and `tests/test_account.py` for the tests that enforce it.
280
+
281
+ Equivalent env var: `METERHOUSE_ACCOUNT_REPORTING=true`.
282
+
283
+ Run the test suite with `pip install pytest && python -m pytest`.
@@ -0,0 +1,111 @@
1
+ """Meterhouse local agent.
2
+
3
+ Original software (not derived from any third-party project). Scans Claude Code
4
+ JSONL transcripts on the local machine, stores a centralization-ready usage
5
+ record in local SQLite, and (later) syncs it to the central API.
6
+
7
+ The JSONL layout it reads is Claude Code's own on-disk data format, independently
8
+ observed from real transcripts (see docs/UPSTREAM_AUDIT.md for the format).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ __version__ = "0.1.2"
14
+
15
+ from .cli import build_parser, main as cli_main
16
+ from .config import AgentConfig
17
+ from .daemon import run_daemon
18
+ from .health import HealthState
19
+ from .identity import (
20
+ Identity,
21
+ default_config_path,
22
+ load_identity,
23
+ save_identity,
24
+ )
25
+ from .scanner import discover_files, scan as scan_files
26
+ from .store import Store, default_db_path
27
+ from .sync import SyncClient, sync_store
28
+
29
+ __all__ = [
30
+ "Agent",
31
+ "cli_main",
32
+ "build_parser",
33
+ "AgentConfig",
34
+ "HealthState",
35
+ "Identity",
36
+ "load_identity",
37
+ "save_identity",
38
+ "default_config_path",
39
+ "discover_files",
40
+ "scan_files",
41
+ "Store",
42
+ "default_db_path",
43
+ "SyncClient",
44
+ "sync_store",
45
+ "run_daemon",
46
+ "__version__",
47
+ ]
48
+
49
+
50
+ class Agent:
51
+ def __init__(
52
+ self,
53
+ config_path: str | None = None,
54
+ db_path: str | None = None,
55
+ display_name: str | None = None,
56
+ ) -> None:
57
+ self.config_path = config_path
58
+ self.db_path = db_path or str(default_db_path())
59
+ self.identity = load_identity(config_path=config_path, display_name=display_name)
60
+ self.config = AgentConfig.load()
61
+
62
+ def register(
63
+ self,
64
+ server_url: str,
65
+ api_key: str,
66
+ display_name: str | None = None,
67
+ ws_url: str | None = None,
68
+ ) -> dict:
69
+ self.identity.server_url = server_url.rstrip("/")
70
+ self.identity.api_key = api_key
71
+ if display_name:
72
+ self.identity.display_name = display_name
73
+ save_identity(self.identity, self.config_path)
74
+
75
+ if ws_url:
76
+ self.config.ws_enabled = True
77
+ self.config.ws_url = ws_url
78
+ self.config.save()
79
+
80
+ client = SyncClient(self.identity.server_url, self.identity.api_key)
81
+ return client.register(
82
+ self.identity.display_name,
83
+ self.identity.hostname,
84
+ self.identity.agent_version,
85
+ )
86
+
87
+ def scan(self, verbose: bool = False, project_dirs: list[str] | None = None) -> dict:
88
+ return scan_files(
89
+ system_id=self.identity.system_id,
90
+ db_path=self.db_path,
91
+ project_dirs=project_dirs,
92
+ verbose=verbose,
93
+ )
94
+
95
+ def sync(self, verbose: bool = False) -> dict:
96
+ if not self.identity.server_url or not self.identity.api_key:
97
+ raise RuntimeError(
98
+ "Central mode not configured. Call register() with server_url and api_key first."
99
+ )
100
+ store = Store(self.db_path)
101
+ try:
102
+ client = SyncClient(self.identity.server_url, self.identity.api_key)
103
+ return sync_store(store, client, verbose=verbose)
104
+ finally:
105
+ store.close()
106
+
107
+ def health(self) -> HealthState | None:
108
+ return HealthState.load()
109
+
110
+ def daemon(self, display_name: str | None = None) -> None:
111
+ run_daemon(display_name=display_name, db_path=self.db_path)
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()