A metering wrapper around your ML coding CLI
Run claude, cursor, codex, or vibe exactly as you know them — but on a job you tag, in a named, resumable session, with every turn's tokens and cost recorded. An application that uses an ML client, not an ML client pretending to be an application.
Why a wrapper • Clients • The job • Install • Workflows • What it records • Concepts • Guides • Design
Why a wrapper
The raw coding CLIs are black boxes. A session has no name you chose, so you can't find it again. A run's cost is invisible — you never learn what a task actually spent. The exact context a session launched with is gone the moment it starts. And every client does all of this differently, so nothing you learn about one transfers to the next.
gmlw is a thin, deterministic shell around the client that keeps the client's behaviour intact and adds the parts an application needs:
- Identity — you enter at a job you tag; the wrapper mints a named, resumable session on the client.
- Metering — a local relay sits between the client and its upstream API and records tokens and cost per turn, for any metered client, without changing a single thing about how the client runs.
- Provenance — the compiled context a session launched with is written to disk; opt in to a full transcript and every call's request, response, and usage is kept too.
- One surface — claude, cursor, codex, and vibe are all driven the same way and land in the same ledger.
The judgment stays in the client. The deterministic parts — session identity, launch, context compilation, persistence, metering — are Python you can read.
Clients at a glance
Four clients, one surface — but they are not identical. What each supports today:
| Client | Metering | Resume | Status line | Context delivery |
|---|---|---|---|---|
claude (claude) |
✅ | ✅ | ✅ | native --append-system-prompt-file |
cursor (cursor-agent) |
❌ | ✅ | ✅ | context-file instruction |
codex (codex) |
✅ | ✅ ¹ | ❌ | initial instruction |
vibe (vibe) |
✅ | ❌ | ❌ | initial instruction |
¹ Codex resumes from its first completed turn onward: it mints its own session id, which the wrapper binds off the wire rather than handing it one at launch.
Cursor isn't metered by the open-source wrapper (its usage isn't on an interceptable API); Vibe doesn't resume. See docs/CLIENTS.md for the per-client detail and setup.
The job
A job is the one concept everything hangs off. It's the piece of work you're tagging — a ticket, a refactor, an investigation — and it is the primary key of the whole ledger.
job ──► sessions ──► turns ──► tokens + cost
└───► context.md (what it launched with)
└───► transcript (opt-in: in / out / usage per call)
You start work at a job and read it back by job:
gmlw start REFACTOR-42 # mint + launch a session on the default client
gmlw start REFACTOR-42 --resume-latest # pick the latest session back up
gmlw jobs # every job with recorded activity
gmlw sessions REFACTOR-42 # that job's sessions, oldest first
gmlw export REFACTOR-42 # per-turn tokens + cost, totalled by model
Nothing about the client changes — you get its full TUI. The wrapper owns the identity, the launch, and the persistence around it.
Install
Requires one supported coding CLI already installed and logged in (claude, cursor-agent, codex, or vibe). The console script is gmlw.
Linux / macOS:
curl -LsSf https://raw.githubusercontent.com/danielslobozian/generic-ml-wrapper/main/install.sh | sh
Windows (PowerShell):
irm https://raw.githubusercontent.com/danielslobozian/generic-ml-wrapper/main/install.ps1 | iex
Each script ensures uv is present — installing it first if not — then runs uv tool install generic-ml-wrapper. No Python prerequisite: uv fetches its own interpreter. Already have uv?
uv tool install generic-ml-wrapper # or: uv sync --extra dev (from a clone)
Either way:
gmlw start MY-FIRST-JOB # first run self-seeds ~/.gmlw (owner-only)
On first run the wrapper creates ~/.gmlw/ (mode 0700) with a commented config.toml, a SQLite ledger, and the workflow, profile, environment and template folders. Pick the client per run with --client claude|cursor|codex|vibe, or set a default in config.toml.
Update
gmlw does not update itself. It checks PyPI at most once a day (cached, and only if [update] check is on — the default) and notes a newer version on the exit receipt when one exists; nothing is downloaded or installed on your behalf. To update:
uv tool upgrade generic-ml-wrapper # or: uv tool install --reinstall generic-ml-wrapper
Workflows
A workflow is a small operating context you author once and launch a job with. Rather than re-explaining the same standing instructions to the client every time, you compile them once — a base, your profile, the rules of your environment and role, and the workflow's own steps — into the context the session opens with.
gmlw workflow new doc-review # author a workflow (an authoring session, kept apart from work)
gmlw workflow list # the runnable workflows
gmlw start DOCS-1 --workflow doc-review # launch a job with that context compiled in
Workflows can carry their own credentials (gmlw creds set <workflow> <ENV_VAR>), injected into the child process at launch and stored 0600. Context compilation runs through an interceptor chain, so a step like context compression is an opt-in plug-in, not a fork of the engine.
What it records
Everything lives under ~/.gmlw/, owner-only, on your machine:
| Artifact | Where | What |
|---|---|---|
| Ledger | ledger.db (SQLite, WAL) |
jobs, sessions, per-turn token usage, per-session cost |
| Context | contexts/<job>/<session>.context.md |
the exact compiled context the session launched with |
| Transcript (opt-in) | transcripts/<job>/<session>/call_NNN.{in.json,out.sse,usage.json} |
every call's request, response, and usage — a portable, self-contained folder |
For Claude Code and Cursor, the wrapper renders a rich status line straight into the client's own status bar — the git branch, folder, model, context %, and live cost, plus the job's running total. (The status-line seam is client-agnostic; Claude and Cursor are wired today, and other parsers can be added.)
Your operating context, carried across clients
Beyond metering, gmlw builds a portable operating context that follows you from one client to the next — who you are, your project's conventions, a learned notebook, a selectable persona, and rules you've demanded be held to — composed from four independent axes and packaged per launch mode. See docs/CONCEPTS.md for the full model.
Workflows (above) are optional and separate from this personal layer. Every listing/reporting command also speaks --json for automation.
What gmlw does not do
- It is not a sandbox — it launches the real client with your credentials (see
SECURITY.md). - It does not meter Cursor, and does not resume Vibe (see the matrix).
- It does not call models itself — the optional context compressor records through
generic-ml-cache. - It ships no compression prompts — the compressor stays inert until you configure one.
Guides
See docs/README.md for the full map. Start with Concepts for the mental model, then the User guide for task-oriented recipes, or jump straight to the CLI reference · Configuration · Clients · Workflows · Troubleshooting · Design · Security · Contributing.
Develop
The gates are defined once in noxfile.py; CI is a thin caller of them, so what runs locally is byte-for-byte what runs in CI.
uv sync --extra dev # or: nox -s dev (builds the IDE .venv)
nox # lint · imports · typecheck · tests (3.11–3.14)
nox -s green # the whole gate in one env (lint · format · imports · pyright · coverage)
Strict ruff + pyright (over src and tests), import-linter hexagon contracts, and an 80% coverage floor all run in the gate. Every change goes on a branch (feature/… tech/… fix/… docs/… chore/… test/…), keeps nox -s green passing, and merges via PR — direct pushes to main are blocked by branch protection.
See docs/DESIGN.md for the architecture, SECURITY.md for the threat model, and CONTRIBUTING.md to get set up.
Family
Part of the generic-ml-* family, alongside
generic-ml-cache (record/replay execution cache — the wrapper's context compressor records through it) and
generic-ml-workflow.
License
Apache-2.0 — see LICENSE and NOTICE. Contributions welcome; see CONTRIBUTING.md.
Release files for generic-ml-wrapper 0.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| generic_ml_wrapper-0.10.0.tar.gz | 1.8 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| generic_ml_wrapper-0.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.2 MB
Release files / generic_ml_wrapper-0.10.0.tar.gz
| Download URL | generic_ml_wrapper-0.10.0.tar.gz |
|---|---|
| Size | 1.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f49ba7da0e5c6941ac785de19f003de929ba410acf3139b3ee5fce42a2b104e9
|
|
BLAKE2b-256 checksum How to use checksums |
e959b835d8755195afb764cba720b6aa7f24aea1d88ff6838b619152d4901c95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / generic_ml_wrapper-0.10.0-py3-none-any.whl
| Download URL | generic_ml_wrapper-0.10.0-py3-none-any.whl |
|---|---|
| Size | 382.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6f14ed56ca1c1cc276f6ff1bab5694ca7e5041bb60754832be19aaff5432eb7f
|
|
BLAKE2b-256 checksum How to use checksums |
757653fe51da1203a4d2eac586c29fe2e715baec2b0a527b9f77a9858ffdc5cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|