Skip to main content

Contributing

Genie is built with Bun and TypeScript. This guide covers setting up a development environment, running quality gates, and building plugins. We build in public because that’s the only honest way to build developer tools.

Development Setup

Prerequisites

Clone and Install

Build

Genie bundles to a single file for distribution:
This produces dist/genie.js (~305KB minified) with all dependencies inlined. The shebang #!/usr/bin/env bun makes it directly executable. No runtime dependencies need to be co-located.

Run from Source

During development, run directly from source:
Or link globally for the genie command to point at your source:

Code Style

Genie uses Biome for formatting and linting.

No console.log

console.log is banned in source files via a Biome rule. Use structured logging or the CLI output functions instead. The rule is relaxed in test files.

Quality Gates

Run the full quality gate with:
This runs four checks in sequence:
bun run dead-code (knip) has pre-existing false positives for biome, commitlint, and husky devDeps. These are not regressions.

Running Individual Gates

Testing

Framework

Tests use bun:test (import from 'bun:test').

Conventions

Example Test

Project Structure

Plugin Development

Genie supports plugins that extend the CLI with additional commands, hooks, and skills.

Plugin Structure

Plugin Registration

Plugins are registered in openclaw.plugin.json at the repo root:

Adding Skills

Skills are markdown files that define agent behavior. To add a new skill:
  1. Create skills/<skill-name>/SKILL.md
  2. Add frontmatter with name, description, and optional triggers
  3. Write the skill prompt in the body
Skills are loaded dynamically — no registration needed beyond creating the file.

Commit Conventions

Genie uses conventional commits enforced by commitlint:

Branch Naming

PR Workflow

  1. Branch from dev
  2. Make changes, commit with conventional messages
  3. Push and create PR targeting dev
  4. Human reviews and merges devmain when ready

Known Gotchas

  • File lock timeout force-removes are intentional — prevents deadlocks from crashed processes. The open('wx') after unlink is atomic, so only one process wins.
  • Hook dispatch has a 15-second hard timeout — handlers that exceed this silently timeout.
  • System prompt injection can fail silently — if the prompt file write fails, Claude Code dies on startup trying to read it.
  • Mailbox delivery is best-effort — dead pane = message stays deliveredAt: null forever.

Community

Genie is open source and we want your help — code, docs, bug reports, wild ideas, even polite disagreements about our architecture choices. Especially those, actually.

Discord

Chat with contributors, ask questions, share what you’re building.

GitHub

Issues, PRs, and the code itself. Star if you’re feeling generous.