@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.
Files changed (96) hide show
  1. package/dist/index.mjs +105368 -274002
  2. package/package.json +13 -19
  3. package/src/colors.ts +15 -15
  4. package/src/commands/chat.tsx +32 -23
  5. package/src/commands/provider.tsx +11 -238
  6. package/src/commands/repo.tsx +66 -120
  7. package/src/commands/timeline.tsx +11 -22
  8. package/src/components/ChatView.tsx +238 -0
  9. package/src/components/Message.tsx +46 -0
  10. package/src/components/ToolCall.tsx +67 -0
  11. package/src/components/chat/ChatView.tsx +550 -0
  12. package/src/components/chat/Message.tsx +152 -0
  13. package/src/components/chat/StatusBar.tsx +214 -0
  14. package/src/components/chat/TextArea.tsx +173 -176
  15. package/src/components/provider/ApiKeyStep.tsx +207 -199
  16. package/src/components/provider/ModelStep.tsx +90 -88
  17. package/src/components/provider/ProviderSetup.tsx +331 -0
  18. package/src/components/provider/ProviderTypeStep.tsx +53 -61
  19. package/src/components/repo/StepRow.tsx +68 -69
  20. package/src/components/timeline/TimelineView.tsx +840 -0
  21. package/src/components/toolcall-utils.ts +103 -0
  22. package/src/components/watch/RunView.tsx +497 -0
  23. package/src/hooks/useChatInput.ts +49 -0
  24. package/src/hooks/useCommandHandler.ts +117 -0
  25. package/src/index.tsx +386 -139
  26. package/src/utils/git.ts +149 -155
  27. package/src/utils/repo.ts +62 -69
  28. package/src/utils/thinking.tsx +64 -0
  29. package/src/utils/watch.ts +165 -307
  30. package/tests/message.test.ts +38 -0
  31. package/tests/toolcall-utils.test.ts +111 -0
  32. package/tsconfig.json +8 -24
  33. package/CLAUDE.md +0 -50
  34. package/LENS.md +0 -48
  35. package/LICENSE +0 -21
  36. package/README.md +0 -93
  37. package/addons/README.md +0 -55
  38. package/addons/clean-cache.js +0 -48
  39. package/addons/generate-readme.js +0 -67
  40. package/addons/git-stats.js +0 -29
  41. package/addons/run-tests.js +0 -127
  42. package/src/commands/commit.tsx +0 -668
  43. package/src/commands/review.tsx +0 -294
  44. package/src/commands/run.tsx +0 -56
  45. package/src/commands/task.tsx +0 -36
  46. package/src/components/chat/ChatMessage.tsx +0 -195
  47. package/src/components/chat/ChatOverlays.tsx +0 -399
  48. package/src/components/chat/ChatRunner.tsx +0 -517
  49. package/src/components/chat/hooks/useChat.ts +0 -631
  50. package/src/components/chat/hooks/useChatInput.ts +0 -79
  51. package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
  52. package/src/components/provider/ProviderPicker.tsx +0 -76
  53. package/src/components/provider/RemoveProviderStep.tsx +0 -82
  54. package/src/components/repo/DiffViewer.tsx +0 -175
  55. package/src/components/repo/FileReviewer.tsx +0 -70
  56. package/src/components/repo/FileViewer.tsx +0 -60
  57. package/src/components/repo/IssueFixer.tsx +0 -666
  58. package/src/components/repo/LensFileMenu.tsx +0 -115
  59. package/src/components/repo/NoProviderPrompt.tsx +0 -28
  60. package/src/components/repo/PreviewRunner.tsx +0 -217
  61. package/src/components/repo/RepoAnalysis.tsx +0 -534
  62. package/src/components/task/TaskRunner.tsx +0 -396
  63. package/src/components/timeline/CommitDetail.tsx +0 -272
  64. package/src/components/timeline/CommitList.tsx +0 -162
  65. package/src/components/timeline/TimelineChat.tsx +0 -166
  66. package/src/components/timeline/TimelineRunner.tsx +0 -1285
  67. package/src/components/watch/RunRunner.tsx +0 -929
  68. package/src/prompts/fewshot.ts +0 -252
  69. package/src/prompts/index.ts +0 -2
  70. package/src/prompts/system.ts +0 -285
  71. package/src/tools/chart.ts +0 -202
  72. package/src/tools/convert-image.ts +0 -312
  73. package/src/tools/files.ts +0 -253
  74. package/src/tools/git.ts +0 -603
  75. package/src/tools/index.ts +0 -17
  76. package/src/tools/pdf.ts +0 -164
  77. package/src/tools/shell.ts +0 -96
  78. package/src/tools/view-image.ts +0 -335
  79. package/src/tools/web.ts +0 -212
  80. package/src/types/chat.ts +0 -86
  81. package/src/types/config.ts +0 -20
  82. package/src/types/repo.ts +0 -54
  83. package/src/utils/addons/loadAddons.ts +0 -34
  84. package/src/utils/ai.ts +0 -321
  85. package/src/utils/chat.ts +0 -326
  86. package/src/utils/chatHistory.ts +0 -121
  87. package/src/utils/config.ts +0 -61
  88. package/src/utils/files.ts +0 -105
  89. package/src/utils/intentClassifier.ts +0 -58
  90. package/src/utils/lensfile.ts +0 -142
  91. package/src/utils/llm.ts +0 -81
  92. package/src/utils/memory.ts +0 -209
  93. package/src/utils/preview.ts +0 -119
  94. package/src/utils/stats.ts +0 -174
  95. package/src/utils/tools/builtins.ts +0 -377
  96. package/src/utils/tools/registry.ts +0 -105
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.3.7",
4
- "description": "Know Your Codebase.",
3
+ "version": "0.3.9",
4
+ "description": "Understand your codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -13,33 +13,27 @@
13
13
  "lens": "./dist/index.mjs"
