Source on GitHub

truewire.toml reference

A Truewire project is a directory holding a truewire.toml. Every command finds the nearest one above the working directory, or takes --project PATH. truewire init writes the file with the [cores.*] and [python.cores.*] tables its --template needs; see docs/cores.md for what each template wires.

#:schema https://truewire.dev/schemas/truewire.toml.json
[project]
name = "petstore"            # required. Also the default Python package name.

[spec]
dir = "spec"                 # default "spec": holds endpoints/ and schemas.json

[secrets]                    # optional: credential variable *names*, never values.
required = ["PETSTORE_API_KEY"]
optional = ["PETSTORE_ADMIN_KEY"]

[policy]                     # optional: what the client may do on its own behalf.
rate = 10                    # requests per second it paces itself to; unpaced when absent
retry = false                # default false
refuse = ["pets.delete"]     # endpoint ids it refuses to call

[cores.default]              # one table per symbolic core name a router.json can declare.
meta = { type = "object", properties = { public = { type = "boolean" } }, additionalProperties = false }

[python]
package = "petstore"         # default: [project].name
src = "src"                  # default "src": the package lives at <src>/<package>
name = "Petstore"            # root class name; default: PascalCase of [project].name
backend = "backend.py"       # optional: a module exporting `generator`, a Generator subclass
ruff = "ruff.toml"           # optional: formatter config; default is the one shipped with truewire
pyright = false              # run pyright after generate; also implied by a pyrightconfig.json

[python.cores.root]          # the core the root router.json names (usually "root")
base = "petstore.core:ClientBase"

[python.cores.default]       # every other symbolic core: base class + how children get the transport
base = "petstore.core:Endpoint"

[python.cores.streams]       # a base built through `new(client, *, ...)` declares its keywords
base = "petstore.core:StreamsBase"
forward = ["market_client"]  # passed from the composing class's own same-named field
params = { network = "petstore.core:Network" }   # exposed to the caller, typed by import
children = { market_data = "market_client", private = "private_client" }

[typescript]
package = "petstore"         # default: [project].name; the package lives at <src>/<package>
src = "src"                  # default "src"
name = "Petstore"            # root class name; default: [python].name, else PascalCase of [project].name

[rust]
package = "petstore"         # default: [project].name; the modules live at <src>/<package>, lib.rs at their top
src = "src"                  # default "src"
name = "Petstore"            # root struct name; default: [python].name, else PascalCase of [project].name

[score]                      # optional
stranger = 2026-10-12        # the day a newcomer last completed the quickstart unaided

