🧵 Wefter
Wefter is a profile-driven automation runtime that lets you use Lua to define cli command trees that perform structured, template-based code generation, embedding, or introspection.
Quickstart 🚀
Get up and running with Wefter in a few steps by creating a project-local profile:
1. Installation
Install Wefter directly from crates.io:
Or install from source:
2. Set up a project-local profile
Create a .wefter directory in the project root with a profile folder:
3. Add auto.lua & init.lua
Create .wefter/my-profile/auto.lua so Wefter automatically activates this profile when inside the project directory:
-- .wefter/my-profile/auto.lua
return true
Create .wefter/my-profile/init.lua to define custom commands:
-- .wefter/my-profile/init.lua
return
4. Create a template
Create .wefter/my-profile/templates/component.js:
// .wefter/my-profile/templates/component.js
export
5. Run the command
Execute the CLI command:
Wefter will prompt for the component name, render the template, and create src/components/Button.js
Examples
Check more advanced use cases in the examples directory, you can also check this repository's .wefter directory.
Profiles 📑
A profile is a self-contained module written in Lua that defines a CLI command tree and provides the templates to use.
Profile directory structure:
my-profile/
init.lua
auto.lua (optional)
templates/
init.lua
init.lua must return a Lua table describing the command hierarchy.
Example:
return
This creates the following command:
wefter widget create
Each node may contain:
description- String describing the command (for showing help on bad usage)subcommand- Table with key, value pairs for other commandsexec- Function to execute when the command is called
A command can only have subcommand or exec
templates/
This directory contains all user-defined templates (visit Templates on how templates work and how to create them).
Templates are resolved relative to the profile’s templates/ directory.
i.e.
my-profile/
templates/
foo/
bar.md
baz.md
-- Create a file `hello.md` from template `my-profile/templates/foo/bar.md`
wefter..
-- Get rendered template as string from `my-profile/templates/baz.md`
local baz = wefter..
-- Render template to cli (wefter has an embedded markdown CLI renderer)
-- You can use this feature to build introspection commands (i.e list classes,
-- show dependencies, etc) and presenting them with your own markdown template
wefter..;
auto.lua
Helps wefter decide which profile to use for the current project. If no auto.lua is specified, then the profile must be explicitly specified using wefter -p my-profile.
auto.lua is executed for every profile (when no profile is specified with -p). This file must return a boolean (true if the profile applies, false otherwise). Only one profile must be valid at a time; Wefter will request explicit profile selection on conflict.
In auto.lua, only early-loading API modules (such as wefter.fs) are available. It does not have access to other profile-specific modules or templates.
Here's a simple example of detecting a profile for a Rust application:
return wefter..
For most project specific profiles (.wefter directory) a simple return true statement is enough.
Profile Locations
Profiles can be defined either system-wide or per project.
System profiles live in the configured data directory, ~/.local/share/wefter by default, but location can be changed in the configuration file ~/.config/wefter/wefter.toml:
= "/home/<user>/.local/share/wefter"
Paths vary by OS and are resolved using the Rust directories crate.
Project-local profiles live inside a folder named .wefter in the project root directory.
my-project/
src/
.wefter/ (Project-local profiles)
my-profile/
Use the data directory for reusable profiles, and .wefter/ for project-specific ones.
Templates 📐
Wefter uses Tera as its template rendering engine. Tera is a powerful templating engine inspired by Jinja2 and Django templates.
Overview
Templates live in your profile's templates/ directory and support dynamic expression substitution ({{ variable }}), control flow constructs ({% if %}, {% for %}), filters, and formatting logic.
You can interact with templates using the Lua API:
wefter.template.create(destination, template_path, params): Renders a template and creates a new file atdestination.wefter.template.embed(destination, insertion_point, template_path, params): Renders a template and inserts its content into an existing file right before matching@wefter.embedor@wefter.embed:<named>insertion point comments.wefter.template.get(template_path, params): Renders a template and returns the resulting content as a string.
You can also use inline variant (create_inline, embed_inline) to use template strings instead of template paths.
For full details on template syntax, variables, filters, and control structures, check the official Tera Documentation.
Insertion Points 🪡
Insertion points allow Wefter to inject dynamically rendered code directly into existing files at specific locations marked by special comments.
Comment Syntax
Place an insertion point comment anywhere in your target file using your language's standard comment syntax (//, /* ... */, #, --, <!-- ... -->):
- Default Insertion Point:
@wefter.embed(matches whenipointisnil) - Named Insertion Point:
@wefter.embed:<name>(e.g.,@wefter.embed:includes,@wefter.embed:routes)
// main.c
/* @wefter.embed:includes */
int
Embedding via Lua API
Use wefter.template.embed or wefter.template.embed_inline to insert content at an insertion point:
wefter.template.embed(destination, ipoint, template_path, params): Renders a template file and inserts it.wefter.template.embed_inline(destination, ipoint, template_str, params): Renders an inline template string and inserts it.
Parameters:
destination(string): Target file path relative to project root.ipoint(string | nil): Target insertion point name (e.g.,"includes"matches@wefter.embed:includes). Passnilto target generic@wefter.embedcomments.template/template_str(string): Template file path insidetemplates/or raw template string.params(table): Parameters passed to the Tera rendering engine.
Example
In your Lua profile script (init.lua):
-- Inject an include statement at `@wefter.embed:includes`
wefter..
-- Inject initialization code at `@wefter.embed:init`
wefter..
Resulting main.c:
/* @wefter.embed:includes */
int
Key Behaviors
- Automatic Indentation Matching: Injected code automatically matches the indentation (leading spaces or tabs) of the insertion point comment line.
- Marker Preservation: The insertion point comment remains in place after injection, allowing subsequent commands to insert additional code at the same marker.
- Multiple Markers: If multiple matching insertion point comments exist in the target file, content is injected before each occurrence.
Lua API ⚒️
Wefter exposes a global wefter namespace in the Lua runtime along with environment constants:
Global Constants
WEFTER_VERSION: Current Wefter version string.WEFTER_PROJECT_ROOT: Absolute path to the root directory of the current project.
API Modules
wefter.fs: Filesystem utilities (is_dir,is_file,read_to_string,read_dir). Available during early-loading (auto.lua).wefter.io: TUI and user interaction tools (input,select,markdown).wefter.template: Template rendering and code generation utilities (create,embed,get).wefter.txt: String casing transformation helpers (to_snake_case,to_camel_case,to_pascal_case,to_upper_camel_case,to_kebab_case).
For full type definitions, function signatures, and docstrings, see the definition file static/lua/wefter.d.lua (or generate it locally via wefter --meta > .wefter.d.lua).
Lua LSP 🧰
Need help navigating the API? wefter can create a Lua Definition File, this file will be used by your LSP to give you proper diagnostics and completion.
Create a definition file using the following command:
wefter --meta > .wefter.d.lua
Then, setup the LSP to use it.
LuaLS example, .luarc.json:
Acknowledgements ❤️
Wefter is built on top of amazing open-source libraries in the Rust ecosystem:
- mlua - Embedded Lua runtime bindings for Rust.
- tera - Powerful Jinja2/Django-inspired templating engine.
- clap - Command line argument parsing and help formatting.
- inquire - Interactive terminal prompts for user inputs and selections.
- termimad - In-terminal Markdown rendering engine.
- directories - Cross-platform path resolution for system configuration and data folders.
- convert_case - String case conversion helpers (wefter.txt API).
AI Notice 🤖
The core codebase of Wefter is mostly human-written (90%+). AI assistance was brealy used and was mainly focused on documentation and minor boilerplate