repo.ts

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