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