Numi
Numi is a blazingly fast CLI for generating code from Apple project resources. It turns asset catalogs, localization files, and file lists into generated accessors and helpers using built-in or custom templates.
Install
The installed binary is named numi.
What Numi Does
- reads
.xcassetsand generates image and color accessors - reads
.stringsand.xcstringsand generates localization helpers - reads
filesinputs and generates file-oriented helpers - renders built-in templates or custom Minijinja templates
- supports workspace orchestration when a repo has multiple
numi.tomlfiles
Numi is built for deterministic generation workflows: check in the outputs you want, regenerate them locally, and verify them in CI with numi check.
Quick Start
Initialize a starter config in your project:
Generate code:
Check whether committed generated files are up to date:
Workspace orchestration is also available when a repo has multiple numi.toml files:
Minimal Config
Numi uses numi.toml as its config filename.
= 1
[]
= "internal"
[]
= "module"
[]
= "Generated/Assets.swift"
[[]]
= "xcassets"
= "Resources/Assets.xcassets"
[]
= "swift"
= "swiftui-assets"
[]
= "Generated/L10n.swift"
[[]]
= "strings"
= "Resources/Localization"
[]
= "swift"
= "l10n"
You can also point localization generation at .xcstrings:
[]
= "Generated/L10n.swift"
[[]]
= "xcstrings"
= "Resources/Localization"
[]
= "swift"
= "l10n"
The starter config shipped with numi init lives in docs/examples/starter-numi.toml.
The same shape also works for Objective-C built-ins when you want an ObjC output:
[]
= "objc"
= "assets"
Commands
numi generate
- discovers the nearest manifest unless
--configis passed - uses the nearest local
numi.tomlfirst - runs one config for
[jobs]manifests and the whole workspace for[workspace]manifests - generates outputs for all named jobs, or only selected jobs when
--jobis repeated - prints non-fatal warnings to stderr
- may reuse cached parser outputs when inputs are unchanged
numi check
- computes what
generatewould write without modifying files - exits
0when outputs are current - exits
2when outputs are stale - prints warnings to stderr without turning the run into a failure
numi dump-context
- prints the exact JSON context a job template receives
- only supports single-config (
[jobs]) manifests and rejects workspace manifests - is the fastest way to debug or author custom templates
numi config locate
- prints the resolved config path
numi config print
- prints the resolved config with defaults materialized
- only supports single-config (
[jobs]) manifests and rejects workspace manifests
Built-In Templates
Numi currently ships these built-in templates:
- Swift:
language = "swift",name = "swiftui-assets"language = "swift",name = "l10n"language = "swift",name = "files"
- Objective-C:
language = "objc",name = "assets"language = "objc",name = "l10n"language = "objc",name = "files"
Fonts are supported in the template context and in custom-template workflows, but the first public release does not ship a dedicated built-in Swift template for fonts.
Current Limitations
.xcstringsplural and device-specific variations are skipped with warnings- the shipped
l10ntemplate currently emits simple no-argument accessors even when placeholder metadata is present in template context
Workspace Manifests
Repos with more than one numi.toml can orchestrate them from a repo-level numi.toml:
= 1
[]
= ["AppUI", "Core"]
[]
= "objc"
[]
= ["assets"]
Then each member job can keep only the built-in name:
[]
= "assets"
Workspace members are directory roots, not config-file paths. From the repo root, plain numi generate and numi check use that nearest workspace numi.toml automatically. From inside a member directory, add --workspace when you want the ancestor workspace instead of the local member manifest. Workspace defaults can provide template.builtin.language, but each job still needs to pick its own built-in name.
Custom Templates
Custom templates use Minijinja:
[]
= "Templates/l10n.jinja"
Numi supports {% include %} from:
- the including template's local directory
- the config-root search path
If the same include path exists in both places, Numi errors instead of guessing.
Start custom-template work with:
The stable context contract is documented in docs/context-schema.md.
Development
Useful local commands:
crates.io release notes for the workspace live in docs/crates-io-release.md.