Skip to content

Feather CLI

The Feather CLI lets you run and debug LÖVE games without modifying your game code. Running a game through the CLI injects Feather automatically at the process level — no require("feather.auto") needed.

Note

The npm package is scoped as @kyonru/feather, but the executable command is feather. npm creates that command from the package bin field.

feather run
feather run path/to/my-game

Important

feather run is best for local desktop development. For mobile, handhelds, Steam Deck, or a second computer where the CLI is not launching the game process, use feather init --mode auto so the game carries the embedded Feather library.


Installation

npm install -g @kyonru/feather

Requires Node.js 18+ and LÖVE installed on your system.

For local development inside this repository:

npm install
npm run cli:build
npm run feather -- --help

npm install links the workspace package into node_modules/@kyonru/feather and exposes node_modules/.bin/feather for local CLI testing.


How injection works

feather run creates a temporary shim directory and passes it to love2d as the game source:

/tmp/feather-{uuid}/
  conf.lua     ← delegates to your game's conf.lua (window title, modules, etc.)
  main.lua     ← loads feather.auto, then runs your game's main.lua
  feather/     ← symlink to the bundled feather library
  plugins/     ← symlink to the bundled plugins

Your game's directory is:

  1. Added to Lua's package.path so all require() calls resolve correctly.
  2. Mounted into love.filesystem so assets (images, audio, data files) are accessible.
  3. Loaded via loadfile() to avoid a conflict with the shim's own main.lua.

Your game code runs exactly as normal — it just has Feather already active.


Commands

feather run [game-path]

Inject Feather into a Love2D game and run it.

feather run                                # interactive run workflow
feather run .                              # run game in current directory
feather run path/to/my-game               # run from an explicit path
feather run . --session-name "RPG"        # custom name in the desktop session tab
feather run . --no-plugins                # feather core only, no plugins
feather run . --love /usr/bin/love        # override love2d binary
feather run . --plugins-dir ./my-plugins  # use a custom plugins directory
feather run . -- --level dev              # pass args through to the game

When game-path is omitted in an interactive terminal, Feather opens an Ink workflow that asks for the game path, session name, config path, whether plugins should be disabled, and optional advanced paths/arguments. Scripts should pass game-path explicitly.

Options:

Option Description
--love <path> Path to the love2d binary. Defaults to auto-detect (see Binary detection).
--session-name <name> Custom session name shown in the Feather desktop app.
--no-plugins Load feather core only — no plugins registered.
--config <path> Explicit path to a feather.config.lua file.
--feather-path <path> Use a local feather install instead of the CLI's bundled copy.
--plugins-dir <path> Use a custom plugins directory instead of the CLI's bundled plugins.

Use -- to separate Feather CLI options from arguments intended for the LÖVE game. Everything after -- is passed to love after the generated shim path.

Project config file:

If a feather.config.lua exists in the game directory, it is read automatically and merged into the feather setup. See feather.config.lua.


feather init [dir]

Initialize Feather in a Love2D project. By default this copies the Lua runtime and plugins that ship with the CLI, so init works offline and stays version-matched with the CLI.

feather init                         # initialize in current directory
feather init path/to/my-game        # initialize in a specific directory
feather init --no-plugins           # install core only, skip plugins
feather init --plugins screenshots,profiler  # install specific plugins only
feather init --mode cli             # create config only; use feather run
feather init --mode auto            # install and patch main.lua with guarded feather.auto
feather init --mode manual          # create feather.debugger.lua and a guarded loader
feather init --remote --branch v0.7.0  # download a specific release from GitHub
feather init --local-src ../feather/src-lua  # copy from a local source tree
feather init --install-dir lib/feather  # install like FEATHER_DIR=lib/feather

Important

Use --mode auto for device builds. It embeds Feather into the project and patches main.lua with a USE_DEBUGGER-guarded feather.auto loader, which works when the game runs on Android, iOS, Steam Deck, or another remote machine.

What it does:

By default, feather init opens an interactive terminal picker powered by Ink:

Mode Behavior
cli Creates feather.config.lua only. Run with feather run ..
auto Copies core/plugins and patches main.lua with a USE_DEBUGGER-guarded require("feather.auto").
manual Copies core/plugins, creates feather.debugger.lua, and loads it from main.lua when USE_DEBUGGER is set.

Install source priority:

  1. --local-src <path> copies from a local src-lua style tree.
  2. Running the CLI from the Feather monorepo copies the repo's src-lua.
  3. Published CLI installs copy the bundled cli/lua runtime.
  4. --remote downloads from GitHub using --branch.

Auto and manual mode use the same project layout as scripts/install-feather.sh:

my-game/
  feather/init.lua
  feather/plugins/screenshots/init.lua
  feather/plugins/hump/signal/init.lua
  main.lua

If you choose lib/feather as the install directory, the Lua module becomes lib.feather, so auto mode patches main.lua with a guarded loader:

local featherUseDebugger = os.getenv("USE_DEBUGGER")
if featherUseDebugger and featherUseDebugger ~= "0" and featherUseDebugger:lower() ~= "false" then
  require("lib.feather.auto")
