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 • Clients • The job • Install • Workflows • What it records • 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 |
Cursor isn't metered by the open-source wrapper (its usage isn't on an interceptable API); Codex and Vibe don'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 Python 3.11–3.14 and uv, plus one supported coding CLI already installed and logged in (claude, cursor-agent, codex, or vibe). 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 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 — none of it locked inside a single tool:
- Profile & company — who you are and your project's conventions (
profile/me,profile/company), composed into every session. - Learned notebook — what your tools notice about how you work, in one file they all mirror into; negatives ("what to avoid") are first-class.
- Personas — a selectable tone with a free, local greeting at launch (
gmlw persona list). - Rules — corrections you've demanded, captured as reusable reflexes (
~/.gmlw/rules/*.rule.md). - Mode-aware packaging — a
[startup.<mode>]matrix decides which sources compose for a plain session, a workflow, or authoring, with optional typed compression. - Plugins & overrides — swap a client's caller by id (
gmlw plugins list,[callers]).
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 Codex or 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
- User guide — task-oriented, first launch through daily work.
- CLI reference — every command, flag, and
--jsonoption. - Configuration — every
config.tomlsection, with examples. - Clients — per-client behaviour, setup, and limitations.
- Workflows — authoring, layout, scripts, rules, credentials.
- Troubleshooting — failures, diagnostics, reset/recovery.
- 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.
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.6.0.tar.gz.
File metadata
- Download URL: generic_ml_wrapper-0.6.0.tar.gz
- Upload date:
- Size: 553.5 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 |
67dbd7255787892c65525c993876f98264a50c7995e893197d6ce098528582d1
|
|
| MD5 |
4b4c2ce49e059923e74f16fa888c4740
|
|
| BLAKE2b-256 |
38c516bf9f51b4e4b1f2ffa92638813306173aac46e1052b329bdbd33d5701f7
|
File details
Details for the file generic_ml_wrapper-0.6.0-py3-none-any.whl.
File metadata
- Download URL: generic_ml_wrapper-0.6.0-py3-none-any.whl
- Upload date:
- Size: 249.1 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 |
cadeebdd5be074a7517fe1d4167d4cfd66ee1c90ff590aa647f86a47a0c8e48d
|
|
| MD5 |
e347134cd824a064722eb397b4755006
|
|
| BLAKE2b-256 |
23e410a7c7c4c12bdb2602fad7d3653514ce3ac9294c8ceeb7479b82f15c8f91
|