1import { readFile } from "node:fs/promises";
2import { expandHomePath } from "../../util/path.js";
3import { applyConfigOverrides, loadConfig } from "../../config/loader.js";
4import { createWorkspace } from "../../workspace/manager.js";
5import { createGrepTool } from "../../agent/tools/grep.js";
6import { createReadTool } from "../../agent/tools/read.js";
7import { createLsTool } from "../../agent/tools/ls.js";
8import { createFindTool } from "../../agent/tools/find.js";
9import { createGitLogTool } from "../../agent/tools/git/log.js";
10import { createGitShowTool } from "../../agent/tools/git/show.js";
11import { createGitBlameTool } from "../../agent/tools/git/blame.js";
12import { createGitDiffTool } from "../../agent/tools/git/diff.js";
13import { createGitRefsTool } from "../../agent/tools/git/refs.js";
14import { createGitCheckoutTool } from "../../agent/tools/git/checkout.js";
15import { runAgent } from "../../agent/runner.js";
16import { buildRepoPrompt } from "../../agent/prompts/repo.js";
17import { createEventLogger, printUsageSummary } from "../output.js";
18import { CloneError } from "../../util/errors.js";
19import simpleGit from "simple-git";
20
21export interface RepoCommandOptions {
22 query: string;
23 uri: string;
24 ref?: string;
25 full: boolean;
26 model?: string;
27 verbose: boolean;
28 cleanup: boolean;
29}
30
31export async function runRepoCommand(options: RepoCommandOptions): Promise<void> {
32 const { config } = await loadConfig();
33 const overrides = applyConfigOverrides(config, {
34 defaults: { model: options.model ?? config.defaults.model, cleanup: options.cleanup },
35 repo: { model: options.model ?? config.repo.model },
36 });
37
38 const workspace = await createWorkspace({ cleanup: overrides.defaults.cleanup });
39
40 try {
41 const logger = createEventLogger({ verbose: options.verbose });
42
43 let systemPrompt = buildRepoPrompt({
44 currentTime: new Date().toISOString(),
45 hasHistory: options.full,
46 });
47 const promptPath = overrides.repo.system_prompt_path;
48 if (promptPath) {
49 systemPrompt = await readFile(expandHomePath(promptPath), "utf8");
50 }
51
52 const git = simpleGit();
53 const cloneArgs: string[] = [];
54 if (!options.full) {
55 const depth = overrides.repo.default_depth ?? 1;
56 const blobLimit = overrides.repo.blob_limit ?? "5m";
57 cloneArgs.push("--depth", String(depth), `--filter=blob:limit=${blobLimit}`);
58 }
59
60 try {
61 await git.clone(options.uri, workspace.path, cloneArgs);
62 } catch (error: any) {
63 if (!overrides.defaults.cleanup) {
64 console.error(`Workspace preserved at ${workspace.path}`);
65 }
66 throw new CloneError(options.uri, error?.message ?? String(error));
67 }
68
69 const repoGit = simpleGit(workspace.path);
70 if (options.ref) {
71 await repoGit.checkout(options.ref);
72 }
73
74 const tools = [
75 createReadTool(workspace.path),
76 createGrepTool(workspace.path),
77 createLsTool(workspace.path),
78 createFindTool(workspace.path),
79 createGitLogTool(workspace.path),
80 createGitShowTool(workspace.path),
81 createGitBlameTool(workspace.path),
82 createGitDiffTool(workspace.path),
83 createGitRefsTool(workspace.path),
84 createGitCheckoutTool(workspace.path),
85 ];
86
87 const result = await runAgent(options.query, {
88 model: overrides.repo.model ?? overrides.defaults.model,
89 systemPrompt,
90 tools,
91 onEvent: logger,
92 config: overrides,
93 });
94
95 process.stdout.write(result.message + "\n");
96 printUsageSummary(result.usage as any, result.requestCount);
97 } finally {
98 await workspace.cleanup();
99 }
100}