14
14
  },
15
15
  "scripts": {
16
- "build": "bun build src/index.tsx --target node --outfile dist/index.mjs",
16
+ "dev": "bun src/index.tsx",
17
+ "build": "bun build src/index.tsx --target node --outfile dist/index.mjs --external react-devtools-core",
17
18
  "postbuild": "node -e \"const fs=require('fs');const f='dist/index.mjs';fs.writeFileSync(f,'#!/usr/bin/env node\\n'+fs.readFileSync(f,'utf8'))\"",
18
- "tag": "node -e \"const v=require('./package.json').version;require('child_process').execSync('git tag v'+v+' && git push origin v'+v,{stdio:'inherit'})\"",
19
- "prepublishOnly": "npm run build && npm run tag"
19
+ "prepublishOnly": "bun run build",
20
+ "test": "bun test"
20
21
  },
21
22
  "dependencies": {
22
- "@ridit/lens-sdk": "^0.2.0",
23
- "asciichart": "^1.5.25",
24
- "bun": "^1.3.11",
23
+ "@ridit/ink-ui": "*",
24
+ "@ridit/lens-core": "*",
25
+ "chalk": "^5.6.2",
25
26
  "commander": "^14.0.3",
26
27
  "figures": "^6.1.0",
27
- "ink": "^6.8.0",
28
+ "ink": "4.4.1",
28
29
  "ink-spinner": "^5.0.0",
29
30
  "ink-text-input": "^6.0.0",
30
- "nanoid": "^5.1.6",
31
- "pdfkit": "^0.18.0",
32
- "react": "^19.2.4",
33
- "react-devtools-core": "^7.0.1",
34
- "sugar-high": "^0.9.5",
35
- "terminal-image": "^4.2.0"
31
+ "react": "18.2.0"
36
32
  },
37
33
  "devDependencies": {
38
- "@types/asciichart": "^1.5.8",
34
+ "@types/react": "^18.0.0",
39
35
  "@types/bun": "latest",
40
- "@types/jest": "^30.0.0",
41
- "@types/pdfkit": "^0.17.5",
42
- "@types/react": "^19.2.14"
36
+ "@ridit/typescript-config": "*"
43
37
  },
