Quick Start
- MorphClient
- Anthropic
- OpenAI
- Vercel AI SDK
- Standalone Client
Copy
Ask AI
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
// Simple - defaults to LocalRipgrepProvider
const result = await morph.warpGrep.execute({
query: 'Find authentication middleware',
repoRoot: '.'
});
if (result.success) {
for (const ctx of result.contexts) {
console.log(`File: ${ctx.file}`);
console.log(ctx.content);
}
}
Response Types
Both direct usage and tool usage return the same response structure:- Success Response
- Error Response
Copy
Ask AI
interface WarpGrepResult {
success: true;
contexts: Array<{
file: string; // File path relative to repo root
content: string; // Content of the relevant code section
}>;
summary: string; // Summary of what was found
}
All types are exported from the SDK root:
Copy
Ask AI
import type {
// Result types
WarpGrepResult,
WarpGrepContext,
WarpGrepInput,
// Config types
WarpGrepClientConfig,
WarpGrepToolConfig,
// Provider types for custom implementations
WarpGrepProvider,
GrepResult,
ReadResult,
AnalyseEntry,
// Agent types (advanced usage)
AgentRunResult,
SessionConfig,
ChatMessage,
} from '@morphllm/morphsdk';
Handling Results
Copy
Ask AI
const result = await morph.warpGrep.execute({
query: 'Find authentication middleware',
repoRoot: '.'
});
if (result.success) {
console.log(`Found ${result.contexts.length} relevant code sections`);
for (const ctx of result.contexts) {
console.log(`\n--- ${ctx.file} ---`);
console.log(ctx.content);
}
console.log(`\nSummary: ${result.summary}`);
} else {
console.error(`Search failed: ${result.error}`);
}
Customize Tool Name and Description
Override the default tool name and description to tailor it for your use case:- MorphClient
- Standalone
Copy
Ask AI
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const grepTool = morph.openai.createWarpGrepTool({
repoRoot: '.',
name: 'my_custom_grep', // override the default tool name
description: 'Use this tool when you know what you are looking for in the codebase.' // override the default tool description
});
| Option | Default | Description |
|---|---|---|
name | warpgrep_codebase_search | The tool name exposed to the LLM |
description | (see SDK) | The tool description explaining when/how to use it |
Remote Sandboxes
How Warp Grep Works
Warp Grep runs an agent loop: it sends a query to a specialized search model, the model outputs tool calls (grep, read, analyse), those tools execute on your codebase, and the results go back to the model. This repeats until the model finds the relevant code.
For local development, this just works — the tools run on your machine. But when your code lives in a remote sandbox (E2B, Modal, Daytona), you need to tell the SDK how to run those tools remotely.
The Solution: Implement WarpGrepProvider
You provide three functions that execute in your sandbox:
Copy
Ask AI
import type { WarpGrepProvider, GrepResult, ReadResult, AnalyseEntry } from '@morphllm/morphsdk';
const provider: WarpGrepProvider = {
// Search for regex pattern, return "path:line:content" format
grep: async ({ pattern, path }): Promise<GrepResult> => {
// Run ripgrep in your sandbox
return { lines: ['src/auth.ts:10:function login()'] };
},
// Read file lines, return "lineNumber|content" format
read: async ({ path, start, end }): Promise<ReadResult> => {
// Read from your sandbox filesystem
return { lines: ['1|const x = 1;', '2|const y = 2;'] };
},
// List directory contents
analyse: async ({ path, pattern, maxResults, maxDepth }): Promise<AnalyseEntry[]> => {
// List files in your sandbox
return [{ name: 'src', path: 'src', type: 'dir', depth: 0 }];
}
};
// Pass to warp grep
const result = await runWarpGrep({
query: 'Find authentication middleware',
repoRoot: '/path/in/sandbox',
provider,
apiKey: process.env.MORPH_API_KEY,
});
Full Examples
- E2B
- Modal
- Daytona
Copy
Ask AI
/**
* E2B + Warp Grep Example
*
* Demonstrates using warp-grep with a remote E2B sandbox.
* We implement the WarpGrepProvider interface with three functions
* that execute in the sandbox.
*/
import { Sandbox } from "@e2b/code-interpreter";
import { runWarpGrep } from "@morphllm/morphsdk/tools/warp-grep";
import type { WarpGrepProvider, GrepResult, ReadResult, AnalyseEntry } from "@morphllm/morphsdk/tools/warp-grep";
const MORPH_API_KEY = process.env.MORPH_API_KEY;
const REMOTE_DIR = "/home/user/repo";
const TEST_REPO = "https://github.com/google-gemini/gemini-cli.git";
/**
* Creates a WarpGrepProvider for E2B sandbox.
* Implements grep, read, and analyse functions that run in the sandbox.
*/
function createE2BProvider(sandbox: Sandbox, repoDir: string): WarpGrepProvider {
/**
* grep - Search for a regex pattern in files
* Must return lines in ripgrep format: "path:lineNumber:content"
*/
async function grep(params: { pattern: string; path: string }): Promise<GrepResult> {
const targetPath = params.path === '.' ? repoDir : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
const cmd = `rg --no-config --no-heading --with-filename --line-number --color=never --trim --max-columns=400 -g '!.git' -g '!node_modules' -g '!dist' '${params.pattern}' '${targetPath}'`;
const result = await sandbox.commands.run(cmd, { cwd: repoDir });
// Exit code 1 = no matches (not an error)
if (result.exitCode !== 0 && result.exitCode !== 1) {
return { lines: [], error: result.stderr || `grep failed with exit code ${result.exitCode}` };
}
const lines = (result.stdout || '').trim().split('\n').filter(l => l.length > 0);
return { lines };
}
/**
* read - Read lines from a file
* Must return lines in format: "lineNumber|content"
*/
async function read(params: { path: string; start?: number; end?: number }): Promise<ReadResult> {
const filePath = params.path.startsWith(repoDir) ? params.path : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
const start = params.start ?? 1;
const end = params.end ?? 1_000_000;
const cmd = `sed -n '${start},${end}p' '${filePath}'`;
const result = await sandbox.commands.run(cmd, { cwd: repoDir });
if (result.exitCode !== 0) {
return { lines: [], error: result.stderr || `read failed: ${filePath}` };
}
const lines = (result.stdout || '').split('\n').map((line, idx) => `${start + idx}|${line}`);
if (lines.length > 0 && lines[lines.length - 1] === `${start + lines.length - 1}|`) {
lines.pop();
}
return { lines };
}
/**
* analyse - List directory contents
* Must return entries with: name, path, type ('file' | 'dir'), depth
*/
async function analyse(params: { path: string; pattern?: string | null; maxResults?: number; maxDepth?: number }): Promise<AnalyseEntry[]> {
const targetPath = params.path === '.' ? repoDir : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
const maxDepth = params.maxDepth ?? 2;
const maxResults = params.maxResults ?? 100;
const cmd = `find '${targetPath}' -maxdepth ${maxDepth} -not -path '*/\\.git/*' -not -path '*/node_modules/*' | head -${maxResults}`;
const result = await sandbox.commands.run(cmd, { cwd: repoDir });
if (result.exitCode !== 0) return [];
const entries: AnalyseEntry[] = [];
const paths = (result.stdout || '').trim().split('\n').filter(p => p.length > 0);
for (const fullPath of paths) {
if (fullPath === targetPath) continue;
const name = fullPath.split('/').pop() || '';
const relativePath = fullPath.replace(repoDir + '/', '');
const depth = relativePath.split('/').length - 1;
const statResult = await sandbox.commands.run(`test -d '${fullPath}' && echo dir || echo file`, { cwd: repoDir });
const type = statResult.stdout.trim() === 'dir' ? 'dir' : 'file';
if (params.pattern && !new RegExp(params.pattern).test(name)) continue;
entries.push({ name, path: relativePath, type, depth });
}
return entries.slice(0, maxResults);
}
return { grep, read, analyse };
}
async function main() {
// Create E2B sandbox
const sandbox = await Sandbox.create();
try {
// Clone repo and install ripgrep
await sandbox.commands.run(`git clone --depth 1 ${TEST_REPO} ${REMOTE_DIR}`, { cwd: "/home/user", timeoutMs: 120000 });
await sandbox.commands.run("sudo apt-get update -qq && sudo apt-get install -y -qq ripgrep", { cwd: REMOTE_DIR, timeoutMs: 120000 });
// Create provider and run warp-grep
const provider = createE2BProvider(sandbox, REMOTE_DIR);
const result = await runWarpGrep({
query: "find the code that handles custom mcp integrations",
repoRoot: REMOTE_DIR,
provider,
apiKey: MORPH_API_KEY,
});
// Use the results
if (result.terminationReason === 'completed' && result.finish?.resolved) {
for (const file of result.finish.resolved) {
console.log(`Found: ${file.path}`);
console.log(file.content);
}
}
} finally {
await sandbox.kill();
}
}
main();
Your sandbox needs
ripgrep (rg) installed for the grep function to work.