fraude-code 0.1.0
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/README.md +68 -0
- package/dist/index.js +179297 -0
- package/package.json +88 -0
- package/src/agent/agent.ts +475 -0
- package/src/agent/contextManager.ts +141 -0
- package/src/agent/index.ts +14 -0
- package/src/agent/pendingChanges.ts +270 -0
- package/src/agent/prompts/AskPrompt.txt +10 -0
- package/src/agent/prompts/FastPrompt.txt +40 -0
- package/src/agent/prompts/PlannerPrompt.txt +51 -0
- package/src/agent/prompts/ReviewerPrompt.txt +57 -0
- package/src/agent/prompts/WorkerPrompt.txt +33 -0
- package/src/agent/subagents/askAgent.ts +37 -0
- package/src/agent/subagents/extractionAgent.ts +123 -0
- package/src/agent/subagents/fastAgent.ts +45 -0
- package/src/agent/subagents/managerAgent.ts +36 -0
- package/src/agent/subagents/relationAgent.ts +76 -0
- package/src/agent/subagents/researchSubAgent.ts +79 -0
- package/src/agent/subagents/reviewerSubAgent.ts +42 -0
- package/src/agent/subagents/workerSubAgent.ts +42 -0
- package/src/agent/tools/bashTool.ts +94 -0
- package/src/agent/tools/descriptions/bash.txt +47 -0
- package/src/agent/tools/descriptions/edit.txt +7 -0
- package/src/agent/tools/descriptions/glob.txt +4 -0
- package/src/agent/tools/descriptions/grep.txt +8 -0
- package/src/agent/tools/descriptions/lsp.txt +20 -0
- package/src/agent/tools/descriptions/plan.txt +3 -0
- package/src/agent/tools/descriptions/read.txt +9 -0
- package/src/agent/tools/descriptions/todo.txt +12 -0
- package/src/agent/tools/descriptions/write.txt +8 -0
- package/src/agent/tools/editTool.ts +44 -0
- package/src/agent/tools/globTool.ts +59 -0
- package/src/agent/tools/grepTool.ts +343 -0
- package/src/agent/tools/lspTool.ts +429 -0
- package/src/agent/tools/planTool.ts +118 -0
- package/src/agent/tools/readTool.ts +78 -0
- package/src/agent/tools/rememberTool.ts +91 -0
- package/src/agent/tools/testRunnerTool.ts +77 -0
- package/src/agent/tools/testTool.ts +44 -0
- package/src/agent/tools/todoTool.ts +224 -0
- package/src/agent/tools/writeTool.ts +33 -0
- package/src/commands/COMMANDS.ts +38 -0
- package/src/commands/cerebras/auth.ts +27 -0
- package/src/commands/cerebras/index.ts +31 -0
- package/src/commands/forget.ts +29 -0
- package/src/commands/google/auth.ts +24 -0
- package/src/commands/google/index.ts +31 -0
- package/src/commands/groq/add_model.ts +60 -0
- package/src/commands/groq/auth.ts +24 -0
- package/src/commands/groq/index.ts +33 -0
- package/src/commands/index.ts +65 -0
- package/src/commands/knowledge.ts +92 -0
- package/src/commands/log.ts +32 -0
- package/src/commands/mistral/auth.ts +27 -0
- package/src/commands/mistral/index.ts +31 -0
- package/src/commands/model/index.ts +145 -0
- package/src/commands/models/index.ts +16 -0
- package/src/commands/ollama/index.ts +29 -0
- package/src/commands/openrouter/add_model.ts +64 -0
- package/src/commands/openrouter/auth.ts +24 -0
- package/src/commands/openrouter/index.ts +33 -0
- package/src/commands/remember.ts +48 -0
- package/src/commands/serve.ts +31 -0
- package/src/commands/session/index.ts +21 -0
- package/src/commands/usage.ts +15 -0
- package/src/commands/visualize.ts +773 -0
- package/src/components/App.tsx +55 -0
- package/src/components/IntroComponent.tsx +70 -0
- package/src/components/LoaderComponent.tsx +68 -0
- package/src/components/OutputRenderer.tsx +88 -0
- package/src/components/SettingsRenderer.tsx +23 -0
- package/src/components/input/CommandSuggestions.tsx +41 -0
- package/src/components/input/FileSuggestions.tsx +61 -0
- package/src/components/input/InputBox.tsx +371 -0
- package/src/components/output/CheckpointView.tsx +13 -0
- package/src/components/output/CommandView.tsx +13 -0
- package/src/components/output/CommentView.tsx +12 -0
- package/src/components/output/ConfirmationView.tsx +179 -0
- package/src/components/output/ContextUsage.tsx +62 -0
- package/src/components/output/DiffView.tsx +202 -0
- package/src/components/output/ErrorView.tsx +14 -0
- package/src/components/output/InteractiveServerView.tsx +69 -0
- package/src/components/output/KnowledgeView.tsx +220 -0
- package/src/components/output/MarkdownView.tsx +15 -0
- package/src/components/output/ModelSelectView.tsx +71 -0
- package/src/components/output/ReasoningView.tsx +21 -0
- package/src/components/output/ToolCallView.tsx +45 -0
- package/src/components/settings/ModelList.tsx +250 -0
- package/src/components/settings/TokenUsage.tsx +274 -0
- package/src/config/schema.ts +19 -0
- package/src/config/settings.ts +229 -0
- package/src/index.tsx +100 -0
- package/src/parsers/tree-sitter-python.wasm +0 -0
- package/src/providers/providers.ts +71 -0
- package/src/services/PluginLoader.ts +123 -0
- package/src/services/cerebras.ts +69 -0
- package/src/services/embeddingService.ts +229 -0
- package/src/services/google.ts +65 -0
- package/src/services/graphSerializer.ts +248 -0
- package/src/services/groq.ts +23 -0
- package/src/services/knowledgeOrchestrator.ts +286 -0
- package/src/services/mistral.ts +79 -0
- package/src/services/ollama.ts +109 -0
- package/src/services/openrouter.ts +23 -0
- package/src/services/symbolExtractor.ts +277 -0
- package/src/store/useFraudeStore.ts +123 -0
- package/src/store/useSettingsStore.ts +38 -0
- package/src/theme.ts +26 -0
- package/src/types/Agent.ts +147 -0
- package/src/types/CommandDefinition.ts +8 -0
- package/src/types/Model.ts +94 -0
- package/src/types/OutputItem.ts +24 -0
- package/src/types/PluginContext.ts +55 -0
- package/src/types/TokenUsage.ts +5 -0
- package/src/types/assets.d.ts +4 -0
- package/src/utils/agentCognition.ts +1152 -0
- package/src/utils/fileSuggestions.ts +111 -0
- package/src/utils/index.ts +17 -0
- package/src/utils/initFraude.ts +8 -0
- package/src/utils/logger.ts +24 -0
- package/src/utils/lspClient.ts +1415 -0
- package/src/utils/paths.ts +24 -0
- package/src/utils/queryHandler.ts +227 -0
- package/src/utils/router.ts +278 -0
- package/src/utils/streamHandler.ts +132 -0
- package/src/utils/treeSitterQueries.ts +125 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import log from "../utils/logger";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, useInput, Text } from "ink";
|
|
4
|
+
import InputBox from "./input/InputBox";
|
|
5
|
+
import OutputRenderer from "./OutputRenderer";
|
|
6
|
+
import useFraudeStore from "../store/useFraudeStore";
|
|
7
|
+
import IntroComponent from "./IntroComponent";
|
|
8
|
+
import LoaderComponent from "./LoaderComponent";
|
|
9
|
+
import { THEME } from "../theme";
|
|
10
|
+
|
|
11
|
+
export default function App() {
|
|
12
|
+
const { status, started, updateOutput } = useFraudeStore();
|
|
13
|
+
const [exitPending, setExitPending] = useState(false);
|
|
14
|
+
|
|
15
|
+
useInput((input, key) => {
|
|
16
|
+
if (key.return && !started) {
|
|
17
|
+
useFraudeStore.setState({ started: true });
|
|
18
|
+
log("App Started...");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (input === "c" && key.ctrl) {
|
|
22
|
+
if (exitPending) {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} else {
|
|
25
|
+
setExitPending(true);
|
|
26
|
+
setTimeout(() => setExitPending(false), 2000);
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle escape key to interrupt the agent
|
|
32
|
+
if (key.escape && status === 1) {
|
|
33
|
+
useFraudeStore.getState().interruptAgent();
|
|
34
|
+
log("User pressed escape - interrupting agent");
|
|
35
|
+
updateOutput(
|
|
36
|
+
"interrupted",
|
|
37
|
+
(useFraudeStore.getState().elapsedTime / 10).toFixed(1),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return !started ? (
|
|
42
|
+
<IntroComponent />
|
|
43
|
+
) : (
|
|
44
|
+
<Box flexDirection="column" paddingX={2} paddingY={1}>
|
|
45
|
+
<OutputRenderer />
|
|
46
|
+
{status === 0 && <InputBox />}
|
|
47
|
+
{status === 1 && <LoaderComponent />}
|
|
48
|
+
{exitPending && (
|
|
49
|
+
<Box marginTop={1}>
|
|
50
|
+
<Text color={THEME.error}>Press Ctrl+C again to exit</Text>
|
|
51
|
+
</Box>
|
|
52
|
+
)}
|
|
53
|
+
</Box>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import Gradient from "ink-gradient";
|
|
3
|
+
|
|
4
|
+
const INTRO_THEME = {
|
|
5
|
+
primary: "#FFB6C1",
|
|
6
|
+
primaryLight: "#FFDDE2",
|
|
7
|
+
primaryDim: "#B07D85",
|
|
8
|
+
dim: "#666666",
|
|
9
|
+
gradient: [
|
|
10
|
+
{ r: 255, g: 105, b: 180 }, // pink
|
|
11
|
+
{ r: 255, g: 120, b: 160 },
|
|
12
|
+
{ r: 255, g: 135, b: 145 },
|
|
13
|
+
{ r: 255, g: 150, b: 130 },
|
|
14
|
+
{ r: 255, g: 165, b: 115 },
|
|
15
|
+
{ r: 255, g: 180, b: 100 },
|
|
16
|
+
{ r: 255, g: 195, b: 85 },
|
|
17
|
+
{ r: 255, g: 210, b: 70 },
|
|
18
|
+
{ r: 255, g: 225, b: 55 },
|
|
19
|
+
{ r: 240, g: 140, b: 0 }, // orange
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const LOGO_FRAUDE = `
|
|
24
|
+
███████╗██████╗ █████╗ ██╗ ██╗██████╗ ███████╗
|
|
25
|
+
██╔════╝██╔══██╗██╔══██╗██║ ██║██╔══██╗██╔════╝
|
|
26
|
+
█████╗ ██████╔╝███████║██║ ██║██║ ██║█████╗
|
|
27
|
+
██╔══╝ ██╔══██╗██╔══██║██║ ██║██║ ██║██╔══╝
|
|
28
|
+
██║ ██║ ██║██║ ██║╚██████╔╝██████╔╝███████╗
|
|
29
|
+
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const LOGO_CODE = `
|
|
33
|
+
██████╗ ██████╗ ██████╗ ███████╗
|
|
34
|
+
██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
35
|
+
██║ ██║ ██║██║ ██║█████╗
|
|
36
|
+
██║ ██║ ██║██║ ██║██╔══╝
|
|
37
|
+
╚██████╗╚██████╔╝██████╔╝███████╗
|
|
38
|
+
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export default function IntroComponent() {
|
|
42
|
+
return (
|
|
43
|
+
<Box flexDirection="column">
|
|
44
|
+
<Box paddingX={1} marginBottom={1}>
|
|
45
|
+
<Text color={INTRO_THEME.primaryDim}>
|
|
46
|
+
FRAUDE CODE <Text color={INTRO_THEME.dim}>·</Text>{" "}
|
|
47
|
+
<Text italic>Agentic AI Assistant</Text>
|
|
48
|
+
</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
|
|
51
|
+
<Gradient colors={INTRO_THEME.gradient}>
|
|
52
|
+
<Text bold>{LOGO_FRAUDE}</Text>
|
|
53
|
+
<Text bold>{LOGO_CODE}</Text>
|
|
54
|
+
</Gradient>
|
|
55
|
+
|
|
56
|
+
<Box flexDirection="column" marginTop={1}>
|
|
57
|
+
<Text color={INTRO_THEME.dim}>Ready to build something amazing?</Text>
|
|
58
|
+
<Box marginTop={1}>
|
|
59
|
+
<Text color={INTRO_THEME.dim}>
|
|
60
|
+
Press{" "}
|
|
61
|
+
<Text bold color={INTRO_THEME.primaryLight}>
|
|
62
|
+
Enter
|
|
63
|
+
</Text>{" "}
|
|
64
|
+
to start your journey...
|
|
65
|
+
</Text>
|
|
66
|
+
</Box>
|
|
67
|
+
</Box>
|
|
68
|
+
</Box>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import useFraudeStore from "../store/useFraudeStore";
|
|
4
|
+
import type { TokenUsage } from "@/types/TokenUsage";
|
|
5
|
+
import { THEME } from "../theme";
|
|
6
|
+
|
|
7
|
+
const LoaderComponent = () => {
|
|
8
|
+
const [i, setFrame] = useState(0);
|
|
9
|
+
const getFrames = (text: string) => [
|
|
10
|
+
`· ${text}. `,
|
|
11
|
+
`• ${text}.. `,
|
|
12
|
+
`● ${text}...`,
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const { status, elapsedTime, statusText } = useFraudeStore();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
let timer: NodeJS.Timeout | null = null;
|
|
19
|
+
|
|
20
|
+
if (status === 1) {
|
|
21
|
+
const frameCount = getFrames("").length;
|
|
22
|
+
timer = setInterval(() => {
|
|
23
|
+
setFrame((prevIndex) => (prevIndex + 1) % frameCount);
|
|
24
|
+
|
|
25
|
+
const currentElapsed = useFraudeStore.getState().elapsedTime;
|
|
26
|
+
useFraudeStore.setState({
|
|
27
|
+
elapsedTime: currentElapsed + 1,
|
|
28
|
+
});
|
|
29
|
+
}, 80);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
if (timer) {
|
|
34
|
+
clearInterval(timer);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}, [status]);
|
|
38
|
+
|
|
39
|
+
const currentStatusText = statusText || "Pondering";
|
|
40
|
+
const currentFrames = getFrames(currentStatusText);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Box marginY={0}>
|
|
44
|
+
<Text color={THEME.primary}>{currentFrames[i]} </Text>
|
|
45
|
+
<Box paddingLeft={1}>
|
|
46
|
+
<Text color={THEME.dim}>
|
|
47
|
+
({(elapsedTime / 10).toFixed(1)}s · ESC to interrupt)
|
|
48
|
+
</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// {status === 2 && (
|
|
55
|
+
// <Text dimColor>
|
|
56
|
+
// Finished ({(elapsed / 10).toFixed(1)}s ※ {tokenUsage.total} tokens)
|
|
57
|
+
// </Text>
|
|
58
|
+
// )}
|
|
59
|
+
// {status === 3 && (
|
|
60
|
+
// <Text color="yellow">
|
|
61
|
+
// ▶ Awaiting user confirmation... ({(elapsed / 10).toFixed(1)}s)
|
|
62
|
+
// </Text>
|
|
63
|
+
// )}
|
|
64
|
+
// {status === -1 && (
|
|
65
|
+
// <Text dimColor>Interrupted ({(elapsed / 10).toFixed(1)}s)</Text>
|
|
66
|
+
// )}
|
|
67
|
+
|
|
68
|
+
export default LoaderComponent;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { type OutputItem } from "@/types/OutputItem";
|
|
4
|
+
import useFraudeStore from "../store/useFraudeStore";
|
|
5
|
+
import CommandView from "./output/CommandView";
|
|
6
|
+
import CommentView from "./output/CommentView";
|
|
7
|
+
import MarkdownView from "./output/MarkdownView";
|
|
8
|
+
import SettingsRenderer from "./SettingsRenderer";
|
|
9
|
+
import ErrorView from "./output/ErrorView";
|
|
10
|
+
import ReasoningView from "./output/ReasoningView";
|
|
11
|
+
import ToolCallView from "./output/ToolCallView";
|
|
12
|
+
import DiffView from "./output/DiffView";
|
|
13
|
+
import ConfirmationView from "./output/ConfirmationView";
|
|
14
|
+
import ModelSelectView from "./output/ModelSelectView";
|
|
15
|
+
import KnowledgeView, { type KnowledgeViewData } from "./output/KnowledgeView";
|
|
16
|
+
import { THEME } from "../theme";
|
|
17
|
+
|
|
18
|
+
import InteractiveServerView from "./output/InteractiveServerView";
|
|
19
|
+
|
|
20
|
+
function renderItem(item: OutputItem) {
|
|
21
|
+
switch (item.type) {
|
|
22
|
+
case "log":
|
|
23
|
+
return <Text>{item.content}</Text>;
|
|
24
|
+
case "interrupted":
|
|
25
|
+
return <Text dimColor>Interrupted ({item.content}s)</Text>;
|
|
26
|
+
case "done":
|
|
27
|
+
return <Text color={THEME.primaryDim}>{item.content}</Text>;
|
|
28
|
+
case "error":
|
|
29
|
+
return <ErrorView error={item.content} />;
|
|
30
|
+
case "command":
|
|
31
|
+
return <CommandView command={item.content} />;
|
|
32
|
+
case "comment":
|
|
33
|
+
return <CommentView comment={item.content} />;
|
|
34
|
+
case "agentText":
|
|
35
|
+
case "markdown":
|
|
36
|
+
return <MarkdownView markdown={item.content} />;
|
|
37
|
+
case "settings":
|
|
38
|
+
return <SettingsRenderer item={item} />;
|
|
39
|
+
case "reasoning":
|
|
40
|
+
return <ReasoningView content={item.content} duration={item.duration} />;
|
|
41
|
+
case "toolCall": {
|
|
42
|
+
try {
|
|
43
|
+
const data = JSON.parse(item.content);
|
|
44
|
+
return (
|
|
45
|
+
<ToolCallView
|
|
46
|
+
action={data.action}
|
|
47
|
+
details={data.details}
|
|
48
|
+
result={data.result}
|
|
49
|
+
duration={data.duration}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return <Text dimColor>{item.content}</Text>;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
case "diff":
|
|
57
|
+
return <DiffView diff={item.content} />;
|
|
58
|
+
case "confirmation":
|
|
59
|
+
return <ConfirmationView />;
|
|
60
|
+
case "modelSelect":
|
|
61
|
+
return <ModelSelectView />;
|
|
62
|
+
case "interactive-server":
|
|
63
|
+
return <InteractiveServerView routerId={item.content} />;
|
|
64
|
+
case "knowledge": {
|
|
65
|
+
try {
|
|
66
|
+
const data = JSON.parse(item.content) as KnowledgeViewData;
|
|
67
|
+
return <KnowledgeView data={data} />;
|
|
68
|
+
} catch {
|
|
69
|
+
return <Text dimColor>{item.content}</Text>;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default memo(function OutputRenderer() {
|
|
78
|
+
const outputItems = useFraudeStore((state) => state.outputItems);
|
|
79
|
+
return (
|
|
80
|
+
<Box flexDirection="column">
|
|
81
|
+
{outputItems.map((item: OutputItem) => (
|
|
82
|
+
<Box key={item.id} marginBottom={1}>
|
|
83
|
+
{renderItem(item)}
|
|
84
|
+
</Box>
|
|
85
|
+
))}
|
|
86
|
+
</Box>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import { type OutputItem } from "@/types/OutputItem";
|
|
3
|
+
import ModelList from "./settings/ModelList";
|
|
4
|
+
import ContextUsage from "./output/ContextUsage";
|
|
5
|
+
import TokenUsage from "./settings/TokenUsage";
|
|
6
|
+
|
|
7
|
+
export default memo(function SettingsRenderer({ item }: { item: OutputItem }) {
|
|
8
|
+
const content = item.content;
|
|
9
|
+
|
|
10
|
+
if (content.startsWith("/models")) {
|
|
11
|
+
const provider = content.split(":")[1];
|
|
12
|
+
return <ModelList providerFilter={provider} showAll={!!provider} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch (content) {
|
|
16
|
+
case "/context":
|
|
17
|
+
return <ContextUsage />;
|
|
18
|
+
case "/usage":
|
|
19
|
+
return <TokenUsage />;
|
|
20
|
+
default:
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { Command } from "@/types/CommandDefinition";
|
|
3
|
+
import { THEME } from "@/theme";
|
|
4
|
+
|
|
5
|
+
export default function CommandSuggestions({
|
|
6
|
+
selectedIndex,
|
|
7
|
+
filteredTemplates,
|
|
8
|
+
}: {
|
|
9
|
+
selectedIndex: number;
|
|
10
|
+
filteredTemplates: Command[];
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<Box
|
|
14
|
+
flexDirection="column"
|
|
15
|
+
paddingX={2}
|
|
16
|
+
borderStyle="round"
|
|
17
|
+
borderColor={THEME.border}
|
|
18
|
+
width={70}
|
|
19
|
+
>
|
|
20
|
+
<Text dimColor>Commands (Tab accepts ghost text):</Text>
|
|
21
|
+
{filteredTemplates.map((cmd, i) => (
|
|
22
|
+
<Box key={cmd.usage}>
|
|
23
|
+
<Text
|
|
24
|
+
color={i === selectedIndex ? THEME.primaryLight : THEME.dim}
|
|
25
|
+
bold={i === selectedIndex}
|
|
26
|
+
>
|
|
27
|
+
{i === selectedIndex ? "› " : " "}
|
|
28
|
+
{cmd.usage}
|
|
29
|
+
</Text>
|
|
30
|
+
<Text color={THEME.dim}> - {cmd.description}</Text>
|
|
31
|
+
</Box>
|
|
32
|
+
))}
|
|
33
|
+
{/* Show role hint if selected command has [role] */}
|
|
34
|
+
{filteredTemplates[selectedIndex]?.usage.includes("[role]") && (
|
|
35
|
+
<Text dimColor italic>
|
|
36
|
+
{" "}[role]: reasoning | general | light | all (or r|g|l|a)
|
|
37
|
+
</Text>
|
|
38
|
+
)}
|
|
39
|
+
</Box>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { THEME } from "@/theme";
|
|
4
|
+
import type { FileSuggestion } from "@/utils/fileSuggestions";
|
|
5
|
+
|
|
6
|
+
export default function FileSuggestions({
|
|
7
|
+
selectedIndex,
|
|
8
|
+
suggestions,
|
|
9
|
+
}: {
|
|
10
|
+
selectedIndex: number;
|
|
11
|
+
suggestions: FileSuggestion[];
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<Box
|
|
15
|
+
flexDirection="column"
|
|
16
|
+
paddingX={2}
|
|
17
|
+
borderStyle="round"
|
|
18
|
+
borderColor={THEME.border} // Distinct color for files
|
|
19
|
+
width={70}
|
|
20
|
+
>
|
|
21
|
+
<Text dimColor>Files (Tab to select):</Text>
|
|
22
|
+
{suggestions.map((file, i) => {
|
|
23
|
+
const fileName = path.basename(file.path);
|
|
24
|
+
const dirName = path.dirname(file.path);
|
|
25
|
+
|
|
26
|
+
let displayDir = "";
|
|
27
|
+
if (dirName !== ".") {
|
|
28
|
+
const fullDir = dirName + "/";
|
|
29
|
+
const available = 58 - fileName.length;
|
|
30
|
+
|
|
31
|
+
if (fullDir.length > available && available > 3) {
|
|
32
|
+
displayDir = "..." + fullDir.slice(-(available - 3));
|
|
33
|
+
} else if (fullDir.length <= available) {
|
|
34
|
+
displayDir = fullDir;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Box key={file.path}>
|
|
40
|
+
<Text wrap="truncate-end">
|
|
41
|
+
<Text
|
|
42
|
+
color={i === selectedIndex ? THEME.primaryLight : THEME.text}
|
|
43
|
+
bold={i === selectedIndex}
|
|
44
|
+
>
|
|
45
|
+
{i === selectedIndex ? "› " : " "}
|
|
46
|
+
{fileName}
|
|
47
|
+
</Text>
|
|
48
|
+
{displayDir && <Text color={THEME.dim}> {displayDir}</Text>}
|
|
49
|
+
{file.type === "dir" && file.childCount !== undefined ? (
|
|
50
|
+
<Text color="gray">
|
|
51
|
+
{" "}
|
|
52
|
+
({file.childCount} {file.childCount === 1 ? "file" : "files"})
|
|
53
|
+
</Text>
|
|
54
|
+
) : null}
|
|
55
|
+
</Text>
|
|
56
|
+
</Box>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</Box>
|
|
60
|
+
);
|
|
61
|
+
}
|