UNPKG

relaycode

Version:

A developer assistant that automates applying code changes from LLMs.

72 lines (60 loc) 7.2 kB
import {loadConfigOrExit,findConfigPath,findConfig}from'../core/config';import {createClipboardWatcher}from'../core/clipboard';import {parseLLMResponse}from'relaycode-core';import {processPatch}from'../core/transaction';import {logger}from'../utils/logger';import R from'fs';import O from'path';const E=(f,i,a)=>{const r=` \u2705 relaycode is watching for changes. IMPORTANT: For relaycode to work, you must configure your AI assistant. Copy the entire text below and paste it into your LLM's "System Prompt" or "Custom Instructions" section. ---------------------------------------------------------------------------`,n="You are an expert AI programmer. To modify a file, you MUST use a code block with a specified patch strategy.",c='\n**Syntax:**\n```typescript // filePath {patchStrategy}\n... content ...\n```\n- `filePath`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- `patchStrategy`: (Optional) One of `standard-diff`, `search-replace`. If omitted, the entire file is replaced (this is the `replace` strategy).\n\n**Examples:**\n```typescript // src/components/Button.tsx\n...\n```\n```typescript // "src/components/My Component.tsx" standard-diff\n...\n```',d="\n**Syntax:**\n```typescript // filePath\n... content ...\n```\n- `filePath`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- Only the `replace` strategy is enabled. This means you must provide the ENTIRE file content for any change. This is suitable for creating new files or making changes to small files.",l="\n**Syntax:**\n```typescript // filePath standard-diff\n... diff content ...\n```\n- `filePath`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- You must use the `standard-diff` patch strategy for all modifications.",s="\n**Syntax:**\n```typescript // filePath search-replace\n... diff content ...\n```\n- `filePath`: The path to the file. **If the path contains spaces, it MUST be enclosed in double quotes.**\n- You must use the `search-replace` patch strategy for all modifications.",p=`--- ### Strategy 1: Advanced Unified Diff (\`standard-diff\`) - RECOMMENDED Use for most changes, like refactoring, adding features, and fixing bugs. It's resilient to minor changes in the source file. **Diff Format:** 1. **File Headers**: Start with \`--- {filePath}\` and \`+++ {filePath}\`. 2. **Hunk Header**: Use \`@@ ... @@\`. Exact line numbers are not needed. 3. **Context Lines**: Include 2-3 unchanged lines before and after your change for context. 4. **Changes**: Mark additions with \`+\` and removals with \`-\`. Maintain indentation. **Example:** \`\`\`diff --- src/utils.ts +++ src/utils.ts @@ ... @@ function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => { - return sum + item; - }, 0); + const total = items.reduce((sum, item) => { + return sum + item * 1.1; // Add 10% markup + }, 0); + return Math.round(total * 100) / 100; // Round to 2 decimal places + } \`\`\` `,e=`--- ### Strategy 2: Search-Replace (\`search-replace\`) Use for precise, surgical replacements. The \`SEARCH\` block must be an exact match of the content in the file. **Diff Format:** Repeat this block for each replacement. \`\`\`diff <<<<<<< SEARCH [exact content to find including whitespace] ======= [new content to replace with] >>>>>>> REPLACE \`\`\` `,h='---\n\n### Other Operations\n\n- **Creating a file**: Use the default `replace` strategy (omit the strategy name) and provide the full file content.\n- **Deleting a file**:\n ```typescript // path/to/file.ts\n //TODO: delete this file\n ```\n ```typescript // "path/to/My Old Component.ts"\n //TODO: delete this file\n ```\n- **Renaming/Moving a file**:\n ```json // rename-file\n {\n "from": "src/old/path/to/file.ts",\n "to": "src/new/path/to/file.ts"\n }\n ```\n',o=[];a.minFileChanges>0&&o.push(`You must modify at least ${a.minFileChanges} file(s) in this transaction.`),a.maxFileChanges&&o.push(`You must not modify more than ${a.maxFileChanges} file(s) in this transaction.`);const u=["Add your step-by-step reasoning in plain text before each code block."];o.length>0&&u.push(`Adhere to file limits: ${o.join(" ")}`),u.push("ALWAYS add the following YAML block at the very end of your response. Use the exact projectId shown here. Generate a new random uuid for each response.");const y=`--- ### Final Steps ${u.map((x,w)=>`${w+1}. ${x}`).join(` `)} \`\`\`yaml projectId: ${f} uuid: (generate a random uuid) changeSummary: # A list of key-value pairs for changes - edit: src/main.ts - new: src/components/Button.tsx - delete: src/utils/old-helper.ts promptSummary: A brief summary of my request. gitCommitMsg: >- feat: A concise, imperative git commit message. Optionally, provide a longer description here. \`\`\` `,S="---------------------------------------------------------------------------",m={auto:{syntax:c,details:`${p} ${e}`},replace:{syntax:d,details:""},"standard-diff":{syntax:l,details:p},"search-replace":{syntax:s,details:e}},g=m[i]??m.auto,b=g.syntax,C=g.details;return [r,n,b,C,h,y,S].filter(Boolean).join(` `)},q=async(f={},i=process.cwd())=>{let a=null,r=null,n=null;const c=e=>{a&&a.stop(),logger.setLevel(e.core.logLevel),logger.debug(`Log level set to: ${e.core.logLevel}`),logger.debug(`Preferred strategy set to: ${e.watcher.preferredStrategy}`),logger.log(E(e.projectId,e.watcher.preferredStrategy,e.patch)),a=createClipboardWatcher(e.watcher.clipboardPollInterval,async h=>{logger.info("New clipboard content detected. Attempting to parse...");const o=parseLLMResponse(h);if(!o){logger.warn("Clipboard content is not a valid relaycode patch. Ignoring.");return}if(o.control.projectId!==e.projectId){logger.debug(`Ignoring patch for different project (expected '${e.projectId}', got '${o.control.projectId}').`);return}await processPatch(e,o,{cwd:i,notifyOnStart:true,yes:f.yes}),logger.info("--------------------------------------------------"),logger.info("Watching for next patch...");});},d=()=>{n&&clearTimeout(n),n=setTimeout(async()=>{logger.info("Configuration file change detected. Reloading...");try{const e=await findConfig(i);e?(logger.success("Configuration reloaded. Restarting services..."),c(e)):(logger.error("Configuration file is invalid or has been deleted. Services paused."),a&&(a.stop(),a=null));}catch(e){logger.error(`Error reloading configuration: ${e instanceof Error?e.message:String(e)}`);}},250);},l=await loadConfigOrExit(i),s=await findConfigPath(i);return logger.success("Configuration loaded. Starting relaycode watch..."),c(l),l.core.watchConfig&&s?(logger.info(`Configuration file watching is enabled for ${O.basename(s)}.`),r=R.watch(s,d)):logger.info("Configuration file watching is disabled. Changes to config will require a restart to take effect."),{stop:()=>{a&&a.stop(),r&&(r.close(),logger.info("Configuration file watcher stopped.")),n&&clearTimeout(n);}}};export{q as watchCommand};//# sourceMappingURL=watch.js.map //# sourceMappingURL=watch.js.map