Relaycode is a powerful developer assistant that bridges the gap between Large Language Models (LLMs) and your local development environment. It listens for code modification instructions copied to your clipboard, parses them, and applies them to your codebase in a safe, transactional, and interactive way.
Think of it as an automated pair programmer. You ask your LLM to perform a task (e.g., "refactor this component," "add a new feature," "fix this bug"), copy its response, and relaycode
handles the file operations, linting, and validation, giving you the final say before committing the changes.
(Note: This is a conceptual animation)
- The Problem Relaycode Solves
- Core Concepts
- Getting Started
- Configuring Your LLM
- Command-Line Interface (CLI)
- Configuration File (
relaycode.config.json
) - How It Works: The Transaction Lifecycle
- Best Practices & Advanced Usage
- Troubleshooting
- Contributing
- License
LLMs are incredibly proficient at generating code, but integrating that code into an existing project is often a tedious and error-prone process. Developers are stuck in a loop of:
- Pasting code from the LLM into their editor.
- Manually creating new files.
- Finding the correct location to insert or replace code.
- Deleting old files.
- Running linters and formatters to fix style issues.
- Running tests to ensure nothing broke.
This manual process is slow and distracting. Relaycode automates this entire workflow, allowing you to stay focused on the high-level task while your AI assistant handles the "grunt work" of file manipulation.
Relaycode is built around a few simple but powerful ideas:
- Clipboard-Driven Workflow: The clipboard is the universal interface. Any text you copy is a potential command for Relaycode.
- Structured Format: Relaycode expects the LLM's response to be in a specific format containing markdown code blocks and a final YAML block. This structure allows for unambiguous parsing of file operations.
- Transactional Integrity: Every set of changes is treated as a single transaction. If any part of the process fails (e.g., a linter error, a failed command, or manual rejection), the entire set of changes is rolled back, leaving your codebase in its original state. Nothing is committed until it's approved.
- Safety First: Relaycode creates a snapshot of all affected files before touching them. This guarantees a perfect rollback if anything goes wrong.
- Interactive Approval: You are always in control. Relaycode can be configured to automatically apply changes that pass all checks, or it can prompt you for final approval before committing the transaction.
- Node.js & Bun: Relaycode is a Bun-based project. You'll need Bun installed on your system.
- LLM Assistant: Access to an LLM that supports custom instructions or system prompts (e.g., ChatGPT with GPT-4, Claude, etc.).
Install Relaycode globally using Bun:
bun install -g relaycode
Navigate to the root directory of your project and run:
relay init
This command performs several crucial actions:
-
Identifies Project ID: It determines a unique
projectId
for your project (usually frompackage.json
'sname
field or the directory name). -
Creates
relaycode.config.json
: It generates a configuration file with sensible defaults. -
Creates
.relaycode
Directory: This hidden directory is used to store state files for all transactions. -
Updates
.gitignore
: It ensures the.relaycode
directory is ignored by Git. - Displays the System Prompt: Most importantly, it prints a detailed system prompt that you must copy and paste into your LLM's custom instructions.
Once initialized, start the Relaycode watcher in your terminal:
relay watch
Relaycode will now monitor your clipboard. When it detects content that matches its expected format, it will spring into action.
For Relaycode to work, your LLM must be instructed to format its responses correctly. The relay init
command provides the necessary text. Paste this into the "System Prompt," "Custom Instructions," or equivalent configuration area of your AI assistant.
The system prompt teaches the LLM its new role as an AI programmer that interacts with your local file system via a structured text format. It defines the syntax for file paths, patch strategies, and the required final YAML block.
The core of the format is the markdown code block, which is overloaded with a file path and an optional patch strategy.
Syntax:
... file content or diff ...
-
filePath
: The relative path to the file from your project root. If the path contains spaces, it MUST be enclosed in double quotes. -
patchStrategy
: (Optional) Defines how the content should be applied. Can bereplace
,new-unified
, ormulti-search-replace
. If omitted, it defaults toreplace
.
Choosing the right patch strategy is key to getting the best results from your LLM.
This is the simplest strategy. It completely replaces the entire content of the specified file with the content inside the code block.
-
When to use it:
- Creating a new file.
- Completely overwriting an existing file when the changes are extensive.
-
Example (Creating a new file):
import React from 'react'; const Welcome = () => { return <h1>Hello, Relaycode!</h1>; }; export default Welcome;
This strategy uses an advanced fuzzy diffing algorithm to apply changes. It's highly resilient to minor variations between the LLM's context and your actual file content (e.g., small bug fixes you made since you last copied the code).
The format is a standard unified diff.
-
When to use it:
- Refactoring code.
- Adding or removing functions, properties, or logic.
- Fixing bugs.
- Most day-to-day editing tasks.
-
Diff Format:
- Lines starting with
+
are added. - Lines starting with
-
are removed. - Lines starting with a space are for context and are essential for finding the correct location to apply the patch.
- Lines starting with
-
Example (Adding a feature):
--- src/utils.ts +++ src/utils.ts @@ ... @@ function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => sum + item, 0); + // Add 10% tax + const total = items.reduce((sum, item) => sum + item, 0); + return total * 1.1; + }
This strategy performs one or more exact search-and-replace operations within a file. It is powerful for surgical changes across multiple locations but requires the SEARCH
block to be a perfect, character-for-character match of the text in the file.
-
When to use it:
- Renaming a variable in a few specific places.
- Updating import paths.
- When you need absolute precision and the
new-unified
diff is too broad.
-
Diff Format:
Repeat this block for each replacement.
<<<<<<< SEARCH // The exact string to find ======= // The new string to replace it with >>>>>>> REPLACE
-
Example (Renaming a function call):
<<<<<<< SEARCH const result = oldCalculateFunction(data); ======= const result = newCalculateFunction(data); >>>>>>> REPLACE
-
Creating a File: Use the default
replace
strategy. Just provide the file path and the full content. -
Deleting a File: Use a special marker inside the code block.
//TODO: delete this file
Every response from the LLM must end with a YAML block. This block acts as a control record for the transaction.
projectId: your-project-name
uuid: (generate a random uuid)
changeSummary:
- edit: src/main.ts
- new: src/components/Button.tsx
- delete: src/utils/old-helper.ts
-
projectId
: Must match theprojectId
in yourrelaycode.config.json
. This prevents accidentally applying a patch meant for another project. -
uuid
: A unique identifier for this transaction. Relaycode uses this to prevent re-processing the same patch. The LLM should generate a new UUID for every response. -
changeSummary
: A human-readable summary of the changes. (Currently for informational purposes).
Initializes Relaycode in the current project directory. See the Initialization section for details.
Starts the clipboard watcher. It runs continuously, waiting for a valid Relaycode patch to appear on the clipboard. It takes no arguments and uses the relaycode.config.json
file in the current directory.
This file, created by relay init
, controls the behavior of the watch
command.
{
"projectId": "my-cool-app",
"clipboardPollInterval": 2000,
"approval": "yes",
"approvalOnErrorCount": 0,
"linter": "bun tsc --noEmit",
"preCommand": "",
"postCommand": "bunx prettier --write ."
}
-
projectId
(string): A unique identifier for your project. -
clipboardPollInterval
(number): The frequency in milliseconds at which to check the clipboard for new content. Default:2000
. -
approval
(enum:"yes"
|"no"
):-
"yes"
: Relaycode will try to automatically approve transactions if thelinter
command passes (or returns an error count within theapprovalOnErrorCount
threshold). -
"no"
: Relaycode will always ask for manual confirmation before committing changes, regardless of the linter result.
-
-
approvalOnErrorCount
(number): Ifapproval
is"yes"
, Relaycode will still auto-approve if the number of errors reported by thelinter
is less than or equal to this value. Default:0
. -
linter
(string): The shell command to run to check for errors after applying the patch. Relaycode checks its exit code and tries to parse an error count from its output. An empty string (""
) disables linting. -
preCommand
(string): A shell command to run before the transaction begins (e.g.,bun install
if the LLM might add a new dependency). If this command fails, the transaction is aborted. -
postCommand
(string): A shell command to run after the patch is applied but before the linter runs (e.g., a code formatter likebunx prettier --write .
). If this command fails, the transaction is rolled back.
When you copy a valid patch, Relaycode executes the following sequence:
-
Detect & Parse: The
watch
command detects new clipboard content and parses it, extracting the file operations and the control YAML. -
Validate: It checks if the
projectId
matches and if theuuid
has been processed before. -
Run
preCommand
: Executes the pre-command, if defined. - Create Snapshot: It reads the current content of all affected files and stores them in memory. This is the rollback point.
-
Write Pending State: A
{uuid}.pending.yml
file is written to the.relaycode
directory, recording the intended operations and the snapshot. - Apply Operations: The file modifications (write, delete, patch) are executed on your file system.
-
Run
postCommand
: Executes the post-command, if defined. - Run Linter: Executes the linter command, if defined, and counts any errors.
-
Request Approval: Based on the
approval
config and linter results, it either auto-approves or prompts for manual approval (y/N
). -
Commit or Rollback:
-
On Approval: The
.pending.yml
file is renamed to{uuid}.yml
, atomically committing the transaction. A success notification is sent. -
On Rejection or Error: The in-memory snapshot is used to restore all files to their original state. The
.pending.yml
file is deleted. A failure notification is sent.
-
On Approval: The
-
Be Specific in Prompts: The more context you give your LLM, the better the patch will be. Instead of "fix the button," say "In
src/components/Button.tsx
, theonClick
handler is not working. Please fix it and add adata-testid
attribute for testing. Use thenew-unified
patch strategy." -
Use
preCommand
for Dependencies: If you ask an LLM to add a new library, you can set"preCommand": "bun install"
to ensure the dependency is available before the code that uses it is written. -
Use
postCommand
for Formatting: Keep your code clean by using a formatter in the post-command, like"postCommand": "bunx prettier --write ."
. This ensures the LLM's output always matches your project's code style. - Start Small: Begin with small, targeted changes. As you get more comfortable, you can give the LLM larger, more complex tasks involving multiple files.
-
Patch Not Detected:
- Ensure the
relay watch
command is running. - Check that the YAML block is present at the very end of the copied text and is formatted correctly.
- Verify the
projectId
in the YAML matches your config file.
- Ensure the
-
Changes Applied Incorrectly:
- The patch strategy may have been inappropriate. For edits,
new-unified
is usually best. If it fails,replace
with the full file content is a reliable fallback. - The context provided to the LLM might have been out of date. Try re-copying the latest version of the file into your prompt.
- The patch strategy may have been inappropriate. For edits,
-
Rollback Failed: This is rare but could happen if file permissions change during a transaction. The original content is still available in the
.pending.yml
file in the.relaycode
directory for manual recovery.
We welcome contributions! Please feel free to open an issue or submit a pull request.
- Clone the repository.
- Install dependencies:
bun install
- Run the development server:
bun run dev
- Run tests:
bun test
This project is licensed under the MIT License.
npm install -g .