A wrapper around an ML coding CLI: enter at a job, get a named resumable session, optionally driven by a workflow.
Project description
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 • The job • Install • Workflows • What it records • 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.
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 Python 3.11–3.13 and uv. The console script is gmlw.
uv tool install generic-ml-wrapper # or: uv sync --extra dev (from a clone)
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/rule folders. Pick the client per run with --client claude|cursor|codex|vibe, or set a default in config.toml.
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, global rules, 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, 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 is the one wired today.)
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.13)
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file generic_ml_wrapper-0.1.0.tar.gz.
File metadata
- Download URL: generic_ml_wrapper-0.1.0.tar.gz
- Upload date:
- Size: 382.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69d73dea0efc52fe43a5d53e9874b060df9891b05112a73fdcc9dbdbbbc2a16e
|
|
| MD5 |
d4e855cada485593ced20be2e66a650f
|
|
| BLAKE2b-256 |
3574dd9cb6d6ef55dd6cbb798b881bc5d41ed743c7cd20266aa832a3c602084d
|
File details
Details for the file generic_ml_wrapper-0.1.0-py3-none-any.whl.
File metadata
- Download URL: generic_ml_wrapper-0.1.0-py3-none-any.whl
- Upload date:
- Size: 117.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c4a30a3bd573ec2ed9af96230f47078c2417707bef3b2cacded70fdb7c2f99d
|
|
| MD5 |
7040fa76f5e27bde54e6d5d2412d3990
|
|
| BLAKE2b-256 |
1cce961814eb2d2b4aca3e3f5b91ec9b1606344477ddf4a6dc6103addc1227fe
|