Python quickstart
Go from an empty directory to generated, tested Python code.
You will write a JWT helper as a spec module: one magic_module call and two function signatures with their docstrings, no bodies. Jaunt turns that into real Python, you call it like any other module, and then you let Jaunt write the test too.
The generated snippets and representative output below come from a real run. Token use and cost vary with the contract, repository context, cache state, and configured model.
jaunt build drives the OpenAI Codex CLI (codex exec) and spends tokens. Install the codex CLI and run codex login before you start. Jaunt previews likely model work before a build and reports actual usage afterward.
1. Create a project
Start from an empty uv project and add jaunt. The base install already carries pytest, anyio, rich, and watchfiles, so there is nothing else to pull in.
uv init jwt-quickstart
cd jwt-quickstart
uv add jaunt
codex login # one-time Codex authentication2. Scaffold with jaunt init
uv run jaunt initIt prints nothing on success. It writes a jaunt.toml, a JAUNT_LOG journal, and a starter src/specs.py. The starter is already in module-magic style:
# Starter spec: `jaunt build` implements this module into `__generated__/`.
import jaunt
jaunt.magic_module(__name__)
def greet(name: str) -> str:
"""Return a friendly greeting for `name`.
Includes the name verbatim and ends with an exclamation mark.
"""
...The jaunt.magic_module(__name__) call at the top is the whole trick: every top-level stub in the file becomes a spec, no per-function decorator needed. The default config points at a src layout:
version = 1
[paths]
source_roots = ["src"]
test_roots = ["tests"]
generated_dir = "__generated__"init writes a fuller annotated config than this; the rest is covered in the config reference. Replace the starter greet spec with the two below.
3. Write the spec
A spec is a normal function with a real signature and a docstring that states the contract. The body is a placeholder — a bare ..., which never runs. Because magic_module governs the file, create_token is a spec on its own. verify_token carries a @jaunt.magic decorator only to declare a dependency; the decorator is the precision layer for a single symbol, and it still inherits whatever the module call set. Put this in src/specs.py:
A spec body never runs — Jaunt replaces it, and an unbuilt spec raises a clear error the first time you call it. So ... is all you need. Some type checkers (ty, Pyright) flag ... under a concrete return annotation; two remedies work equally well and jaunt leaves the choice to you: relax that rule on your spec roots, or write raise NotImplementedError in place of .... The two forms digest identically, so switching between them never restales a module. Jaunt does not scaffold type-checker config.
import jaunt
jaunt.magic_module(__name__)
def create_token(user_id: str, secret: str, ttl_seconds: int = 3600) -> str:
"""
Create an HS256-signed JWT for a user.
Structure: base64url(header).base64url(payload).base64url(signature)
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": user_id, "iat": <now>, "exp": <now + ttl_seconds>}
- Sign with HMAC-SHA256 using `secret` as the key.
- base64url segments must omit "=" padding.
- Raise ValueError if user_id is empty.
"""
...
@jaunt.magic(deps=[create_token])
def verify_token(token: str, secret: str) -> dict:
"""
Verify an HS256-signed JWT and return its decoded payload.
- Split on "." into exactly three parts, else raise ValueError("malformed").
- Recompute the HMAC over header.payload and compare in constant time;
raise ValueError("invalid signature") on mismatch.
- Raise ValueError("expired") if the "exp" claim is at or before now.
- Otherwise return the payload dict with "sub", "iat", and "exp".
"""
...The type hints and the docstring are the contract. deps=[create_token] tells Jaunt that verify_token depends on create_token, so the two build together and verify_token restales if create_token's public shape changes.
4. Build
uv run jaunt build --progress plain[build] specs: generating
[build] specs: attempt (1/3)
[build] specs: done (attempt 1)
[build] specs: validating
[build] 1/1 ok=1 fail=0 specs
[build] done 1/1 ok=1 fail=0
Cost: 1 API call(s), 0 cache hit(s)
Tokens: 134,368 prompt + 4,950 completion = 139,318 total
Cached prompt tokens: 118,016
Emitted 1 .pyi stub(s).
specs context: 3k chars (~765 tok) — preamble 91%, blueprint 5%, repo_map 3%, package_context 1%
Built 1 module(s), skipped 0.Jaunt read the module, drove codex exec, checked the result against your signatures, and wrote a real module to src/__generated__/specs.py. Two docstrings became roughly a hundred lines that do the tedious part right: canonical JSON, base64url without padding, a constant-time signature compare.
def create_token(user_id: str, secret: str, ttl_seconds: int = 3600) -> str:
if user_id == "":
raise ValueError("user_id is empty")
now = int(time.time())
header_segment = _json_segment({"alg": "HS256", "typ": "JWT"})
payload_segment = _json_segment({"sub": user_id, "iat": now, "exp": now + ttl_seconds})
signature_segment = _base64url_encode(_signed_bytes(header_segment, payload_segment, secret))
return f"{header_segment}.{payload_segment}.{signature_segment}"Jaunt also emitted src/specs.pyi next to the spec, so your editor sees the real signatures.
5. Call it
Import the spec module, not the generated one. The magic_module call rebinds each spec name to its generated implementation the first time you touch it.
PYTHONPATH=src uv run python - <<'PY'
from specs import create_token, verify_token
token = create_token("user-42", "s3cr3t")
print("token:", token[:32], "...")
print("claims:", verify_token(token, "s3cr3t"))
try:
verify_token(token, "wrong-secret")
except ValueError as e:
print("rejected:", e)
PYtoken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX ...
claims: {'sub': 'user-42', 'iat': 1783199036, 'exp': 1783202636}
rejected: invalid signatureCall a spec before building it and you get a clear error instead of a mysterious None:
JauntNotBuiltError: Spec specs:create_token has not been built yet. Run `jaunt build` and try again.6. Let Jaunt write the test
A @jaunt.test spec describes what to check; Jaunt writes the pytest. Test specs are always explicit decorators, whichever style your source uses. Put this in tests/specs_jwt.py:
from __future__ import annotations
import jaunt
from specs import create_token, verify_token
@jaunt.test(targets=[create_token, verify_token])
def test_roundtrip() -> None:
"""
Cover the token lifecycle:
- A token from create_token verifies and returns a payload whose "sub"
matches the user id.
- verify_token raises ValueError on a tampered signature.
- verify_token raises ValueError("expired") for a token whose ttl has passed.
"""
...Generate the test and run pytest in one step:
PYTHONPATH=src uv run jaunt test --progress plain[test] tests.specs_jwt: generating
[test] tests.specs_jwt: attempt (1/2)
[test] tests.specs_jwt: done (attempt 1)
[test] tests.specs_jwt: validating
[test] 1/1 ok=1 fail=0 tests.specs_jwt
[test] done 1/1 ok=1 fail=0
Emitted 1 .pyi stub(s).
Built 0 module(s), skipped 1.
........ [100%]
8 passed in 0.02s
Generated 1 test module(s), skipped 0.One test spec became eight cases. The generated file lives at tests/__generated__/specs_jwt.py; the example anchored to your docstring reads:
@pytest.mark.jaunt_tier("example")
def test_roundtrip() -> None:
token = create_token("user-123", "shared-secret", ttl_seconds=60)
payload = verify_token(token, "shared-secret")
assert payload["sub"] == "user-123"
header_segment, payload_segment, signature_segment = token.split(".")
tampered_signature = signature_segment[:-1] + _different_char(signature_segment[-1])
tampered_token = ".".join((header_segment, payload_segment, tampered_signature))
with pytest.raises(ValueError, match="^invalid signature$"):
verify_token(tampered_token, "shared-secret")
expired_token = create_token("user-123", "shared-secret", ttl_seconds=-1)
with pytest.raises(ValueError, match="^expired$"):
verify_token(expired_token, "shared-secret")Jaunt derived five more cases beside it — header shape, signature recomputation, the empty-user_id guard, the wrong-secret path, and malformed tokens — each tagged @pytest.mark.jaunt_tier("derived").
That is the whole loop: write intent, build, call, test. From here you keep editing docstrings and signatures; jaunt build regenerates only what changed.
Where to next
Try the TypeScript target
Sync a typed facade, generate ordinary TypeScript, and run a Vitest battery.
Adopt jaunt in an existing project
Convert one function in a real repo without touching the rest.
Whole-class specs and the daemon
Design a class from a docstring, then let a background daemon rebuild on commit.
How jaunt works
What runtime forwarding and incremental builds actually do.
Writing better specs
Get sharper generated code from sharper contracts.
Jaunt builds Jaunt
Since 1.5.2 the framework is its own adopter: seven modules are magic-mode specs with committed generated bodies, fifteen more run contract batteries, and jaunt check gates Jaunt's own drift in CI.
TypeScript quickstart
Go from an empty directory to generated, typechecked, and tested TypeScript.