@ridit/lens 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LENS.md +32 -68
- package/README.md +91 -0
- package/dist/index.mjs +69211 -2205
- package/package.json +6 -3
- package/src/commands/commit.tsx +713 -0
- package/src/components/chat/ChatOverlays.tsx +14 -4
- package/src/components/chat/ChatRunner.tsx +197 -30
- package/src/components/timeline/CommitDetail.tsx +2 -4
- package/src/components/timeline/CommitList.tsx +2 -14
- package/src/components/timeline/TimelineChat.tsx +1 -2
- package/src/components/timeline/TimelineRunner.tsx +505 -422
- package/src/index.tsx +38 -0
- package/src/prompts/fewshot.ts +100 -3
- package/src/prompts/system.ts +16 -20
- package/src/tools/chart.ts +210 -0
- package/src/tools/convert-image.ts +312 -0
- package/src/tools/files.ts +1 -9
- package/src/tools/git.ts +577 -0
- package/src/tools/index.ts +17 -13
- package/src/tools/view-image.ts +335 -0
- package/src/tools/web.ts +0 -4
- package/src/utils/chat.ts +7 -17
- package/src/utils/thinking.tsx +275 -162
- package/src/utils/tools/builtins.ts +8 -31
package/src/tools/git.ts
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import type { Tool } from "@ridit/lens-sdk";
|
|
3
|
+
import { registry } from "../utils/tools/registry";
|
|
4
|
+
|
|
5
|
+
function gitRun(cmd: string, cwd: string): { ok: boolean; out: string } {
|
|
6
|
+
try {
|
|
7
|
+
const out = execSync(cmd, {
|
|
8
|
+
cwd,
|
|
9
|
+
encoding: "utf-8",
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
+
timeout: 30_000,
|
|
12
|
+
}).trim();
|
|
13
|
+
return { ok: true, out: out || "(done)" };
|
|
14
|
+
} catch (e: any) {
|
|
15
|
+
const msg =
|
|
16
|
+
[e.stdout, e.stderr].filter(Boolean).join("\n").trim() || e.message;
|
|
17
|
+
return { ok: false, out: msg };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type GitArgs = string;
|
|
22
|
+
|
|
23
|
+
function parseArgs(body: string): GitArgs | null {
|
|
24
|
+
return body ?? "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const gitStatusTool: Tool<GitArgs> = {
|
|
28
|
+
name: "git-status",
|
|
29
|
+
description: "show working tree status",
|
|
30
|
+
safe: true,
|
|
31
|
+
permissionLabel: "git status",
|
|
32
|
+
systemPromptEntry: (i) =>
|
|
33
|
+
`### ${i}. git-status — show working tree status (staged, unstaged, untracked)\n<git-status></git-status>`,
|
|
34
|
+
parseInput: parseArgs,
|
|
35
|
+
summariseInput: () => "git status",
|
|
36
|
+
execute: async (_args, ctx) => {
|
|
37
|
+
const r = gitRun("git status --short", ctx.repoPath);
|
|
38
|
+
return {
|
|
39
|
+
kind: "text",
|
|
40
|
+
value: r.out || "nothing to commit, working tree clean",
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const gitLogTool: Tool<GitArgs> = {
|
|
46
|
+
name: "git-log",
|
|
47
|
+
description: "show commit log",
|
|
48
|
+
safe: true,
|
|
49
|
+
permissionLabel: "git log",
|
|
50
|
+
systemPromptEntry: (i) =>
|
|
51
|
+
`### ${i}. git-log — show commit log with optional flags\n<git-log>-20</git-log>\n<git-log>--author=Ridit --since=1.week</git-log>\n<git-log>--pretty=format:"%h %ad %s" --date=short</git-log>`,
|
|
52
|
+
parseInput: parseArgs,
|
|
53
|
+
summariseInput: (args) => `git log ${args}`.trim(),
|
|
54
|
+
execute: async (args, ctx) => {
|
|
55
|
+
const r = gitRun(`git log --oneline ${args}`.trim(), ctx.repoPath);
|
|
56
|
+
return { kind: "text", value: r.out.slice(0, 4000) || "(no commits)" };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const gitDiffTool: Tool<GitArgs> = {
|
|
61
|
+
name: "git-diff",
|
|
62
|
+
description: "show changes between commits, working tree, or staged files",
|
|
63
|
+
safe: true,
|
|
64
|
+
permissionLabel: "git diff",
|
|
65
|
+
systemPromptEntry: (i) =>
|
|
66
|
+
`### ${i}. git-diff — show changes\n<git-diff></git-diff>\n<git-diff>--cached</git-diff>\n<git-diff>HEAD~1 HEAD</git-diff>\n<git-diff>-- src/foo.ts</git-diff>`,
|
|
67
|
+
parseInput: parseArgs,
|
|
68
|
+
summariseInput: (args) => `git diff ${args}`.trim(),
|
|
69
|
+
execute: async (args, ctx) => {
|
|
70
|
+
const r = gitRun(`git diff ${args}`.trim(), ctx.repoPath);
|
|
71
|
+
return { kind: "text", value: r.out.slice(0, 5000) || "(no diff)" };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const gitShowTool: Tool<GitArgs> = {
|
|
76
|
+
name: "git-show",
|
|
77
|
+
description: "show a commit's details and stat",
|
|
78
|
+
safe: true,
|
|
79
|
+
permissionLabel: "git show",
|
|
80
|
+
systemPromptEntry: (i) =>
|
|
81
|
+
`### ${i}. git-show — show a commit's details\n<git-show>HEAD</git-show>\n<git-show>abc1234</git-show>`,
|
|
82
|
+
parseInput: parseArgs,
|
|
83
|
+
summariseInput: (args) => `git show ${args}`.trim(),
|
|
84
|
+
execute: async (args, ctx) => {
|
|
85
|
+
const r = gitRun(`git show --stat ${args}`.trim(), ctx.repoPath);
|
|
86
|
+
return { kind: "text", value: r.out.slice(0, 4000) };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const gitBranchTool: Tool<GitArgs> = {
|
|
91
|
+
name: "git-branch",
|
|
92
|
+
description: "list branches",
|
|
93
|
+
safe: true,
|
|
94
|
+
permissionLabel: "git branch",
|
|
95
|
+
systemPromptEntry: (i) =>
|
|
96
|
+
`### ${i}. git-branch — list branches\n<git-branch></git-branch>\n<git-branch>-a</git-branch>\n<git-branch>--show-current</git-branch>`,
|
|
97
|
+
parseInput: parseArgs,
|
|
98
|
+
summariseInput: (args) => `git branch ${args}`.trim(),
|
|
99
|
+
execute: async (args, ctx) => {
|
|
100
|
+
const r = gitRun(`git branch ${args}`.trim(), ctx.repoPath);
|
|
101
|
+
return { kind: "text", value: r.out || "(no branches)" };
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const gitRemoteTool: Tool<GitArgs> = {
|
|
106
|
+
name: "git-remote",
|
|
107
|
+
description: "list or inspect remotes",
|
|
108
|
+
safe: true,
|
|
109
|
+
permissionLabel: "git remote",
|
|
110
|
+
systemPromptEntry: (i) =>
|
|
111
|
+
`### ${i}. git-remote — list remotes\n<git-remote>-v</git-remote>`,
|
|
112
|
+
parseInput: parseArgs,
|
|
113
|
+
summariseInput: (args) => `git remote ${args}`.trim(),
|
|
114
|
+
execute: async (args, ctx) => {
|
|
115
|
+
const r = gitRun(`git remote ${args}`.trim(), ctx.repoPath);
|
|
116
|
+
return { kind: "text", value: r.out || "(no remotes)" };
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const gitTagTool: Tool<GitArgs> = {
|
|
121
|
+
name: "git-tag",
|
|
122
|
+
description: "list tags",
|
|
123
|
+
safe: true,
|
|
124
|
+
permissionLabel: "git tag",
|
|
125
|
+
systemPromptEntry: (i) =>
|
|
126
|
+
`### ${i}. git-tag — list tags\n<git-tag></git-tag>\n<git-tag>-l "v2.*"</git-tag>`,
|
|
127
|
+
parseInput: parseArgs,
|
|
128
|
+
summariseInput: (args) => `git tag ${args}`.trim(),
|
|
129
|
+
execute: async (args, ctx) => {
|
|
130
|
+
const r = gitRun(`git tag ${args}`.trim(), ctx.repoPath);
|
|
131
|
+
return { kind: "text", value: r.out || "(no tags)" };
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const gitBlameTool: Tool<GitArgs> = {
|
|
136
|
+
name: "git-blame",
|
|
137
|
+
description: "show who last modified each line of a file",
|
|
138
|
+
safe: true,
|
|
139
|
+
permissionLabel: "git blame",
|
|
140
|
+
systemPromptEntry: (i) =>
|
|
141
|
+
`### ${i}. git-blame — show per-line authorship\n<git-blame>src/main.ts</git-blame>`,
|
|
142
|
+
parseInput: parseArgs,
|
|
143
|
+
summariseInput: (args) => `git blame ${args}`.trim(),
|
|
144
|
+
execute: async (args, ctx) => {
|
|
145
|
+
if (!args.trim()) return { kind: "text", value: "Error: pass a file path" };
|
|
146
|
+
const r = gitRun(`git blame --line-porcelain ${args.trim()}`, ctx.repoPath);
|
|
147
|
+
if (!r.ok) return { kind: "text", value: `Error: ${r.out}` };
|
|
148
|
+
|
|
149
|
+
const lines = r.out.split("\n");
|
|
150
|
+
const out: string[] = [];
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
if (lines[i]?.startsWith("author ") && lines[i + 9]?.startsWith("\t")) {
|
|
153
|
+
out.push(`${lines[i]!.slice(7).trim()}: ${lines[i + 9]!.slice(1)}`);
|
|
154
|
+
i += 10;
|
|
155
|
+
}
|
|
156
|
+
if (out.length >= 80) {
|
|
157
|
+
out.push("… (truncated)");
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
kind: "text",
|
|
163
|
+
value: out.join("\n").slice(0, 4000) || r.out.slice(0, 4000),
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const gitStashListTool: Tool<GitArgs> = {
|
|
169
|
+
name: "git-stash-list",
|
|
170
|
+
description: "list stashed changes",
|
|
171
|
+
safe: true,
|
|
172
|
+
permissionLabel: "git stash list",
|
|
173
|
+
systemPromptEntry: (i) =>
|
|
174
|
+
`### ${i}. git-stash-list — list stashes\n<git-stash-list></git-stash-list>`,
|
|
175
|
+
parseInput: parseArgs,
|
|
176
|
+
summariseInput: () => "git stash list",
|
|
177
|
+
execute: async (_args, ctx) => {
|
|
178
|
+
const r = gitRun("git stash list", ctx.repoPath);
|
|
179
|
+
return { kind: "text", value: r.out || "(no stashes)" };
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const gitAddTool: Tool<GitArgs> = {
|
|
184
|
+
name: "git-add",
|
|
185
|
+
description: "stage files for commit",
|
|
186
|
+
safe: false,
|
|
187
|
+
permissionLabel: "git add",
|
|
188
|
+
systemPromptEntry: (i) =>
|
|
189
|
+
`### ${i}. git-add — stage files\n<git-add>-A</git-add>\n<git-add>src/foo.ts src/bar.ts</git-add>`,
|
|
190
|
+
parseInput: parseArgs,
|
|
191
|
+
summariseInput: (args) => `git add ${args}`.trim(),
|
|
192
|
+
execute: async (args, ctx) => {
|
|
193
|
+
if (!args.trim()) return { kind: "text", value: "Error: pass paths or -A" };
|
|
194
|
+
const r = gitRun(`git add ${args}`, ctx.repoPath);
|
|
195
|
+
return {
|
|
196
|
+
kind: "text",
|
|
197
|
+
value: r.ok ? `staged: ${args.trim()}` : `Error: ${r.out}`,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const gitCommitTool: Tool<GitArgs> = {
|
|
203
|
+
name: "git-commit",
|
|
204
|
+
description: "commit staged changes with a message",
|
|
205
|
+
safe: false,
|
|
206
|
+
permissionLabel: "git commit",
|
|
207
|
+
systemPromptEntry: (i) =>
|
|
208
|
+
`### ${i}. git-commit — commit staged changes (stage with git-add first)\n<git-commit>feat: add dark mode toggle</git-commit>`,
|
|
209
|
+
parseInput: parseArgs,
|
|
210
|
+
summariseInput: (args) => `git commit -m "${args.slice(0, 60)}"`,
|
|
211
|
+
execute: async (args, ctx) => {
|
|
212
|
+
const msg = args.replace(/^["']|["']$/g, "").trim();
|
|
213
|
+
if (!msg) return { kind: "text", value: "Error: pass a commit message" };
|
|
214
|
+
const r = gitRun(`git commit -m ${JSON.stringify(msg)}`, ctx.repoPath);
|
|
215
|
+
return {
|
|
216
|
+
kind: "text",
|
|
217
|
+
value: r.ok ? `committed: ${msg}` : `Error: ${r.out}`,
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const gitCommitAmendTool: Tool<GitArgs> = {
|
|
223
|
+
name: "git-commit-amend",
|
|
224
|
+
description: "amend the last commit message",
|
|
225
|
+
safe: false,
|
|
226
|
+
permissionLabel: "git commit --amend",
|
|
227
|
+
systemPromptEntry: (i) =>
|
|
228
|
+
`### ${i}. git-commit-amend — amend the last commit message\n<git-commit-amend>fix: correct typo in header</git-commit-amend>`,
|
|
229
|
+
parseInput: parseArgs,
|
|
230
|
+
summariseInput: (args) => `git commit --amend "${args.slice(0, 60)}"`,
|
|
231
|
+
execute: async (args, ctx) => {
|
|
232
|
+
const msg = args.replace(/^["']|["']$/g, "").trim();
|
|
233
|
+
if (!msg) return { kind: "text", value: "Error: pass a new message" };
|
|
234
|
+
const r = gitRun(
|
|
235
|
+
`git commit --amend --no-edit -m ${JSON.stringify(msg)}`,
|
|
236
|
+
ctx.repoPath,
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
kind: "text",
|
|
240
|
+
value: r.ok ? `amended: ${msg}` : `Error: ${r.out}`,
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const gitRevertTool: Tool<GitArgs> = {
|
|
246
|
+
name: "git-revert",
|
|
247
|
+
description:
|
|
248
|
+
"revert a commit by hash (creates a new revert commit, history preserved)",
|
|
249
|
+
safe: false,
|
|
250
|
+
permissionLabel: "git revert",
|
|
251
|
+
systemPromptEntry: (i) =>
|
|
252
|
+
`### ${i}. git-revert — revert a commit (safe, creates new commit)\n<git-revert>abc1234</git-revert>`,
|
|
253
|
+
parseInput: parseArgs,
|
|
254
|
+
summariseInput: (args) => `git revert ${args}`.trim(),
|
|
255
|
+
execute: async (args, ctx) => {
|
|
256
|
+
if (!args.trim())
|
|
257
|
+
return { kind: "text", value: "Error: pass a commit hash" };
|
|
258
|
+
const r = gitRun(`git revert --no-edit ${args.trim()}`, ctx.repoPath);
|
|
259
|
+
return {
|
|
260
|
+
kind: "text",
|
|
261
|
+
value: r.ok ? `reverted: ${args.trim()}` : `Error: ${r.out}`,
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export const gitResetTool: Tool<GitArgs> = {
|
|
267
|
+
name: "git-reset",
|
|
268
|
+
description: "reset HEAD or unstage files",
|
|
269
|
+
safe: false,
|
|
270
|
+
permissionLabel: "git reset",
|
|
271
|
+
systemPromptEntry: (i) =>
|
|
272
|
+
`### ${i}. git-reset — reset HEAD or unstage files\n<git-reset>--soft HEAD~1</git-reset>\n<git-reset>HEAD src/foo.ts</git-reset>`,
|
|
273
|
+
parseInput: parseArgs,
|
|
274
|
+
summariseInput: (args) => `git reset ${args}`.trim(),
|
|
275
|
+
execute: async (args, ctx) => {
|
|
276
|
+
if (!args.trim()) return { kind: "text", value: "Error: pass reset args" };
|
|
277
|
+
const r = gitRun(`git reset ${args.trim()}`, ctx.repoPath);
|
|
278
|
+
return {
|
|
279
|
+
kind: "text",
|
|
280
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export const gitCheckoutTool: Tool<GitArgs> = {
|
|
286
|
+
name: "git-checkout",
|
|
287
|
+
description: "switch branches or restore files",
|
|
288
|
+
safe: false,
|
|
289
|
+
permissionLabel: "git checkout",
|
|
290
|
+
systemPromptEntry: (i) =>
|
|
291
|
+
`### ${i}. git-checkout — switch branch or restore file\n<git-checkout>main</git-checkout>\n<git-checkout>-- src/foo.ts</git-checkout>`,
|
|
292
|
+
parseInput: parseArgs,
|
|
293
|
+
summariseInput: (args) => `git checkout ${args}`.trim(),
|
|
294
|
+
execute: async (args, ctx) => {
|
|
295
|
+
if (!args.trim())
|
|
296
|
+
return { kind: "text", value: "Error: pass a branch or path" };
|
|
297
|
+
const r = gitRun(`git checkout ${args.trim()}`, ctx.repoPath);
|
|
298
|
+
return {
|
|
299
|
+
kind: "text",
|
|
300
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const gitSwitchTool: Tool<GitArgs> = {
|
|
306
|
+
name: "git-switch",
|
|
307
|
+
description: "switch or create branches",
|
|
308
|
+
safe: false,
|
|
309
|
+
permissionLabel: "git switch",
|
|
310
|
+
systemPromptEntry: (i) =>
|
|
311
|
+
`### ${i}. git-switch — switch or create branches\n<git-switch>main</git-switch>\n<git-switch>-c feature/my-branch</git-switch>`,
|
|
312
|
+
parseInput: parseArgs,
|
|
313
|
+
summariseInput: (args) => `git switch ${args}`.trim(),
|
|
314
|
+
execute: async (args, ctx) => {
|
|
315
|
+
if (!args.trim())
|
|
316
|
+
return { kind: "text", value: "Error: pass a branch name" };
|
|
317
|
+
const r = gitRun(`git switch ${args.trim()}`, ctx.repoPath);
|
|
318
|
+
return {
|
|
319
|
+
kind: "text",
|
|
320
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
321
|
+
};
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const gitMergeTool: Tool<GitArgs> = {
|
|
326
|
+
name: "git-merge",
|
|
327
|
+
description: "merge a branch into the current branch",
|
|
328
|
+
safe: false,
|
|
329
|
+
permissionLabel: "git merge",
|
|
330
|
+
systemPromptEntry: (i) =>
|
|
331
|
+
`### ${i}. git-merge — merge a branch into HEAD\n<git-merge>feature/my-branch</git-merge>`,
|
|
332
|
+
parseInput: parseArgs,
|
|
333
|
+
summariseInput: (args) => `git merge ${args}`.trim(),
|
|
334
|
+
execute: async (args, ctx) => {
|
|
335
|
+
if (!args.trim())
|
|
336
|
+
return { kind: "text", value: "Error: pass a branch name" };
|
|
337
|
+
const r = gitRun(`git merge ${args.trim()}`, ctx.repoPath);
|
|
338
|
+
return {
|
|
339
|
+
kind: "text",
|
|
340
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const gitPullTool: Tool<GitArgs> = {
|
|
346
|
+
name: "git-pull",
|
|
347
|
+
description: "pull from remote",
|
|
348
|
+
safe: false,
|
|
349
|
+
permissionLabel: "git pull",
|
|
350
|
+
systemPromptEntry: (i) =>
|
|
351
|
+
`### ${i}. git-pull — pull from remote\n<git-pull></git-pull>\n<git-pull>origin main</git-pull>`,
|
|
352
|
+
parseInput: parseArgs,
|
|
353
|
+
summariseInput: (args) => `git pull ${args}`.trim(),
|
|
354
|
+
execute: async (args, ctx) => {
|
|
355
|
+
const r = gitRun(`git pull ${args}`.trim(), ctx.repoPath);
|
|
356
|
+
return {
|
|
357
|
+
kind: "text",
|
|
358
|
+
value: r.ok ? r.out || "(up to date)" : `Error: ${r.out}`,
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export const gitPushTool: Tool<GitArgs> = {
|
|
364
|
+
name: "git-push",
|
|
365
|
+
description: "push commits to remote",
|
|
366
|
+
safe: false,
|
|
367
|
+
permissionLabel: "git push",
|
|
368
|
+
systemPromptEntry: (i) =>
|
|
369
|
+
`### ${i}. git-push — push to remote\n<git-push></git-push>\n<git-push>origin main</git-push>\n<git-push>--force-with-lease</git-push>`,
|
|
370
|
+
parseInput: parseArgs,
|
|
371
|
+
summariseInput: (args) => `git push ${args}`.trim(),
|
|
372
|
+
execute: async (args, ctx) => {
|
|
373
|
+
const r = gitRun(`git push ${args}`.trim(), ctx.repoPath);
|
|
374
|
+
return {
|
|
375
|
+
kind: "text",
|
|
376
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
377
|
+
};
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export const gitStashTool: Tool<GitArgs> = {
|
|
382
|
+
name: "git-stash",
|
|
383
|
+
description: "stash or apply stashed changes",
|
|
384
|
+
safe: false,
|
|
385
|
+
permissionLabel: "git stash",
|
|
386
|
+
systemPromptEntry: (i) =>
|
|
387
|
+
`### ${i}. git-stash — stash/apply/pop/drop changes\n<git-stash>push -m "work in progress"</git-stash>\n<git-stash>pop</git-stash>\n<git-stash>drop stash@{0}</git-stash>`,
|
|
388
|
+
parseInput: parseArgs,
|
|
389
|
+
summariseInput: (args) => `git stash ${args}`.trim(),
|
|
390
|
+
execute: async (args, ctx) => {
|
|
391
|
+
if (!args.trim())
|
|
392
|
+
return { kind: "text", value: "Error: pass a stash subcommand" };
|
|
393
|
+
const r = gitRun(`git stash ${args.trim()}`, ctx.repoPath);
|
|
394
|
+
return {
|
|
395
|
+
kind: "text",
|
|
396
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export const gitBranchCreateTool: Tool<GitArgs> = {
|
|
402
|
+
name: "git-branch-create",
|
|
403
|
+
description: "create a new branch without switching to it",
|
|
404
|
+
safe: false,
|
|
405
|
+
permissionLabel: "git branch (create)",
|
|
406
|
+
systemPromptEntry: (i) =>
|
|
407
|
+
`### ${i}. git-branch-create — create a branch\n<git-branch-create>feature/my-feature</git-branch-create>`,
|
|
408
|
+
parseInput: parseArgs,
|
|
409
|
+
summariseInput: (args) => `git branch ${args}`.trim(),
|
|
410
|
+
execute: async (args, ctx) => {
|
|
411
|
+
if (!args.trim())
|
|
412
|
+
return { kind: "text", value: "Error: pass a branch name" };
|
|
413
|
+
const r = gitRun(`git branch ${args.trim()}`, ctx.repoPath);
|
|
414
|
+
return {
|
|
415
|
+
kind: "text",
|
|
416
|
+
value: r.ok ? `created: ${args.trim()}` : `Error: ${r.out}`,
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
export const gitBranchDeleteTool: Tool<GitArgs> = {
|
|
422
|
+
name: "git-branch-delete",
|
|
423
|
+
description: "delete a branch",
|
|
424
|
+
safe: false,
|
|
425
|
+
permissionLabel: "git branch -d",
|
|
426
|
+
systemPromptEntry: (i) =>
|
|
427
|
+
`### ${i}. git-branch-delete — delete a branch\n<git-branch-delete>feature/old</git-branch-delete>\n<git-branch-delete>-D feature/old</git-branch-delete>`,
|
|
428
|
+
parseInput: parseArgs,
|
|
429
|
+
summariseInput: (args) => `git branch -d ${args}`.trim(),
|
|
430
|
+
execute: async (args, ctx) => {
|
|
431
|
+
if (!args.trim())
|
|
432
|
+
return { kind: "text", value: "Error: pass a branch name" };
|
|
433
|
+
const isForce = args.trim().startsWith("-D");
|
|
434
|
+
const flag = isForce ? "-D" : "-d";
|
|
435
|
+
const name = args.trim().replace(/^-D\s*/, "");
|
|
436
|
+
const r = gitRun(`git branch ${flag} ${name}`.trim(), ctx.repoPath);
|
|
437
|
+
return {
|
|
438
|
+
kind: "text",
|
|
439
|
+
value: r.ok ? `deleted: ${name}` : `Error: ${r.out}`,
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export const gitCherryPickTool: Tool<GitArgs> = {
|
|
445
|
+
name: "git-cherry-pick",
|
|
446
|
+
description: "apply a specific commit from another branch",
|
|
447
|
+
safe: false,
|
|
448
|
+
permissionLabel: "git cherry-pick",
|
|
449
|
+
systemPromptEntry: (i) =>
|
|
450
|
+
`### ${i}. git-cherry-pick — apply a commit by hash\n<git-cherry-pick>abc1234</git-cherry-pick>`,
|
|
451
|
+
parseInput: parseArgs,
|
|
452
|
+
summariseInput: (args) => `git cherry-pick ${args}`.trim(),
|
|
453
|
+
execute: async (args, ctx) => {
|
|
454
|
+
if (!args.trim())
|
|
455
|
+
return { kind: "text", value: "Error: pass a commit hash" };
|
|
456
|
+
const r = gitRun(`git cherry-pick ${args.trim()}`, ctx.repoPath);
|
|
457
|
+
return {
|
|
458
|
+
kind: "text",
|
|
459
|
+
value: r.ok
|
|
460
|
+
? r.out || `cherry-picked: ${args.trim()}`
|
|
461
|
+
: `Error: ${r.out}`,
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export const gitTagCreateTool: Tool<GitArgs> = {
|
|
467
|
+
name: "git-tag-create",
|
|
468
|
+
description: "create a lightweight or annotated tag",
|
|
469
|
+
safe: false,
|
|
470
|
+
permissionLabel: "git tag (create)",
|
|
471
|
+
systemPromptEntry: (i) =>
|
|
472
|
+
`### ${i}. git-tag-create — create a tag\n<git-tag-create>v1.0.0</git-tag-create>\n<git-tag-create>v1.0.0 -m "release 1.0.0"</git-tag-create>`,
|
|
473
|
+
parseInput: parseArgs,
|
|
474
|
+
summariseInput: (args) => `git tag ${args}`.trim(),
|
|
475
|
+
execute: async (args, ctx) => {
|
|
476
|
+
if (!args.trim()) return { kind: "text", value: "Error: pass a tag name" };
|
|
477
|
+
const r = gitRun(`git tag ${args.trim()}`, ctx.repoPath);
|
|
478
|
+
return {
|
|
479
|
+
kind: "text",
|
|
480
|
+
value: r.ok ? `tagged: ${args.trim().split(" ")[0]}` : `Error: ${r.out}`,
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
export const gitRestoreTool: Tool<GitArgs> = {
|
|
486
|
+
name: "git-restore",
|
|
487
|
+
description:
|
|
488
|
+
"discard working directory changes for a file (cannot be undone)",
|
|
489
|
+
safe: false,
|
|
490
|
+
permissionLabel: "git restore",
|
|
491
|
+
systemPromptEntry: (i) =>
|
|
492
|
+
`### ${i}. git-restore — discard changes in a file (irreversible)\n<git-restore>src/foo.ts</git-restore>`,
|
|
493
|
+
parseInput: parseArgs,
|
|
494
|
+
summariseInput: (args) => `git restore ${args}`.trim(),
|
|
495
|
+
execute: async (args, ctx) => {
|
|
496
|
+
if (!args.trim()) return { kind: "text", value: "Error: pass a file path" };
|
|
497
|
+
const r = gitRun(`git restore ${args.trim()}`, ctx.repoPath);
|
|
498
|
+
return {
|
|
499
|
+
kind: "text",
|
|
500
|
+
value: r.ok ? `restored: ${args.trim()}` : `Error: ${r.out}`,
|
|
501
|
+
};
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export const gitCleanTool: Tool<GitArgs> = {
|
|
506
|
+
name: "git-clean",
|
|
507
|
+
description: "remove untracked files (cannot be undone)",
|
|
508
|
+
safe: false,
|
|
509
|
+
permissionLabel: "git clean",
|
|
510
|
+
systemPromptEntry: (i) =>
|
|
511
|
+
`### ${i}. git-clean — remove untracked files (irreversible)\n<git-clean>-fd</git-clean>`,
|
|
512
|
+
parseInput: parseArgs,
|
|
513
|
+
summariseInput: (args) => `git clean ${args}`.trim(),
|
|
514
|
+
execute: async (args, ctx) => {
|
|
515
|
+
if (!args.trim())
|
|
516
|
+
return { kind: "text", value: "Error: pass flags like -fd" };
|
|
517
|
+
const r = gitRun(`git clean ${args.trim()}`, ctx.repoPath);
|
|
518
|
+
return {
|
|
519
|
+
kind: "text",
|
|
520
|
+
value: r.ok ? r.out || "(done)" : `Error: ${r.out}`,
|
|
521
|
+
};
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const ALL_GIT_TOOLS: Tool<GitArgs>[] = [
|
|
526
|
+
gitStatusTool,
|
|
527
|
+
gitLogTool,
|
|
528
|
+
gitDiffTool,
|
|
529
|
+
gitShowTool,
|
|
530
|
+
gitBranchTool,
|
|
531
|
+
gitRemoteTool,
|
|
532
|
+
gitTagTool,
|
|
533
|
+
gitBlameTool,
|
|
534
|
+
gitStashListTool,
|
|
535
|
+
gitAddTool,
|
|
536
|
+
gitCommitTool,
|
|
537
|
+
gitCommitAmendTool,
|
|
538
|
+
gitRevertTool,
|
|
539
|
+
gitResetTool,
|
|
540
|
+
gitCheckoutTool,
|
|
541
|
+
gitSwitchTool,
|
|
542
|
+
gitMergeTool,
|
|
543
|
+
gitPullTool,
|
|
544
|
+
gitPushTool,
|
|
545
|
+
gitStashTool,
|
|
546
|
+
gitBranchCreateTool,
|
|
547
|
+
gitBranchDeleteTool,
|
|
548
|
+
gitCherryPickTool,
|
|
549
|
+
gitTagCreateTool,
|
|
550
|
+
gitRestoreTool,
|
|
551
|
+
gitCleanTool,
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
export function registerGitTools(): void {
|
|
555
|
+
for (const tool of ALL_GIT_TOOLS) {
|
|
556
|
+
registry.register(tool);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function buildGitToolsPromptSection(): string {
|
|
561
|
+
const read = ALL_GIT_TOOLS.filter((t) => t.safe);
|
|
562
|
+
const write = ALL_GIT_TOOLS.filter((t) => !t.safe);
|
|
563
|
+
|
|
564
|
+
const lines: string[] = [
|
|
565
|
+
"## Git Tools\n",
|
|
566
|
+
"To use a tool emit its XML tag — the result is returned to you before you continue.\n",
|
|
567
|
+
"### Read-only (auto-approved)",
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
let i = 1;
|
|
571
|
+
for (const t of read) lines.push(t.systemPromptEntry(i++));
|
|
572
|
+
|
|
573
|
+
lines.push("\n### Write operations (require user confirmation)");
|
|
574
|
+
for (const t of write) lines.push(t.systemPromptEntry(i++));
|
|
575
|
+
|
|
576
|
+
return lines.join("\n");
|
|
577
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
export { runShell, readClipboard, openUrl } from "./shell";
|
|
2
|
-
export {
|
|
3
|
-
walkDir,
|
|
4
|
-
applyPatches,
|
|
5
|
-
readFile,
|
|
6
|
-
readFolder,
|
|
7
|
-
grepFiles,
|
|
8
|
-
writeFile,
|
|
9
|
-
deleteFile,
|
|
10
|
-
deleteFolder,
|
|
11
|
-
} from "./files";
|
|
12
|
-
export { fetchUrl, searchWeb } from "./web";
|
|
13
|
-
export { generatePdf } from "./pdf";
|
|
1
|
+
export { runShell, readClipboard, openUrl } from "./shell";
|
|
2
|
+
export {
|
|
3
|
+
walkDir,
|
|
4
|
+
applyPatches,
|
|
5
|
+
readFile,
|
|
6
|
+
readFolder,
|
|
7
|
+
grepFiles,
|
|
8
|
+
writeFile,
|
|
9
|
+
deleteFile,
|
|
10
|
+
deleteFolder,
|
|
11
|
+
} from "./files";
|
|
12
|
+
export { fetchUrl, searchWeb } from "./web";
|
|
13
|
+
export { generatePdf } from "./pdf";
|
|
14
|
+
export { registerGitTools } from "./git";
|
|
15
|
+
export { viewImageTool } from "./view-image";
|
|
16
|
+
export { chartDataTool } from "./chart";
|
|
17
|
+
export { convertImageTool } from "./convert-image";
|