44
38
  "peerDependencies": {
45
39
  "typescript": "^5"
package/src/colors.ts CHANGED
@@ -1,15 +1,15 @@
1
- export const ACCENT = "#DA7758";
2
- export const HEADING = "#FFFFFF";
3
- export const TEXT = "#E8E8E8";
4
- export const RED = "#E06C75";
5
- export const GREEN = "#85C98A";
6
- export const CYAN = "#79C5D4";
7
-
8
- export const TOKEN_KEYWORD = "#B385C9";
9
- export const TOKEN_STRING = "#85C98A";
10
- export const TOKEN_NUMBER = "#E8C170";
11
- export const TOKEN_PROPERTY = "#79C5D4";
12
- export const TOKEN_ENTITY = "#7AABDB";
13
- export const TOKEN_TEXT = "#E8E8E8";
14
- export const TOKEN_MUTED = "#888888";
15
- export const TOKEN_COMMENT = "#777777";
1
+ export const ACCENT = "#DA7758";
2
+ export const HEADING = "#FFFFFF";
3
+ export const TEXT = "#E8E8E8";
4
+ export const RED = "#E06C75";
5
+ export const GREEN = "#85C98A";
6
+ export const CYAN = "#79C5D4";
7
+
8
+ export const TOKEN_KEYWORD = "#B385C9";
9
+ export const TOKEN_STRING = "#85C98A";
10
+ export const TOKEN_NUMBER = "#E8C170";
11
+ export const TOKEN_PROPERTY = "#79C5D4";
12
+ export const TOKEN_ENTITY = "#7AABDB";
13
+ export const TOKEN_TEXT = "#E8E8E8";
14
+ export const TOKEN_MUTED = "#888888";
15
+ export const TOKEN_COMMENT = "#777777";
@@ -1,23 +1,32 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import figures from "figures";
4
- import { existsSync } from "fs";
5
- import path from "path";
6
- import { ChatRunner } from "../components/chat/ChatRunner";
7
- import { ACCENT } from "../colors";
8
-
9
- export const ChatCommand = ({ path: inputPath }: { path: string }) => {
10
- const resolvedPath = path.resolve(inputPath);
11
-
12
- if (!existsSync(resolvedPath)) {
13
- return (
14
- <Box marginTop={1}>
15
- <Text color="red">
16
- {figures.cross} Path not found: {resolvedPath}
17
- </Text>
18
- </Box>
19
- );
20
- }
21
-
22
- return <ChatRunner repoPath={resolvedPath} />;
23
- };
1
+ import React from "react";
2
+ import { Box } from "ink";
3
+ import { ChatRunner } from "../components/chat/ChatView";
4
+
5
+ export function ChatCommand({
6
+ path,
7
+ autoForce = false,
8
+ initialMessage,
9
+ dev = false,
10
+ single = false,
11
+ sessionId,
12
+ }: {
13
+ path: string;
14
+ autoForce?: boolean;
15
+ initialMessage?: string;
16
+ dev?: boolean;
17
+ single?: boolean;
18
+ sessionId?: string;
19
+ }) {
20
+ return (
21
+ <Box flexDirection="column">
22
+ <ChatRunner
23
+ repoPath={path}
24
+ autoForce={autoForce}
25
+ initialMessage={initialMessage}
26
+ dev={dev}
27
+ single={single}
28
+ sessionId={sessionId}
29
+ />
30
+ </Box>
31
+ );
32
+ }
@@ -1,238 +1,11 @@
1
- import { Box, Text, useInput } from "ink";
2
- import figures from "figures";
3
- import { useState } from "react";
4
- import { nanoid } from "nanoid";
5
- import { addProvider, loadConfig } from "../utils/config";
6
- import { ProviderTypeStep } from "../components/provider/ProviderTypeStep";
7
- import { ApiKeyStep } from "../components/provider/ApiKeyStep";
8
- import { ModelStep } from "../components/provider/ModelStep";
9
- import { RemoveProviderStep } from "../components/provider/RemoveProviderStep";
10
- import type { Provider, ProviderType } from "../types/config";
11
- import { ACCENT, CYAN, GREEN, TEXT } from "../colors";
12
-
13
- type InitStage =
14
- | { type: "menu" }
15
- | { type: "provider-type" }
16
- | { type: "api-key"; providerType: ProviderType }
17
- | { type: "base-url"; providerType: ProviderType; apiKey: string }
18
- | {
19
- type: "model";
20
- providerType: ProviderType;
21
- apiKey: string;
22
- baseUrl?: string;
23
- }
24
- | { type: "remove" }
25
- | { type: "done"; provider: Provider };
26
-
27
- const MENU_OPTIONS = [
28
- { label: "Add a provider", action: "provider-type" },
29
- { label: "Remove a provider", action: "remove" },
30
- ] as const;
31
-
32
- export const InitCommand = () => {
33
- const [stage, setStage] = useState<InitStage>({ type: "menu" });
34
- const [completedSteps, setCompletedSteps] = useState<string[]>([]);
35
- const [menuIndex, setMenuIndex] = useState(0);
36
-
37
- const pushStep = (label: string) => setCompletedSteps((s) => [...s, label]);
38
- const popStep = () => setCompletedSteps((s) => s.slice(0, -1));
39
- const popSteps = (n: number) => setCompletedSteps((s) => s.slice(0, -n));
40
-
41
- useInput((input, key) => {
42
- if (stage.type !== "menu") return;
43
- if (key.upArrow) setMenuIndex((i) => Math.max(0, i - 1));
44
- if (key.downArrow)
45
- setMenuIndex((i) => Math.min(MENU_OPTIONS.length - 1, i + 1));
46
- if (key.return) {
47
- const action = MENU_OPTIONS[menuIndex]?.action;
48
- if (action === "provider-type") setStage({ type: "provider-type" });
49
- if (action === "remove") setStage({ type: "remove" });
50
- }
51
- });
52
-
53
- if (stage.type === "menu") {
54
- const config = loadConfig();
55
- return (
56
- <Box flexDirection="column" gap={1}>
57
- {completedSteps.map((s, i) => (
58
- <Text key={i} color={GREEN}>
59
- {figures.arrowRight} {s}
60
- </Text>
61
- ))}
62
- {config.providers.length > 0 && (
63
- <Text color="gray">
64
- {config.providers.length} provider(s) configured
65
- </Text>
66
- )}
67
- {MENU_OPTIONS.map((opt, i) => (
68
- <Box key={opt.action} marginLeft={1}>
69
- <Text color={i === menuIndex ? ACCENT : "white"}>
70
- {i === menuIndex ? figures.arrowRight : " "}
71
- {" "}
72
- {opt.label}
73
- </Text>
74
- </Box>
75
- ))}
76
- <Text color="gray">↑↓ navigate · enter to select</Text>
77
- </Box>
78
- );
79
- }
80
-
81
- if (stage.type === "remove") {
82
- return (
83
- <Box flexDirection="column" gap={1}>
84
- {completedSteps.map((s, i) => (
85
- <Text key={i} color={GREEN}>
86
- {figures.arrowRight} {s}
87
- </Text>
88
- ))}
89
- <RemoveProviderStep onDone={() => setStage({ type: "menu" })} />
90
- </Box>
91
- );
92
- }
93
-
94
- if (stage.type === "provider-type") {
95
- return (
96
- <Box flexDirection="column" gap={1}>
97
- {completedSteps.map((s, i) => (
98
- <Text key={i} color={GREEN}>
99
- {figures.arrowRight} {s}
100
- </Text>
101
- ))}
102
- <ProviderTypeStep
103
- onSelect={(providerType) => {
104
- pushStep(`Provider: ${providerType}`);
105
- setStage({ type: "api-key", providerType });
106
- }}
107
- onBack={() => setStage({ type: "menu" })}
108
- />
109
- </Box>
110
- );
111
- }
112
-
113
- if (stage.type === "api-key") {
114
- return (
115
- <Box flexDirection="column" gap={1}>
116
- {completedSteps.map((s, i) => (
117
- <Text key={i} color={GREEN}>
118
- {figures.arrowRight} {s}
119
- </Text>
120
- ))}
121
- <ApiKeyStep
122
- providerType={stage.providerType}
123
- onSubmit={(value) => {
124
- if (stage.providerType === "custom") {
125
- const { apiKey, baseUrl } = value as {
126
- apiKey: string;
127
- baseUrl?: string;
128
- };
129
- pushStep("API key saved");
130
- if (baseUrl) pushStep(`Base URL: ${baseUrl}`);
131
- setStage({
132
- type: "model",
133
- providerType: stage.providerType,
134
- apiKey,
135
- baseUrl,
136
- });
137
- } else if (stage.providerType === "ollama") {
138
- pushStep(`Base URL: ${value}`);
139
- setStage({
140
- type: "model",
141
- providerType: stage.providerType,
142
- apiKey: "",
143
- baseUrl: value as string,
144
- });
145
- } else {
146
- pushStep("API key saved");
147
- setStage({
148
- type: "model",
149
- providerType: stage.providerType,
150
- apiKey: value as string,
151
- });
152
- }
153
- }}
154
- onBack={() => {
155
- popStep();
156
- setStage({ type: "provider-type" });
157
- }}
158
- />
159
- </Box>
160
- );
161
- }
162
-
163
- if (stage.type === "base-url") {
164
- return (
165
- <Box flexDirection="column" gap={1}>
166
- {completedSteps.map((s, i) => (
167
- <Text key={i} color={GREEN}>
168
- {figures.arrowRight} {s}
169
- </Text>
170
- ))}
171
- <ApiKeyStep
172
- providerType="ollama"
173
- onSubmit={(baseUrl) => {
174
- pushStep(`Base URL: ${baseUrl}`);
175
- setStage({
176
- type: "model",
177
- providerType: stage.providerType,
178
- apiKey: stage.apiKey,
179
- baseUrl: baseUrl as string,
180
- });
181
- }}
182
- onBack={() => {
183
- popStep();
184
- setStage({ type: "api-key", providerType: stage.providerType });
185
- }}
186
- />
187
- </Box>
188
- );
189
- }
190
-
191
- if (stage.type === "model") {
192
- return (
193
- <Box flexDirection="column" gap={1}>
194
- {completedSteps.map((s, i) => (
195
- <Text key={i} color={GREEN}>
196
- {figures.arrowRight} {s}
197
- </Text>
198
- ))}
199
- <ModelStep
200
- providerType={stage.providerType}
201
- onSelect={(model) => {
202
- const provider: Provider = {
203
- id: nanoid(8),
204
- type: stage.providerType,
205
- name: `${stage.providerType}-${model}`,
206
- apiKey: stage.apiKey || undefined,
207
- baseUrl: stage.baseUrl,
208
- model,
209
- };
210
- addProvider(provider);
211
- pushStep(`Model: ${model}`);
212
- setStage({ type: "done", provider });
213
- }}
214
- onBack={() => {
215
- popStep();
216
- setStage({ type: "api-key", providerType: stage.providerType });
217
- }}
218
- />
219
- </Box>
220
- );
221
- }
222
-
223
- return (
224
- <Box flexDirection="column" gap={1}>
225
- {completedSteps.map((s, i) => (
226
- <Text key={i} color={GREEN}>
227
- {figures.arrowRight} {s}
228
- </Text>
229
- ))}
230
- <Text color={GREEN}>
231
- {figures.arrowRight} Provider configured successfully
232
- </Text>
233
- <Text color="gray">
234
- Run <Text color={CYAN}>lens provider</Text> again to manage providers.
235
- </Text>
236
- </Box>
237
- );
238
- };
1
+ import React from "react";
2
+ import { Box } from "ink";
3
+ import { ProviderSetup } from "../components/provider/ProviderSetup";
4
+
5
+ export function ProviderCommand() {
6
+ return (
7
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
8
+ <ProviderSetup onDone={() => process.exit(0)} />
9
+ </Box>
10
+ );
11
+ }
@@ -1,120 +1,66 @@
1
- import { Box, Text, useInput } from "ink";
2
- import figures from "figures";
3
- import { useEffect, useState } from "react";
4
- import path from "path";
5
- import os from "os";
6
- import { startCloneRepo } from "../utils/repo";
7
- import { fetchFileTree, readImportantFiles } from "../utils/files";
8
- import { StepRow } from "../components/repo/StepRow";
9
- import { FileReviewer } from "../components/repo/FileReviewer";
10
- import { RepoAnalysis } from "../components/repo/RepoAnalysis";
11
- import type { Step, ImportantFile } from "../types/repo";
12
-
13
- export const RepoCommand = ({ url }: { url: string }) => {
14
- const [steps, setSteps] = useState<Step[]>([
15
- { type: "cloning", status: "pending" },
16
- ]);
17
- const [importantFiles, setImportantFiles] = useState<ImportantFile[]>([]);
18
- const [fileTree, setFileTree] = useState<string[]>([]);
19
- const [repoPath, setRepoPath] = useState<string>("");
20
- const [reviewDone, setReviewDone] = useState(false);
21
-
22
- const updateLastStep = (updated: Step) =>
23
- setSteps((prev) => [...prev.slice(0, -1), updated]);
24
-
25
- const pushStep = (step: Step) => setSteps((prev) => [...prev, step]);
26
-
27
- const handleCloneSuccess = (rPath: string) => {
28
- setRepoPath(rPath);
29
- updateLastStep({ type: "cloning", status: "done" });
30
- pushStep({ type: "fetching-tree", status: "pending" });
31
-
32
- fetchFileTree(rPath)
33
- .then((files) => {
34
- updateLastStep({ type: "fetching-tree", status: "done" });
35
- pushStep({ type: "reading-files", status: "pending" });
36
- setFileTree(files);
37
- const found = readImportantFiles(rPath, files);
38
- setImportantFiles(found);
39
- updateLastStep({ type: "reading-files", status: "done" });
40
- })
41
- .catch(() => updateLastStep({ type: "fetching-tree", status: "done" }));
42
- };
43
-
44
- useEffect(() => {
45
- startCloneRepo(url).then((result) => {
46
- if (result.done) {
47
- const repoName = path
48
- .basename(new URL(url).pathname)
49
- .replace(/\.git$/, "");
50
- handleCloneSuccess(path.join(os.tmpdir(), repoName));
51
- } else if (result.folderExists) {
52
- updateLastStep({
53
- type: "folder-exists",
54
- status: "pending",
55
- repoPath: result.repoPath,
56
- });
57
- } else {
58
- updateLastStep({
59
- type: "error",
60
- message: result.error ?? "Unknown error",
61
- });
62
- }
63
- });
64
- }, [url]);
65
-
66
- useInput((input) => {
67
- const last = steps[steps.length - 1];
68
- if (last?.type !== "folder-exists") return;
69
- const rPath = last.repoPath;
70
-
71
- if (input === "y" || input === "Y") {
72
- updateLastStep({ type: "cloning", status: "pending" });
73
- startCloneRepo(url, { forceReclone: true }).then((result) => {
74
- if (result.done) {
75
- handleCloneSuccess(rPath);
76
- } else if (!result.folderExists) {
77
- updateLastStep({
78
- type: "error",
79
- message: result.error ?? "Unknown error",
80
- });
81
- }
82
- });
83
- }
84
-
85
- if (input === "n" || input === "N") handleCloneSuccess(rPath);
86
- });
87
-
88
- const allDone =
89
- steps[steps.length - 1]?.type === "reading-files" &&
90
- (steps[steps.length - 1] as Extract<Step, { type: "reading-files" }>)
91
- .status === "done";
92
-
93
- return (
94
- <Box flexDirection="column">
95
- {steps.map((step, i) => (
96
- <StepRow key={i} step={step} />
97
- ))}
98
-
99
- {allDone && !reviewDone && importantFiles.length > 0 && (
100
- <FileReviewer
101
- files={importantFiles}
102
- onDone={() => setReviewDone(true)}
103
- />
104
- )}
105
-
106
- {allDone && importantFiles.length === 0 && !reviewDone && (
107
- <Text color="gray">{figures.info} No important files found</Text>
108
- )}
109
-
110
- {(reviewDone || (allDone && importantFiles.length === 0)) && (
111
- <RepoAnalysis
112
- repoUrl={url}
113
- repoPath={repoPath}
114
- fileTree={fileTree}
115
- files={importantFiles}
116
- />
117
- )}
118
- </Box>
119
- );
120
- };
1
+ import React, { useState, useEffect } from "react";
2
+ import { Box, useInput } from "ink";
3
+ import { StepRow, type Step } from "../components/repo/StepRow";
4
+ import { ChatRunner } from "../components/chat/ChatView";
5
+ import { startCloneRepo } from "../utils/repo";
6
+
7
+ export function RepoCommand({ url }: { url: string }) {
8
+ const [steps, setSteps] = useState<Step[]>([
9
+ { type: "cloning", status: "pending" },
10
+ ]);
11
+ const [repoPath, setRepoPath] = useState<string | null>(null);
12
+
13
+ const updateLastStep = (updated: Step) =>
14
+ setSteps((prev) => [...prev.slice(0, -1), updated]);
15
+
16
+ const pushStep = (step: Step) => setSteps((prev) => [...prev, step]);
17
+
18
+ const doClone = (forceReclone = false) => {
19
+ startCloneRepo(url, { forceReclone }).then((result) => {
20
+ if (result.done) {
21
+ updateLastStep({ type: "cloning", status: "done" });
22
+ setRepoPath(result.repoPath);
23
+ } else if (!result.done && result.folderExists) {
24
+ updateLastStep({
25
+ type: "folder-exists",
26
+ status: "pending",
27
+ repoPath: result.repoPath,
28
+ });
29
+ } else if (!result.done) {
30
+ updateLastStep({ type: "error", message: result.error ?? "Clone failed" });
31
+ }
32
+ });
33
+ };
34
+
35
+ useEffect(() => {
36
+ doClone();
37
+ }, [url]);
38
+
39
+ useInput((input) => {
40
+ const last = steps[steps.length - 1];
41
+ if (last?.type !== "folder-exists") return;
42
+ if (input === "y" || input === "Y") {
43
+ updateLastStep({ type: "cloning", status: "pending" });
44
+ doClone(true);
45
+ }
46
+ if (input === "n" || input === "N") {
47
+ updateLastStep({ type: "cloning", status: "done" });
48
+ setRepoPath(last.repoPath);
49
+ }
50
+ });
51
+
52
+ return (
53
+ <Box flexDirection="column">
54
+ {steps.map((step, i) => (
55
+ <StepRow key={i} step={step} />
56
+ ))}
57
+
58
+ {repoPath && (
59
+ <ChatRunner
60
+ repoPath={repoPath}
61
+ initialMessage={`I've cloned the repository from ${url}. Please analyze it — give me an overview of what it does, the tech stack, key architectural decisions, and anything interesting.`}
62
+ />
63
+ )}
64
+ </Box>
65
+ );
66
+ }
@@ -1,22 +1,11 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import figures from "figures";
4
- import { existsSync } from "fs";
5
- import path from "path";
6
- import { TimelineRunner } from "../components/timeline/TimelineRunner";
7
-
8
- export const TimelineCommand = ({ path: inputPath }: { path: string }) => {
9
- const resolvedPath = path.resolve(inputPath);
10
-
11
- if (!existsSync(resolvedPath)) {
12
- return (
13
- <Box marginTop={1}>
14
- <Text color="red">
15
- {figures.cross} Path not found: {resolvedPath}
16
- </Text>
17
- </Box>
18
- );
19
- }
20
-
21
- return <TimelineRunner repoPath={resolvedPath} />;
22
- };
1
+ import React from "react";
2
+ import { Box } from "ink";
3
+ import { TimelineRunner } from "../components/timeline/TimelineView";
4
+
5
+ export function TimelineCommand({ path }: { path: string }) {
6
+ return (
7
+ <Box flexDirection="column">
8
+ <TimelineRunner repoPath={path} />
9
+ </Box>
10
+ );
11
+ }