end

Note

Run local/dev builds with USE_DEBUGGER=1 to load Feather. Leave it unset, 0, or false to skip all Feather imports.

# macOS / Linux
USE_DEBUGGER=1 love .
# Windows PowerShell
$env:USE_DEBUGGER = "1"
love .
:: Windows cmd.exe
set USE_DEBUGGER=1 && love .

The interactive flow asks for:

  • session name
  • install directory, matching FEATHER_DIR
  • install source: bundled/local copy or GitHub download
  • Git branch or tag when using GitHub download, matching FEATHER_BRANCH
  • whether to install built-in plugins, matching FEATHER_PLUGINS
  • optional plugins to force-enable, such as Console, Physics Debug, and Timer Inspector
  • plugins to skip/exclude, matching FEATHER_SKIP_PLUGINS; Console, HUMP Signal, and Lua State Machine start preselected like the shell installer defaults
  • advanced connection/runtime options from feather.config.lua, including host/port, socket vs disk mode, observers, logging, debugger, asset previews, capabilities, and binary threshold
  • a strong API key when Console is included

If the terminal is non-interactive, or --yes is used, Feather defaults to auto.

All modes create a feather.config.lua template if one doesn't exist.

Manual mode writes the custom integration into feather.debugger.lua, then adds a small marked loader block near the top of main.lua. Both are guarded by USE_DEBUGGER. For example, when installing into lib/feather with screenshots and runtime-snapshot, the generated file looks like:

-- feather.debugger.lua
local featherUseDebugger = os.getenv("USE_DEBUGGER")
if not (featherUseDebugger and featherUseDebugger ~= "0" and featherUseDebugger:lower() ~= "false") then
  return nil
end

if DEBUGGER then
  return DEBUGGER
end

local FeatherDebugger = require("lib.feather")
local FeatherPluginManager = require("lib.feather.plugin_manager")
local ScreenshotsPlugin = require("lib.feather.plugins.screenshots")
local RuntimeSnapshotPlugin = require("lib.feather.plugins.runtime-snapshot")

DEBUGGER = FeatherDebugger({
  debug = true,
  sessionName = "My Game",
  plugins = {
    FeatherPluginManager.createPlugin(ScreenshotsPlugin, "screenshots", {}),
    FeatherPluginManager.createPlugin(RuntimeSnapshotPlugin, "runtime-snapshot", {}),
  },
})

return DEBUGGER

Tip

main.lua gets matching FEATHER-INIT comments around the loader and update hook. feather.config.lua also includes a managed metadata block so feather remove can remove generated files and markers before production packaging.

Options:

Option Description
--remote Download from GitHub instead of copying the local/bundled Lua runtime.
--branch <branch> GitHub branch or tag to download from when using --remote (default: main).
--local-src <path> Copy from a local src-lua style directory.
--install-dir <path> Install directory for auto/manual modes (default: feather).
--no-plugins Skip plugin installation.
--plugins <ids> Comma-separated list of plugin IDs to install (default: all).
--mode <mode> Setup mode: cli, auto, or manual.
-y, --yes Skip confirmation prompts.

feather remove [dir]

Remove Feather from a project before creating a production build.

feather remove                         # interactive picker
feather remove path/to/my-game
feather remove --yes                   # remove default managed targets
feather remove --dry-run               # preview without changing files
feather remove --keep-config           # keep feather.config.lua
feather remove --keep-runtime          # keep feather/ and feather/plugins/
feather remove --install-dir lib/feather

Caution

feather remove only edits main.lua inside the generated FEATHER-INIT marker blocks. It can also remove the installed runtime directory, feather.config.lua, and feather.debugger.lua when those files are detected.

The command reads managed metadata from feather.config.lua when available, including the install directory and manual entrypoint path.

Options:

Option Description
--install-dir <path> Override the detected Feather install directory.
--dry-run Show what would be removed without changing files.
--keep-config Keep feather.config.lua.
--keep-main Keep main.lua marker blocks and update hook.
--keep-manual Keep feather.debugger.lua.
--keep-runtime Keep installed Feather runtime and plugins.
-y, --yes Skip the interactive picker and remove default managed targets.

feather doctor [dir]

Check the environment and project health.

feather doctor        # check current directory
feather doctor path/to/my-game
feather doctor . --install-dir lib/feather
feather doctor . --host 127.0.0.1 --port 4004
feather doctor . --json

Doctor checks:

  • Node.js, npm, and LÖVE availability
  • main.lua, feather.config.lua, and managed init metadata
  • embedded runtime files for auto/manual setups
  • installed plugin manifests
  • USE_DEBUGGER guards and FEATHER-INIT markers
  • risky settings such as hot reload, screenshot capture, and Console API keys
  • Feather desktop WebSocket reachability

Tip

feather doctor --json is useful in CI or pre-release scripts. It exits with a nonzero status only when it finds blockers.

Example output:

Feather doctor

Project: /path/to/my-game

Environment
  ✔ Node.js  v22.0.0
  ✔ npm  v10.8.1
  ✔ LÖVE binary  /Applications/love.app/Contents/MacOS/love  (11.5)

