@ridit/lens 0.3.7 → 0.3.9
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/dist/index.mjs +105368 -274002
- package/package.json +13 -19
- package/src/colors.ts +15 -15
- package/src/commands/chat.tsx +32 -23
- package/src/commands/provider.tsx +11 -238
- package/src/commands/repo.tsx +66 -120
- package/src/commands/timeline.tsx +11 -22
- package/src/components/ChatView.tsx +238 -0
- package/src/components/Message.tsx +46 -0
- package/src/components/ToolCall.tsx +67 -0
- package/src/components/chat/ChatView.tsx +550 -0
- package/src/components/chat/Message.tsx +152 -0
- package/src/components/chat/StatusBar.tsx +214 -0
- package/src/components/chat/TextArea.tsx +173 -176
- package/src/components/provider/ApiKeyStep.tsx +207 -199
- package/src/components/provider/ModelStep.tsx +90 -88
- package/src/components/provider/ProviderSetup.tsx +331 -0
- package/src/components/provider/ProviderTypeStep.tsx +53 -61
- package/src/components/repo/StepRow.tsx +68 -69
- package/src/components/timeline/TimelineView.tsx +840 -0
- package/src/components/toolcall-utils.ts +103 -0
- package/src/components/watch/RunView.tsx +497 -0
- package/src/hooks/useChatInput.ts +49 -0
- package/src/hooks/useCommandHandler.ts +117 -0
- package/src/index.tsx +386 -139
- package/src/utils/git.ts +149 -155
- package/src/utils/repo.ts +62 -69
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/watch.ts +165 -307
- package/tests/message.test.ts +38 -0
- package/tests/toolcall-utils.test.ts +111 -0
- package/tsconfig.json +8 -24
- package/CLAUDE.md +0 -50
- package/LENS.md +0 -48
- package/LICENSE +0 -21
- package/README.md +0 -93
- package/addons/README.md +0 -55
- package/addons/clean-cache.js +0 -48
- package/addons/generate-readme.js +0 -67
- package/addons/git-stats.js +0 -29
- package/addons/run-tests.js +0 -127
- package/src/commands/commit.tsx +0 -668
- package/src/commands/review.tsx +0 -294
- package/src/commands/run.tsx +0 -56
- package/src/commands/task.tsx +0 -36
- package/src/components/chat/ChatMessage.tsx +0 -195
- package/src/components/chat/ChatOverlays.tsx +0 -399
- package/src/components/chat/ChatRunner.tsx +0 -517
- package/src/components/chat/hooks/useChat.ts +0 -631
- package/src/components/chat/hooks/useChatInput.ts +0 -79
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
- package/src/components/provider/ProviderPicker.tsx +0 -76
- package/src/components/provider/RemoveProviderStep.tsx +0 -82
- package/src/components/repo/DiffViewer.tsx +0 -175
- package/src/components/repo/FileReviewer.tsx +0 -70
- package/src/components/repo/FileViewer.tsx +0 -60
- package/src/components/repo/IssueFixer.tsx +0 -666
- package/src/components/repo/LensFileMenu.tsx +0 -115
- package/src/components/repo/NoProviderPrompt.tsx +0 -28
- package/src/components/repo/PreviewRunner.tsx +0 -217
- package/src/components/repo/RepoAnalysis.tsx +0 -534
- package/src/components/task/TaskRunner.tsx +0 -396
- package/src/components/timeline/CommitDetail.tsx +0 -272
- package/src/components/timeline/CommitList.tsx +0 -162
- package/src/components/timeline/TimelineChat.tsx +0 -166
- package/src/components/timeline/TimelineRunner.tsx +0 -1285
- package/src/components/watch/RunRunner.tsx +0 -929
- package/src/prompts/fewshot.ts +0 -252
- package/src/prompts/index.ts +0 -2
- package/src/prompts/system.ts +0 -285
- package/src/tools/chart.ts +0 -202
- package/src/tools/convert-image.ts +0 -312
- package/src/tools/files.ts +0 -253
- package/src/tools/git.ts +0 -603
- package/src/tools/index.ts +0 -17
- package/src/tools/pdf.ts +0 -164
- package/src/tools/shell.ts +0 -96
- package/src/tools/view-image.ts +0 -335
- package/src/tools/web.ts +0 -212
- package/src/types/chat.ts +0 -86
- package/src/types/config.ts +0 -20
- package/src/types/repo.ts +0 -54
- package/src/utils/addons/loadAddons.ts +0 -34
- package/src/utils/ai.ts +0 -321
- package/src/utils/chat.ts +0 -326
- package/src/utils/chatHistory.ts +0 -121
- package/src/utils/config.ts +0 -61
- package/src/utils/files.ts +0 -105
- package/src/utils/intentClassifier.ts +0 -58
- package/src/utils/lensfile.ts +0 -142
- package/src/utils/llm.ts +0 -81
- package/src/utils/memory.ts +0 -209
- package/src/utils/preview.ts +0 -119
- package/src/utils/stats.ts +0 -174
- package/src/utils/tools/builtins.ts +0 -377
- package/src/utils/tools/registry.ts +0 -105
package/src/utils/git.ts
CHANGED
|
@@ -1,155 +1,149 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
|
-
|
|
3
|
-
export type Commit = {
|
|
4
|
-
hash: string;
|
|
5
|
-
shortHash: string;
|
|
6
|
-
author: string;
|
|
7
|
-
email: string;
|
|
8
|
-
date: string;
|
|
9
|
-
relativeDate: string;
|
|
10
|
-
message: string;
|
|
11
|
-
body: string;
|
|
12
|
-
refs: string;
|
|
13
|
-
parents: string[];
|
|
14
|
-
filesChanged: number;
|
|
15
|
-
insertions: number;
|
|
16
|
-
deletions: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type DiffFile = {
|
|
20
|
-
path: string;
|
|
21
|
-
status: "added" | "modified" | "deleted" | "renamed";
|
|
22
|
-
insertions: number;
|
|
23
|
-
deletions: number;
|
|
24
|
-
lines: { type: "add" | "remove" | "context" | "header"; content: string }[];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function run(cmd: string, cwd: string): string {
|
|
28
|
-
try {
|
|
29
|
-
return execSync(cmd, {
|
|
30
|
-
cwd,
|
|
31
|
-
encoding: "utf-8",
|
|
32
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
33
|
-
}).trim();
|
|
34
|
-
} catch {
|
|
35
|
-
return "";
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function isGitRepo(p: string): boolean {
|
|
40
|
-
return run("git rev-parse --is-inside-work-tree", p) === "true";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function fetchCommits(repoPath: string, limit = 200): Commit[] {
|
|
44
|
-
const SEP = "|||";
|
|
45
|
-
const RS = "^^^";
|
|
46
|
-
const raw = run(
|
|
47
|
-
`git log --max-count=${limit} --format="${RS}%H${SEP}%h${SEP}%an${SEP}%ae${SEP}%ci${SEP}%cr${SEP}%s${SEP}%b${SEP}%D${SEP}%P" --shortstat`,
|
|
48
|
-
repoPath,
|
|
49
|
-
);
|
|
50
|
-
if (!raw) return [];
|
|
51
|
-
|
|
52
|
-
return raw
|
|
53
|
-
.split(RS)
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
.flatMap((block) => {
|
|
56
|
-
const lines = block.split("\n");
|
|
57
|
-
const parts = lines[0]!.split(SEP);
|
|
58
|
-
if (parts.length < 10) return [];
|
|
59
|
-
const [
|
|
60
|
-
hash,
|
|
61
|
-
shortHash,
|
|
62
|
-
author,
|
|
63
|
-
email,
|
|
64
|
-
date,
|
|
65
|
-
relativeDate,
|
|
66
|
-
message,
|
|
67
|
-
body,
|
|
68
|
-
refs,
|
|
69
|
-
parentsRaw,
|
|
70
|
-
] = parts;
|
|
71
|
-
const statLine = lines.find((l) => l.includes("changed")) ?? "";
|
|
72
|
-
return [
|
|
73
|
-
{
|
|
74
|
-
hash: hash!.trim(),
|
|
75
|
-
shortHash: shortHash!.trim(),
|
|
76
|
-
author: author!.trim(),
|
|
77
|
-
email: email!.trim(),
|
|
78
|
-
date: date!.trim(),
|
|
79
|
-
relativeDate: relativeDate!.trim(),
|
|
80
|
-
message: message!.trim(),
|
|
81
|
-
body: body!.trim(),
|
|
82
|
-
refs: refs!.trim(),
|
|
83
|
-
parents: parentsRaw!.trim().split(" ").filter(Boolean),
|
|
84
|
-
filesChanged: parseInt(statLine.match(/(\d+) file/)?.[1] ?? "0"),
|
|
85
|
-
insertions: parseInt(statLine.match(/(\d+) insertion/)?.[1] ?? "0"),
|
|
86
|
-
deletions: parseInt(statLine.match(/(\d+) deletion/)?.[1] ?? "0"),
|
|
87
|
-
},
|
|
88
|
-
];
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function fetchDiff(repoPath: string, hash: string): DiffFile[] {
|
|
93
|
-
const raw = run(
|
|
94
|
-
`git show --unified=3 --diff-filter=ACDMR "${hash}"`,
|
|
95
|
-
repoPath,
|
|
96
|
-
);
|
|
97
|
-
if (!raw) return [];
|
|
98
|
-
|
|
99
|
-
const files: DiffFile[] = [];
|
|
100
|
-
let cur: DiffFile | null = null;
|
|
101
|
-
|
|
102
|
-
for (const line of raw.split("\n")) {
|
|
103
|
-
if (line.startsWith("diff --git")) {
|
|
104
|
-
if (cur) files.push(cur);
|
|
105
|
-
cur = {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} else if (line.startsWith("
|
|
113
|
-
cur.
|
|
114
|
-
} else if (line.startsWith("
|
|
115
|
-
cur.
|
|
116
|
-
} else if (line.startsWith("
|
|
117
|
-
cur.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
cur.lines.push({ type: "
|
|
122
|
-
} else if (
|
|
123
|
-
cur.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
...commits.map(
|
|
151
|
-
(c) =>
|
|
152
|
-
`${c.shortHash} | ${c.date.slice(0, 10)} | ${c.author} | ${c.message} | +${c.insertions}/-${c.deletions}`,
|
|
153
|
-
),
|
|
154
|
-
].join("\n");
|
|
155
|
-
}
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
export type Commit = {
|
|
4
|
+
hash: string;
|
|
5
|
+
shortHash: string;
|
|
6
|
+
author: string;
|
|
7
|
+
email: string;
|
|
8
|
+
date: string;
|
|
9
|
+
relativeDate: string;
|
|
10
|
+
message: string;
|
|
11
|
+
body: string;
|
|
12
|
+
refs: string;
|
|
13
|
+
parents: string[];
|
|
14
|
+
filesChanged: number;
|
|
15
|
+
insertions: number;
|
|
16
|
+
deletions: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type DiffFile = {
|
|
20
|
+
path: string;
|
|
21
|
+
status: "added" | "modified" | "deleted" | "renamed";
|
|
22
|
+
insertions: number;
|
|
23
|
+
deletions: number;
|
|
24
|
+
lines: { type: "add" | "remove" | "context" | "header"; content: string }[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function run(cmd: string, cwd: string): string {
|
|
28
|
+
try {
|
|
29
|
+
return execSync(cmd, {
|
|
30
|
+
cwd,
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
33
|
+
}).trim();
|
|
34
|
+
} catch {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isGitRepo(p: string): boolean {
|
|
40
|
+
return run("git rev-parse --is-inside-work-tree", p) === "true";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function fetchCommits(repoPath: string, limit = 200): Commit[] {
|
|
44
|
+
const SEP = "|||";
|
|
45
|
+
const RS = "^^^";
|
|
46
|
+
const raw = run(
|
|
47
|
+
`git log --max-count=${limit} --format="${RS}%H${SEP}%h${SEP}%an${SEP}%ae${SEP}%ci${SEP}%cr${SEP}%s${SEP}%b${SEP}%D${SEP}%P" --shortstat`,
|
|
48
|
+
repoPath,
|
|
49
|
+
);
|
|
50
|
+
if (!raw) return [];
|
|
51
|
+
|
|
52
|
+
return raw
|
|
53
|
+
.split(RS)
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.flatMap((block) => {
|
|
56
|
+
const lines = block.split("\n");
|
|
57
|
+
const parts = lines[0]!.split(SEP);
|
|
58
|
+
if (parts.length < 10) return [];
|
|
59
|
+
const [
|
|
60
|
+
hash,
|
|
61
|
+
shortHash,
|
|
62
|
+
author,
|
|
63
|
+
email,
|
|
64
|
+
date,
|
|
65
|
+
relativeDate,
|
|
66
|
+
message,
|
|
67
|
+
body,
|
|
68
|
+
refs,
|
|
69
|
+
parentsRaw,
|
|
70
|
+
] = parts;
|
|
71
|
+
const statLine = lines.find((l) => l.includes("changed")) ?? "";
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
hash: hash!.trim(),
|
|
75
|
+
shortHash: shortHash!.trim(),
|
|
76
|
+
author: author!.trim(),
|
|
77
|
+
email: email!.trim(),
|
|
78
|
+
date: date!.trim(),
|
|
79
|
+
relativeDate: relativeDate!.trim(),
|
|
80
|
+
message: message!.trim(),
|
|
81
|
+
body: body!.trim(),
|
|
82
|
+
refs: refs!.trim(),
|
|
83
|
+
parents: parentsRaw!.trim().split(" ").filter(Boolean),
|
|
84
|
+
filesChanged: parseInt(statLine.match(/(\d+) file/)?.[1] ?? "0"),
|
|
85
|
+
insertions: parseInt(statLine.match(/(\d+) insertion/)?.[1] ?? "0"),
|
|
86
|
+
deletions: parseInt(statLine.match(/(\d+) deletion/)?.[1] ?? "0"),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function fetchDiff(repoPath: string, hash: string): DiffFile[] {
|
|
93
|
+
const raw = run(
|
|
94
|
+
`git show --unified=3 --diff-filter=ACDMR "${hash}"`,
|
|
95
|
+
repoPath,
|
|
96
|
+
);
|
|
97
|
+
if (!raw) return [];
|
|
98
|
+
|
|
99
|
+
const files: DiffFile[] = [];
|
|
100
|
+
let cur: DiffFile | null = null;
|
|
101
|
+
|
|
102
|
+
for (const line of raw.split("\n")) {
|
|
103
|
+
if (line.startsWith("diff --git")) {
|
|
104
|
+
if (cur) files.push(cur);
|
|
105
|
+
cur = { path: "", status: "modified", insertions: 0, deletions: 0, lines: [] };
|
|
106
|
+
} else if (line.startsWith("+++ b/") && cur) {
|
|
107
|
+
cur.path = line.slice(6);
|
|
108
|
+
} else if (line.startsWith("new file") && cur) {
|
|
109
|
+
cur.status = "added";
|
|
110
|
+
} else if (line.startsWith("deleted file") && cur) {
|
|
111
|
+
cur.status = "deleted";
|
|
112
|
+
} else if (line.startsWith("rename") && cur) {
|
|
113
|
+
cur.status = "renamed";
|
|
114
|
+
} else if (line.startsWith("@@") && cur) {
|
|
115
|
+
cur.lines.push({ type: "header", content: line });
|
|
116
|
+
} else if (line.startsWith("+") && cur && !line.startsWith("+++")) {
|
|
117
|
+
cur.insertions++;
|
|
118
|
+
cur.lines.push({ type: "add", content: line.slice(1) });
|
|
119
|
+
} else if (line.startsWith("-") && cur && !line.startsWith("---")) {
|
|
120
|
+
cur.deletions++;
|
|
121
|
+
cur.lines.push({ type: "remove", content: line.slice(1) });
|
|
122
|
+
} else if (cur && line !== "\") {
|
|
123
|
+
cur.lines.push({ type: "context", content: line.slice(1) });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (cur) files.push(cur);
|
|
127
|
+
return files.filter((f) => f.path);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function summarizeTimeline(commits: Commit[]): string {
|
|
131
|
+
if (!commits.length) return "No commits.";
|
|
132
|
+
const authors = [...new Set(commits.map((c) => c.author))];
|
|
133
|
+
const biggest = [...commits].sort(
|
|
134
|
+
(a, b) => b.insertions + b.deletions - (a.insertions + a.deletions),
|
|
135
|
+
)[0]!;
|
|
136
|
+
return [
|
|
137
|
+
`Total commits: ${commits.length}`,
|
|
138
|
+
`Authors: ${authors.join(", ")}`,
|
|
139
|
+
`Newest: "${commits[0]!.message}" (${commits[0]!.shortHash}) — ${commits[0]!.relativeDate}`,
|
|
140
|
+
`Oldest: "${commits[commits.length - 1]!.message}" (${commits[commits.length - 1]!.shortHash}) — ${commits[commits.length - 1]!.relativeDate}`,
|
|
141
|
+
`Biggest change: "${biggest.message}" (${biggest.shortHash}) +${biggest.insertions}/-${biggest.deletions}`,
|
|
142
|
+
``,
|
|
143
|
+
`Full log (hash | date | author | message | +ins/-del):`,
|
|
144
|
+
...commits.map(
|
|
145
|
+
(c) =>
|
|
146
|
+
`${c.shortHash} | ${c.date.slice(0, 10)} | ${c.author} | ${c.message} | +${c.insertions}/-${c.deletions}`,
|
|
147
|
+
),
|
|
148
|
+
].join("\n");
|
|
149
|
+
}
|
package/src/utils/repo.ts
CHANGED
|
@@ -1,69 +1,62 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { exec } from "child_process";
|
|
5
|
-
|
|
6
|
-
type CloneResult =
|
|
7
|
-
| { done: true }
|
|
8
|
-
| { done: false; folderExists: true; repoPath: string }
|
|
9
|
-
| { done: false; folderExists?: false; error: string };
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
url: string,
|
|
13
|
-
repoPath: string,
|
|
14
|
-
): Promise<{ done: boolean; error?: string }> {
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
exec(`git clone "${url}" "${repoPath}"`, (err) => {
|
|
17
|
-
if (err) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return secondTry.error
|
|
64
|
-
? { done: false, error: secondTry.error }
|
|
65
|
-
: { done: true };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return { done: false, error: firstTry.error };
|
|
69
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
|
|
6
|
+
export type CloneResult =
|
|
7
|
+
| { done: true; repoPath: string }
|
|
8
|
+
| { done: false; folderExists: true; repoPath: string }
|
|
9
|
+
| { done: false; folderExists?: false; error: string };
|
|
10
|
+
|
|
11
|
+
function cloneRepo(
|
|
12
|
+
url: string,
|
|
13
|
+
repoPath: string,
|
|
14
|
+
): Promise<{ done: boolean; error?: string }> {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
exec(`git clone "${url}" "${repoPath}"`, (err) => {
|
|
17
|
+
if (err) resolve({ done: false, error: err.message });
|
|
18
|
+
else resolve({ done: true });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function deleteRepoFolder(repoPath: string): void {
|
|
24
|
+
fs.rmSync(repoPath, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function startCloneRepo(
|
|
28
|
+
url: string,
|
|
29
|
+
opts: { forceReclone?: boolean } = {},
|
|
30
|
+
): Promise<CloneResult> {
|
|
31
|
+
let parsedUrl: URL;
|
|
32
|
+
try {
|
|
33
|
+
parsedUrl = new URL(url);
|
|
34
|
+
} catch {
|
|
35
|
+
return { done: false, error: `Invalid URL: ${url}` };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const repoName = path.basename(parsedUrl.pathname).replace(/\.git$/, "");
|
|
39
|
+
if (!repoName) {
|
|
40
|
+
return { done: false, error: "Could not determine repository name from URL." };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const repoPath = path.join(os.tmpdir(), repoName);
|
|
44
|
+
|
|
45
|
+
const firstTry = await cloneRepo(url, repoPath);
|
|
46
|
+
if (!firstTry.error) {
|
|
47
|
+
return { done: true, repoPath };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (firstTry.error.includes("already exists")) {
|
|
51
|
+
if (!opts.forceReclone) {
|
|
52
|
+
return { done: false, folderExists: true, repoPath };
|
|
53
|
+
}
|
|
54
|
+
deleteRepoFolder(repoPath);
|
|
55
|
+
const secondTry = await cloneRepo(url, repoPath);
|
|
56
|
+
return secondTry.error
|
|
57
|
+
? { done: false, error: secondTry.error }
|
|
58
|
+
: { done: true, repoPath };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { done: false, error: firstTry.error };
|
|
62
|
+
}
|
package/src/utils/thinking.tsx
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
|
+
const TIPS = [
|
|
4
|
+
"use /auto to toggle auto-approve for safe tools",
|
|
5
|
+
"ctrl+f to toggle force-all mode",
|
|
6
|
+
"shift+enter for a new line in the input",
|
|
7
|
+
"↑ / ↓ arrows navigate message history",
|
|
8
|
+
"ctrl+w deletes the previous word",
|
|
9
|
+
"ctrl+delete deletes the next word",
|
|
10
|
+
"/timeline to browse commit history",
|
|
11
|
+
"/review to analyze the current codebase",
|
|
12
|
+
"/memory list to view stored memories",
|
|
13
|
+
"/chat list to see saved conversations",
|
|
14
|
+
"esc cancels a running response",
|
|
15
|
+
"/clear history resets session memory",
|
|
16
|
+
"tab autocompletes slash commands",
|
|
17
|
+
"lens commit --auto for fast AI commits",
|
|
18
|
+
"lens run \"bun dev\" to watch and auto-fix errors",
|
|
19
|
+
];
|
|
20
|
+
|
|
3
21
|
const PHRASES: Record<string, string[]> = {
|
|
4
22
|
general: [
|
|
5
23
|
"marinating on that... 🍖",
|
|
@@ -321,6 +339,52 @@ const PHRASES: Record<string, string[]> = {
|
|
|
321
339
|
|
|
322
340
|
export type ThinkingKind = keyof typeof PHRASES;
|
|
323
341
|
|
|
342
|
+
export function useThinkingTip(active: boolean, intervalMs = 8000): string {
|
|
343
|
+
const [index, setIndex] = useState(() => Math.floor(Math.random() * TIPS.length));
|
|
344
|
+
const usedRef = useRef<Set<number>>(new Set());
|
|
345
|
+
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (!active) return;
|
|
348
|
+
const pickUnused = () => {
|
|
349
|
+
if (usedRef.current.size >= TIPS.length) usedRef.current.clear();
|
|
350
|
+
let next: number;
|
|
351
|
+
do {
|
|
352
|
+
next = Math.floor(Math.random() * TIPS.length);
|
|
353
|
+
} while (usedRef.current.has(next));
|
|
354
|
+
usedRef.current.add(next);
|
|
355
|
+
return next;
|
|
356
|
+
};
|
|
357
|
+
setIndex(pickUnused());
|
|
358
|
+
const id = setInterval(() => setIndex(pickUnused()), intervalMs);
|
|
359
|
+
return () => clearInterval(id);
|
|
360
|
+
}, [active, intervalMs]);
|
|
361
|
+
|
|
362
|
+
return TIPS[index]!;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function useThinkingTimer(active: boolean): string {
|
|
366
|
+
const [seconds, setSeconds] = useState(0);
|
|
367
|
+
const startRef = useRef<number | null>(null);
|
|
368
|
+
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
if (active) {
|
|
371
|
+
startRef.current = Date.now();
|
|
372
|
+
setSeconds(0);
|
|
373
|
+
const id = setInterval(() => {
|
|
374
|
+
setSeconds(Math.floor((Date.now() - (startRef.current ?? Date.now())) / 1000));
|
|
375
|
+
}, 1000);
|
|
376
|
+
return () => clearInterval(id);
|
|
377
|
+
} else {
|
|
378
|
+
startRef.current = null;
|
|
379
|
+
setSeconds(0);
|
|
380
|
+
}
|
|
381
|
+
}, [active]);
|
|
382
|
+
|
|
383
|
+
if (!active || seconds === 0) return "";
|
|
384
|
+
if (seconds < 60) return `${seconds}s`;
|
|
385
|
+
return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
386
|
+
}
|
|
387
|
+
|
|
324
388
|
export function useThinkingPhrase(
|
|
325
389
|
active: boolean,
|
|
326
390
|
kind: ThinkingKind = "general",
|