Sections

  • [project]: name is required. It names the project in output and is the default for [python].package.

  • [spec]: dir relocates the spec tree. truewire check, examples, surface, mock and generate all read <dir>/endpoints/**/endpoint.json, <dir>/**/router.json and <dir>/schemas.json.

  • [secrets]: required and optional, lists of environment variable names and nothing else: a value, a path or another key is an error. The values live in .env, which truewire init git-ignores. truewire standards uses the names to flag a recorded example that leaked a real credential-shaped value.

  • [policy]: rate, a positive number of requests per second; retry, true or false; refuse, endpoint ids (the dotted function path truewire capture takes). truewire check fails on a refuse id that names no endpoint in the spec. refuse is generated (W15): a refused endpoint keeps its method in Python, TypeScript and Rust, and the method fails with the package's RefusedByPolicy (a LogicError, generated into policy.py, policy.ts or policy.rs) before any request is made. generate fails on an id that names no generated endpoint, and on a refused endpoint whose method is written by hand: surface: handwritten, an extras entry that replaces it, or a [python].backend skip_endpoint. Go does not render refusals yet, and generate go says so for each one. rate and retry are generated onto the root (RATE/RETRY in Python and TypeScript, associated consts in Rust), and the hand-written core builds its runtime HttpClient from them (cores): request starts spaced 1 / rate seconds apart, and, with retry, up to three attempts for a connection that never opened or a 429/503, waiting its Retry-After up to 30 s. Go renders neither yet.

  • [score]: stranger is the date truewire score prints for S12 (docs/shape/score.md): the day someone who had never seen the project completed its quickstart from the published package and the public docs alone. A TOML date or the same as a "YYYY-MM-DD" string. Absent, the row is unchecked and the project is not done. No command writes it; the person who watched the run does.

  • [cores.<name>]: the JSON Schema every endpoint's meta must satisfy when its nearest router.json resolves to <name>. Omit meta for a core that reads nothing per call.

  • [python]: where the generated Python package goes and how it is finished. name is the root class, and it must not be the class one of the spec's own top-level router groups renders -- a client called Weather over a weather/ group has the root import the class it then declares itself (docs/spec/authoring.md rule 18). The same holds for [typescript].name and [rust].name below.

  • [python.cores.<name>]: base is module.path:Class, the hand-written class every generated endpoint under that core subclasses. Nothing is imported during generation (ADR 0011); the three optional keys below say how a composite built on this base is constructed.

    • children maps a composed child attribute to the field of self it receives as its transport, for a base whose children need different connections ({ spot = "spot_client", streams = "private_client" }). Unlisted children get self.client.
    • forward lists the keywords of this base's new(client, *, ...) that the composing class passes from its own same-named fields: forward = ["market_client"] renders Streams.new(self.private_client, market_client=self.market_client).
    • params maps the keywords of new() a caller supplies to their types. A string is module.path:Name (imported by the composing module) or a bare builtin (str); the table form { type = "...", required = false } makes it optional. The composing class exposes each as a keyword-only parameter (def token(self, *, network: Network)), unless its own core is this same core, in which case it already carries the field and forwards self.<name>.

    Declaring forward (even as []) or params means the base is built through new(client, *, ...); declaring neither builds it as Child(client=self.<field>). The protocols a base satisfies are in truewire_core.contract.

  • [[python.extras."<router node>"]]: hand-written classes folded into a generated router node (file, class, optional replaces).

  • [typescript]: where truewire generate typescript writes the TypeScript package (<src>/<package>/) and what its root class is called. There is no per-core table: a generated class takes its core as a constructor argument typed by the @truewire/core contract (HttpEndpoint<Meta>, CommandEndpoint<Meta>, DualEndpoint<Meta>, StreamEndpoint<Meta>, ReplyStreamEndpoint<Meta>), and the hand-written <package>/core/index.ts satisfies it by shape, so nothing is imported or resolved at generation. The composition keys are read from [python.cores.<name>], the one declaration serving both languages: a core declaring children or forward makes its router take a fields object -- one property per field the declarations name, exported as the <Class>Core interface -- and hand each child the field its children entry maps it to (client when unmapped), or the whole object to a child that is itself composite. params needs nothing: the hand-written core takes its parameters when it is built. See docs/typescript.md.

  • [[typescript.extras."<router node>"]]: hand-written classes folded into a generated TypeScript router (file, class, methods, optional replaces and field); what serves an endpoint whose spec declares surface: handwritten, which the TypeScript backend does not render. See docs/typescript.md.

  • [rust]: where truewire generate rust writes the Rust modules (<src>/<package>/, whose lib.rs the project's Cargo.toml names as its library path) and what its root struct is called. As for TypeScript there is no per-core table: a generated struct holds its core as an Arc<dyn HttpEndpoint<Meta>> from truewire-core, and the hand-written <package>/core module, which the generated lib.rs declares, implements the trait, so nothing is imported or resolved at generation. A core declaring children or forward makes its router take one parameter per field (Kraken::from_cores(market_client, private_client, spot_client)), a field needing several traits held as the combined trait contract.rs declares; see docs/rust.md.

Generated state

generate writes <src>/<package>/meta.py, one TypedDict per [cores.<name>] that declares a meta schema (SpotMeta for spot); the hand-written core annotates its meta parameter with it. truewire init writes the first one so the core template can import it before the first generate.

generate typescript writes <src>/<package>/meta.ts, one interface per core with a meta schema (DefaultMeta for default), which the hand-written core names as the Meta parameter of HttpEndpoint. generate rust writes <src>/<package>/meta.rs, one struct per such core, which the hand-written core implements HttpEndpoint<DefaultMeta> for.

generate writes .truewire/codegen/<language>.json (python.json, typescript.json, rust.json, go.json), the manifest of files it owns. Files not in the manifest are never deleted; generate --check compares the plan to it and each owned file's content to what the plan renders (formatted the way generate writes it), and generate --delete removes only what it owns. Commit the manifest: it is what lets --check on a fresh clone see a file an earlier plan owned that this one does not, which the next generate deletes. Only .truewire/cache/ is git-ignored (truewire init writes that line, and rewrites a bare .truewire/ line an older init wrote). On a checkout without a manifest, generate --check takes the plan as the owned file list -- it names every file generate would write -- and checks existence and content the same way, less that one difference.

Up to 0.10 the manifest was .truewire/<language>-files.json, git-ignored. generate moves one it finds to the new path and says so; generate --check reads it when the new path has none and reports it as a stale manifest, so the check fails until generate has moved it.