Safety
  ! Hot reload  enabled
    → Hot reload is development-only remote code execution; keep allowlists narrow and never ship with it on.

Doctor passed with 1 warning.

feather update [dir]

Update the Feather core library in a project.

feather update                       # interactive source picker in a terminal
feather update -y                    # update from the local/bundled CLI copy
feather update path/to/my-game
feather update --remote --branch v0.7.1
feather update --local-src ../feather/src-lua

In an interactive terminal, feather update opens an Ink workflow to choose local/bundled files or a GitHub branch/tag. In scripts or with -y, it uses the local/bundled CLI copy unless --remote is provided.

This updates all core: files listed in manifest.txt. Plugin files are not touched — use feather plugin update for those.


feather plugin

Manage Feather plugins in a project.

Run feather plugin with no subcommand to open an Ink workflow for common plugin tasks:

feather plugin
feather plugin --install-dir lib/feather
feather plugin --remote --branch main

The workflow can list installed plugins, install one or more catalog plugins, remove installed plugins, or update selected plugins. Like feather init, plugin installs and updates are local-first by default:

  1. --local-src <path> copies from a local src-lua style tree.
  2. Running the CLI from the Feather monorepo copies the repo's src-lua.
  3. Published CLI installs copy the bundled cli/lua runtime.
  4. --remote downloads from GitHub using --branch.

feather plugin list [dir]

List installed plugins.

feather plugin list
Installed plugins (12)

  screenshots              1.0.0    Capture screenshots and record GIFs
  profiler                 1.0.0    Function-level CPU profiling
  entity-inspector         1.0.0    ECS entity browser
  ...

feather plugin install <id>

Install a plugin from the local/bundled runtime by default, or from GitHub with --remote.

feather plugin install console
feather plugin install time-travel --remote --branch main
feather plugin install console --local-src ../feather/src-lua
feather plugin install console --install-dir lib/feather

feather plugin remove <id>

Remove an installed plugin.

feather plugin remove hump.signal

feather plugin update [id]

Update a plugin, or all installed plugins if no ID is given.

feather plugin update              # interactive picker for installed plugins
feather plugin update -y           # update all installed plugins
feather plugin update profiler     # update a specific plugin
feather plugin update --remote --branch main

When no plugin ID or source flag is provided in an interactive terminal, feather plugin update opens an Ink workflow where you can choose the source and select installed plugins. Use -y, --remote, or --local-src for CI or scripts.

Use --install-dir <path> with plugin commands when the project was initialized outside the default feather/ directory.


feather.config.lua

Place a feather.config.lua in your game directory to configure the Feather injection without touching command-line flags. feather run reads it automatically.

-- feather.config.lua
return {
  sessionName = "My RPG",

  -- Force-enable opt-in plugins.
  -- "console" is a remote REPL. "hot-reload" is development-only remote code execution.
  include = { "console" },

  -- Remove plugins you don't need
  exclude = { "hump.signal", "lua-state-machine" },

  -- Per-plugin option overrides
  pluginOptions = {
    screenshots = { fps = 60, gifDuration = 10 },
    ["memory-snapshot"] = { autoInterval = 5 },
  },

  -- Connect to a remote desktop app (e.g. on another machine)
  -- host = "192.168.1.42",
}

All feather.auto.setup() options are supported. Command-line flags (--session-name, etc.) take precedence over the config file.


Binary detection

feather run finds the love2d binary in this order:

  1. --love <path> flag
  2. LOVE_BIN environment variable
  3. Platform defaults:
  4. macOS: /Applications/love.app/Contents/MacOS/love
  5. Windows: %PROGRAMFILES%\LOVE\love.exe, %LOCALAPPDATA%\LOVE\love.exe
  6. Linux: love or love2d from PATH

Examples

Run any game with zero setup

# Clone any love2d game and run it with Feather
git clone https://github.com/some/game.git
feather run game/

The Feather desktop app will show a new session as soon as the game connects.

Use the bundled feather vs a local install

By default, feather run uses the feather library and plugins bundled inside the CLI package. If your project has feather installed locally (via feather init), the CLI prefers that:

my-game/
  feather/init.lua   ← detected → local install is used
  feather/plugins/
  main.lua

To point at a different feather build or plugins directory:

feather run . --feather-path ../feather-dev
feather run . --plugins-dir ../my-custom-plugins

--plugins-dir takes precedence over the bundled plugins and any game-local plugins/ directory.

CI / headless mode

feather run exits with love2d's exit code, making it suitable for CI workflows:

- name: Run game smoke test
  run: feather run . --no-plugins
  env:
    LOVE_BIN: /usr/bin/love

Alias for fast iteration

Add a shell alias so fr runs from any game directory:

alias fr='feather run .'

Comparison with manual setup

feather run Manual (require("feather.auto"))
Game code changes None Add require + update call
Works on any game Yes Only games you've modified
feather.config.lua Supported Supported
Plugin management Via CLI Manual download

Both approaches are compatible — a game that already has require("feather.auto") can still be launched with feather run (Feather checks the DEBUGGER global and skips double-initialization).