Skip to content
MrJev

Live Jev

Control Ableton Live with one sentence, in Japanese or English, from a bar that appears over the session and gets out of the way.

View on GitHub →

Hands-on review

Say a phrase in Japanese or English and it moves a fader. A local phrase table catches most of it; what misses sends your whole session.

Good for

  • A local phrase table that resolves most commands without any network call
  • 326 tests that pass offline with Ableton fully stubbed out
  • Keychain on the Swift side, and the key is never written from Python

Watch out for

  • Every track, device, parameter value, clip and scene name goes with the request
  • No cap, no budget, no cache — a 40-track session is a 157 KB request
  • No CI at all, and the key reader scans six shell rc files

Tested Sep 20, 2026 at ecba7e0 · python:3.13-slim in Docker with --network none, against a self-signed TLS stand-in; no Ableton needed

How we reviewed this: we ran it in python:3.13-slim with --network none, pointing api.typesafe.ai at a self-signed TLS stand-in, using the project’s own Ableton stub. We made no Jev calls.

What it does

A macOS daemon that turns a spoken phrase — Japanese or English — into an Ableton Live action. “パッドをちょっといい感じにして” nudges the Pad track’s volume; “make the bass louder” does the obvious thing. A local regex phrase table handles what it can, and only the leftovers go to Jev as a set of multiple-choice questions whose options are the actual tracks, devices and parameters in your session.

The shape is right. Asking a decision model to pick from this session’s real track list is much better than asking a language model to invent a command, and the answers map onto a whitelist of Remote Script calls rather than arbitrary code.

The local-first part is not decoration. In a twenty-command session on a three-track project, thirteen commands never touched the network.

What a request contains

intent.py:401:

state = {
    "utterance": utterance,
    "tracks": [
        {
            "i": track.index,
            "name": track.name,
            "volume": track.volume_display,
            "pan": track.pan,
            "mute": track.mute,
            "solo": track.solo,
        }
        for track in snapshot.tracks
    ],
    "tempo": snapshot.tempo,
    "playing": snapshot.playing,
    "scenes": [{"i": scene.index, "name": scene.name} for scene in snapshot.scenes],
}

Plus, as question criteria: every device name including third-party plugins, every candidate parameter with its current value, clip names, scene names, return bus names, and your own aliases file. Nothing is redacted and nothing is truncated — the only limits are counts (250 clips, 250 devices, 250 parameters, 250 scenes, 26 returns).

File paths are the one thing held back: Track.path and Device.path exist in the snapshot and build_request never reads them. That looks deliberate and it is the right call.

Whether the rest matters is a judgement about your own work. Track names and clip names in an unreleased project are the names of songs and ideas; the device list is a fingerprint of which plugins you own. It is not secrets in the credential sense, and the README does not hide that a session summary is sent. But “adjust the pad” transmitting your whole arrangement is worth knowing before you leave it running.

The request grows with the session. Serialising build_request at three sizes:

session bytes questions
3 tracks, 1 device, 1 param, 1 clip 16,601 19
16 tracks, 2 devices, 8 params, 4 clips 48,117 58
40 tracks, 3 devices, 12 params, 8 clips 157,285 130

At Jev’s input pricing a 40-track session is a few hundredths of a cent per phrase, so this is not expensive. It is unbounded, though: no cap, no budget, no rate limit, and no cache, so the same phrase twice is billed twice. A chained command costs one request per clause, up to four.

Google is there but switched off

There is a second model in the tree, and it is not OpenRouter — it is Gemini, used to rewrite ambiguous phrases. daemon.py:588:

# The public build does not use slow LLM rewriting for ambiguous requests.
# Enable it only for experiments with LIVE_JEV_LLM=1. Otherwise return the clarification unchanged.
if self.llm_key is None and os.environ.get("LIVE_JEV_LLM", "0") == "1":
    self.llm_key = read_key("GEMINI_API_KEY")

Off unless you opt in, with the comment explaining why. That is how an optional third party should be wired.

It won’t start from some directories

bridge_client.py:29:

for candidate in (here.parent / "codex-live-bridge", here.parents[1]):

If the checkout sits one level below the filesystem root, here.parents[1] does not exist. We copied the repo to /app and imported it:

  File "/usr/local/lib/python3.13/pathlib/_local.py", line 49, in __getitem__
    raise IndexError(idx)
IndexError: 1

From /x/y/app it imports fine. This runs at module scope, so there is no way around it, and containers are exactly where a repo ends up at /app. On a Mac, where this is meant to run, you will never see it.

Verdict

MIT, 36 stars, one day old. python -m unittest discover gives 326 tests, 1 skipped, 0 failures with no network and no Ableton — the stub in tests/support.py is a real stateful fake, not a mock that agrees with everything, and the single skip is the author’s own marker on a script not in the public copy. That is a genuinely good test suite.

And there is no CI. Zero workflows, no .github/ directory. 326 tests nobody runs automatically.

The key handling is mostly careful — Keychain on the Swift side, never written to disk from Python, no key in the logs — with one habit worth flagging: read_key scans .zshenv, .zprofile, .zshrc, .bash_profile, .bashrc and .profile line by line for an export. The reason is good (apps launched from Finder don’t inherit your shell environment) but it means the daemon reads six of your dotfiles at startup.

We’ve reported the path crash, the missing CI and the unbounded payload growth. If you make music in Ableton and don’t mind your session structure leaving the machine, this is the most fun thing in the directory.

For the same idea pointed at a browser, see jev-browser.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 20, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Apps & Browser Extensions

Jev Chat Assistant

★ 8.2k▲ 105

jev-chat/jev-chat-jarvis

Android chat assistant that reads the conversation on screen through the accessibility service, has Jev judge the other person's intent and the risk, and fills in a drafted reply that you send yourself. Chinese and English editions.

Kotlin

OpenMuse

★ 6.1k▲ 479

CopilotKit/openmuse

CopilotKit's self-hosted personal agent with its own browser, terminal and files; Jev decides whether a reply becomes a clarification panel, a comparison of options or plain prose, and overrides the agent only when confident. Alpha.

TypeScriptReviewed

Shapeshift

★ 1.1k▲ 16

anishfn/shapeshift

One text box that turns what you type into the right small interface, an event card or a checklist or a bill split, with Jev choosing which; falls back to rules when no key is set.

TypeScriptReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.