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.
Files changed (127) hide show
  1. package/README.md +68 -0
  2. package/dist/index.js +179297 -0
  3. package/package.json +88 -0
  4. package/src/agent/agent.ts +475 -0
  5. package/src/agent/contextManager.ts +141 -0
  6. package/src/agent/index.ts +14 -0
  7. package/src/agent/pendingChanges.ts +270 -0
  8. package/src/agent/prompts/AskPrompt.txt +10 -0
  9. package/src/agent/prompts/FastPrompt.txt +40 -0
  10. package/src/agent/prompts/PlannerPrompt.txt +51 -0
  11. package/src/agent/prompts/ReviewerPrompt.txt +57 -0
  12. package/src/agent/prompts/WorkerPrompt.txt +33 -0
  13. package/src/agent/subagents/askAgent.ts +37 -0
  14. package/src/agent/subagents/extractionAgent.ts +123 -0
  15. package/src/agent/subagents/fastAgent.ts +45 -0
  16. package/src/agent/subagents/managerAgent.ts +36 -0
  17. package/src/agent/subagents/relationAgent.ts +76 -0
  18. package/src/agent/subagents/researchSubAgent.ts +79 -0
  19. package/src/agent/subagents/reviewerSubAgent.ts +42 -0
  20. package/src/agent/subagents/workerSubAgent.ts +42 -0
  21. package/src/agent/tools/bashTool.ts +94 -0
  22. package/src/agent/tools/descriptions/bash.txt +47 -0
  23. package/src/agent/tools/descriptions/edit.txt +7 -0
  24. package/src/agent/tools/descriptions/glob.txt +4 -0
  25. package/src/agent/tools/descriptions/grep.txt +8 -0
  26. package/src/agent/tools/descriptions/lsp.txt +20 -0
  27. package/src/agent/tools/descriptions/plan.txt +3 -0
  28. package/src/agent/tools/descriptions/read.txt +9 -0
  29. package/src/agent/tools/descriptions/todo.txt +12 -0
  30. package/src/agent/tools/descriptions/write.txt +8 -0
  31. package/src/agent/tools/editTool.ts +44 -0
  32. package/src/agent/tools/globTool.ts +59 -0
  33. package/src/agent/tools/grepTool.ts +343 -0
  34. package/src/agent/tools/lspTool.ts +429 -0
  35. package/src/agent/tools/planTool.ts +118 -0
  36. package/src/agent/tools/readTool.ts +78 -0
  37. package/src/agent/tools/rememberTool.ts +91 -0
  38. package/src/agent/tools/testRunnerTool.ts +77 -0
  39. package/src/agent/tools/testTool.ts +44 -0
  40. package/src/agent/tools/todoTool.ts +224 -0
  41. package/src/agent/tools/writeTool.ts +33 -0
  42. package/src/commands/COMMANDS.ts +38 -0
  43. package/src/commands/cerebras/auth.ts +27 -0
  44. package/src/commands/cerebras/index.ts +31 -0
  45. package/src/commands/forget.ts +29 -0
  46. package/src/commands/google/auth.ts +24 -0
  47. package/src/commands/google/index.ts +31 -0
  48. package/src/commands/groq/add_model.ts +60 -0
  49. package/src/commands/groq/auth.ts +24 -0
  50. package/src/commands/groq/index.ts +33 -0
  51. package/src/commands/index.ts +65 -0
  52. package/src/commands/knowledge.ts +92 -0
  53. package/src/commands/log.ts +32 -0
  54. package/src/commands/mistral/auth.ts +27 -0
  55. package/src/commands/mistral/index.ts +31 -0
  56. package/src/commands/model/index.ts +145 -0
  57. package/src/commands/models/index.ts +16 -0
  58. package/src/commands/ollama/index.ts +29 -0
  59. package/src/commands/openrouter/add_model.ts +64 -0
  60. package/src/commands/openrouter/auth.ts +24 -0
  61. package/src/commands/openrouter/index.ts +33 -0
  62. package/src/commands/remember.ts +48 -0
  63. package/src/commands/serve.ts +31 -0
  64. package/src/commands/session/index.ts +21 -0
  65. package/src/commands/usage.ts +15 -0
  66. package/src/commands/visualize.ts +773 -0
  67. package/src/components/App.tsx +55 -0
  68. package/src/components/IntroComponent.tsx +70 -0
  69. package/src/components/LoaderComponent.tsx +68 -0
  70. package/src/components/OutputRenderer.tsx +88 -0
  71. package/src/components/SettingsRenderer.tsx +23 -0
  72. package/src/components/input/CommandSuggestions.tsx +41 -0
  73. package/src/components/input/FileSuggestions.tsx +61 -0
  74. package/src/components/input/InputBox.tsx +371 -0
  75. package/src/components/output/CheckpointView.tsx +13 -0
  76. package/src/components/output/CommandView.tsx +13 -0
  77. package/src/components/output/CommentView.tsx +12 -0
  78. package/src/components/output/ConfirmationView.tsx +179 -0
  79. package/src/components/output/ContextUsage.tsx +62 -0
  80. package/src/components/output/DiffView.tsx +202 -0
  81. package/src/components/output/ErrorView.tsx +14 -0
  82. package/src/components/output/InteractiveServerView.tsx +69 -0
  83. package/src/components/output/KnowledgeView.tsx +220 -0
  84. package/src/components/output/MarkdownView.tsx +15 -0
  85. package/src/components/output/ModelSelectView.tsx +71 -0
  86. package/src/components/output/ReasoningView.tsx +21 -0
  87. package/src/components/output/ToolCallView.tsx +45 -0
  88. package/src/components/settings/ModelList.tsx +250 -0
  89. package/src/components/settings/TokenUsage.tsx +274 -0
  90. package/src/config/schema.ts +19 -0
  91. package/src/config/settings.ts +229 -0
  92. package/src/index.tsx +100 -0
  93. package/src/parsers/tree-sitter-python.wasm +0 -0
  94. package/src/providers/providers.ts +71 -0
  95. package/src/services/PluginLoader.ts +123 -0
  96. package/src/services/cerebras.ts +69 -0
  97. package/src/services/embeddingService.ts +229 -0
  98. package/src/services/google.ts +65 -0
  99. package/src/services/graphSerializer.ts +248 -0
  100. package/src/services/groq.ts +23 -0
  101. package/src/services/knowledgeOrchestrator.ts +286 -0
  102. package/src/services/mistral.ts +79 -0
  103. package/src/services/ollama.ts +109 -0
  104. package/src/services/openrouter.ts +23 -0
  105. package/src/services/symbolExtractor.ts +277 -0
  106. package/src/store/useFraudeStore.ts +123 -0
  107. package/src/store/useSettingsStore.ts +38 -0
  108. package/src/theme.ts +26 -0
  109. package/src/types/Agent.ts +147 -0
  110. package/src/types/CommandDefinition.ts +8 -0
  111. package/src/types/Model.ts +94 -0
  112. package/src/types/OutputItem.ts +24 -0
  113. package/src/types/PluginContext.ts +55 -0
  114. package/src/types/TokenUsage.ts +5 -0
  115. package/src/types/assets.d.ts +4 -0
  116. package/src/utils/agentCognition.ts +1152 -0
  117. package/src/utils/fileSuggestions.ts +111 -0
  118. package/src/utils/index.ts +17 -0
  119. package/src/utils/initFraude.ts +8 -0
  120. package/src/utils/logger.ts +24 -0
  121. package/src/utils/lspClient.ts +1415 -0
  122. package/src/utils/paths.ts +24 -0
  123. package/src/utils/queryHandler.ts +227 -0
  124. package/src/utils/router.ts +278 -0
  125. package/src/utils/streamHandler.ts +132 -0
  126. package/src/utils/treeSitterQueries.ts +125 -0
  127. package/tsconfig.json +33 -0
@@ -0,0 +1,202 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { type DiffPatch } from "@/agent/pendingChanges";
4
+ import { THEME } from "@/theme";
5
+
6
+ interface DiffViewProps {
7
+ diff?: string;
8
+ patches?: DiffPatch[];
9
+ }
10
+
11
+ export default function DiffView({ diff, patches }: DiffViewProps) {
12
+ if (patches && patches.length > 0) {
13
+ return (
14
+ <Box flexDirection="column" paddingY={1}>
15
+ {patches.map((patch, pIndex) => (
16
+ <Box key={pIndex} flexDirection="column" marginBottom={1}>
17
+ {patch.hunks.map((hunk, hIndex) => {
18
+ let oldLn = hunk.oldStart;
19
+ let newLn = hunk.newStart;
20
+
21
+ return (
22
+ <Box key={hIndex} flexDirection="column">
23
+ {/* Visual separator for hunks/chunks */}
24
+ {(pIndex > 0 || hIndex > 0) && (
25
+ <Box
26
+ borderStyle="single"
27
+ borderTop={false}
28
+ borderLeft={false}
29
+ borderRight={false}
30
+ borderColor={THEME.border}
31
+ marginY={0}
32
+ />
33
+ )}
34
+ {/* Hunk Header */}
35
+ <Box>
36
+ <Text color={THEME.primary}>
37
+ ┌─ {hunk.oldStart},{hunk.oldLines} → {hunk.newStart},
38
+ {hunk.newLines}
39
+ </Text>
40
+ </Box>
41
+
42
+ {hunk.lines.map((line, lIndex) => {
43
+ let type: "add" | "remove" | "context" = "context";
44
+ if (line.startsWith("-")) type = "remove";
45
+ else if (line.startsWith("+")) type = "add";
46
+
47
+ const content = line.substring(1);
48
+ let displayNum = "";
49
+ let color = THEME.text;
50
+
51
+ if (type === "remove") {
52
+ displayNum = oldLn.toString();
53
+ oldLn++;
54
+ color = THEME.error;
55
+ } else if (type === "add") {
56
+ displayNum = newLn.toString();
57
+ newLn++;
58
+ color = THEME.success;
59
+ } else {
60
+ displayNum = newLn.toString();
61
+ oldLn++;
62
+ newLn++;
63
+ }
64
+
65
+ return (
66
+ <Box key={lIndex}>
67
+ <Box
68
+ width={4}
69
+ marginRight={1}
70
+ justifyContent="flex-end"
71
+ >
72
+ <Text color={THEME.dim}>{displayNum}</Text>
73
+ </Box>
74
+ <Box>
75
+ <Text bold={type !== "context"} color={color}>
76
+ {content}
77
+ </Text>
78
+ </Box>
79
+ </Box>
80
+ );
81
+ })}
82
+ </Box>
83
+ );
84
+ })}
85
+ </Box>
86
+ ))}
87
+ </Box>
88
+ );
89
+ }
90
+
91
+ if (diff) {
92
+ /*
93
+ * Filter out the header lines:
94
+ * --- @/sample/utils.py Original
95
+ * +++ @/sample/utils.py Modified
96
+ */
97
+ const rawLines = diff
98
+ .split("\n")
99
+ .filter((line) => !line.startsWith("--- ") && !line.startsWith("+++ "));
100
+
101
+ let oldLn = 0;
102
+ let newLn = 0;
103
+
104
+ const parsedLines = rawLines.reduce<
105
+ {
106
+ type: "add" | "remove" | "context" | "chunk" | "other";
107
+ content: string;
108
+ lineNumber?: number; // focusing on new line number for simplicity or context
109
+ oldLineNumber?: number;
110
+ }[]
111
+ >((acc, line) => {
112
+ if (line.startsWith("@@")) {
113
+ const match = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
114
+ if (match) {
115
+ oldLn = parseInt(match[1] || "0", 10);
116
+ newLn = parseInt(match[2] || "0", 10);
117
+ }
118
+ if (acc.length > 0) {
119
+ acc.push({ type: "chunk", content: "..." });
120
+ }
121
+ return acc;
122
+ }
123
+
124
+ if (line.startsWith("-")) {
125
+ acc.push({
126
+ type: "remove",
127
+ content: line.substring(1),
128
+ oldLineNumber: oldLn,
129
+ });
130
+ oldLn++;
131
+ } else if (line.startsWith("+")) {
132
+ acc.push({
133
+ type: "add",
134
+ content: line.substring(1),
135
+ lineNumber: newLn,
136
+ });
137
+ newLn++;
138
+ } else if (line.startsWith(" ")) {
139
+ acc.push({
140
+ type: "context",
141
+ content: line.substring(1),
142
+ oldLineNumber: oldLn,
143
+ lineNumber: newLn,
144
+ });
145
+ oldLn++;
146
+ newLn++;
147
+ } else {
148
+ acc.push({ type: "other", content: line });
149
+ }
150
+ return acc;
151
+ }, []);
152
+
153
+ return (
154
+ <Box flexDirection="column" paddingY={1}>
155
+ {parsedLines.map((item, index) => {
156
+ if (item.type === "chunk") {
157
+ return (
158
+ <Box
159
+ key={index}
160
+ borderStyle="single"
161
+ borderTop={false}
162
+ borderLeft={false}
163
+ borderRight={false}
164
+ borderColor={THEME.border}
165
+ marginY={0}
166
+ >
167
+ {/* Visual separator for chunks */}
168
+ </Box>
169
+ );
170
+ }
171
+
172
+ const lineNum = item.lineNumber ? item.lineNumber.toString() : "";
173
+ const oldLineNum = item.oldLineNumber
174
+ ? item.oldLineNumber.toString()
175
+ : "";
176
+
177
+ // Determine color
178
+ let color = THEME.text;
179
+ if (item.type === "add") color = THEME.success;
180
+ if (item.type === "remove") color = THEME.error;
181
+
182
+ const displayNum = item.type === "remove" ? oldLineNum : lineNum;
183
+
184
+ return (
185
+ <Box key={index}>
186
+ <Box width={4} marginRight={1} justifyContent="flex-end">
187
+ <Text color={THEME.dim}>{displayNum}</Text>
188
+ </Box>
189
+ <Box>
190
+ <Text bold={item.type !== "context"} color={color}>
191
+ {item.content}
192
+ </Text>
193
+ </Box>
194
+ </Box>
195
+ );
196
+ })}
197
+ </Box>
198
+ );
199
+ }
200
+
201
+ return null;
202
+ }
@@ -0,0 +1,14 @@
1
+ import { Box, Text } from "ink";
2
+ import { THEME } from "@/theme";
3
+
4
+ const ErrorView = ({ error }: { error: string }) => {
5
+ return (
6
+ <Box marginY={0}>
7
+ <Text color={THEME.error} bold>
8
+ ✘ {error}
9
+ </Text>
10
+ </Box>
11
+ );
12
+ };
13
+
14
+ export default ErrorView;
@@ -0,0 +1,69 @@
1
+ import { Box, Text, useInput } from "ink";
2
+ import { THEME } from "../../theme";
3
+ import { BunApiRouter } from "../../utils/router";
4
+ import { useState, useEffect } from "react";
5
+
6
+ interface InteractiveServerViewProps {
7
+ routerId: string;
8
+ }
9
+
10
+ export default function InteractiveServerView({
11
+ routerId,
12
+ }: InteractiveServerViewProps) {
13
+ const [stopped, setStopped] = useState(false);
14
+
15
+ // Retrieve the router instance to get details (like port)
16
+ const router = BunApiRouter.getRouter(routerId);
17
+ const port = router?.port || 3000;
18
+
19
+ useEffect(() => {
20
+ if (router) {
21
+ router.onStop = () => {
22
+ setStopped(true);
23
+ };
24
+ }
25
+ }, [router]);
26
+
27
+ useInput((input, key) => {
28
+ if (stopped) return;
29
+
30
+ if (input === "q") {
31
+ BunApiRouter.stopRouter(routerId);
32
+ setStopped(true);
33
+ }
34
+ });
35
+
36
+ if (stopped) {
37
+ return (
38
+ <Box borderStyle="round" borderColor={THEME.dim} paddingX={1}>
39
+ <Text color={THEME.dim}>● </Text>
40
+ <Text color={THEME.dim}>Server stopped</Text>
41
+ </Box>
42
+ );
43
+ }
44
+
45
+ return (
46
+ <Box
47
+ flexDirection="column"
48
+ borderStyle="round"
49
+ borderColor="white"
50
+ paddingX={1}
51
+ >
52
+ <Box>
53
+ <Text color={THEME.success}>● </Text>
54
+ <Text bold color={THEME.text}>
55
+ API Server Running{" "}
56
+ </Text>
57
+ <Text color={THEME.dim}>on </Text>
58
+ <Text color={THEME.primary} underline>
59
+ http://localhost:{port}
60
+ </Text>
61
+ </Box>
62
+ <Box marginTop={1}>
63
+ <Text color={THEME.dim} italic>
64
+ Press 'q' to stop server
65
+ </Text>
66
+ </Box>
67
+ </Box>
68
+ );
69
+ }
@@ -0,0 +1,220 @@
1
+ import { Box, Text } from "ink";
2
+ import { Badge, UnorderedList } from "@inkjs/ui";
3
+ import { THEME } from "@/theme";
4
+
5
+ // Knowledge type configuration - using simple ASCII symbols
6
+ const TYPE_CONFIG: Record<
7
+ string,
8
+ { icon: string; label: string; color: string }
9
+ > = {
10
+ decision: { icon: "*", label: "DEC", color: THEME.success },
11
+ fact: { icon: "-", label: "FCT", color: THEME.info },
12
+ concept: { icon: "+", label: "CON", color: "#cba6f7" }, // Mauve
13
+ reference: { icon: ">", label: "REF", color: "#fab387" }, // Peach
14
+ };
15
+
16
+ interface KnowledgeItem {
17
+ content: string;
18
+ type: string;
19
+ }
20
+
21
+ interface KnowledgeSummaryData {
22
+ decisions: KnowledgeItem[];
23
+ facts: KnowledgeItem[];
24
+ concepts: KnowledgeItem[];
25
+ references: KnowledgeItem[];
26
+ // summaries: KnowledgeItem[];
27
+ }
28
+
29
+ interface KnowledgeSearchData {
30
+ query: string;
31
+ results: KnowledgeItem[];
32
+ }
33
+
34
+ export type KnowledgeViewData =
35
+ | { mode: "summary"; data: KnowledgeSummaryData }
36
+ | { mode: "search"; data: KnowledgeSearchData }
37
+ | { mode: "empty"; message?: string };
38
+
39
+ interface KnowledgeViewProps {
40
+ data: KnowledgeViewData;
41
+ }
42
+
43
+ // Helper to truncate text
44
+ const truncate = (str: string, len: number): string => {
45
+ const cleaned = str.replace(/\n/g, " ").trim();
46
+ return cleaned.length > len ? `${cleaned.slice(0, len)}...` : cleaned;
47
+ };
48
+
49
+ // Section renderer for summary view
50
+ function KnowledgeSection({
51
+ title,
52
+ type,
53
+ items,
54
+ limit,
55
+ }: {
56
+ title: string;
57
+ type: string;
58
+ items: KnowledgeItem[];
59
+ limit: number;
60
+ }) {
61
+ if (items.length === 0) return null;
62
+
63
+ const config = TYPE_CONFIG[type] || {
64
+ icon: "-",
65
+ label: type.slice(0, 3).toUpperCase(),
66
+ color: THEME.dim,
67
+ };
68
+
69
+ return (
70
+ <Box flexDirection="column" marginBottom={1}>
71
+ <Box gap={1}>
72
+ <Text color={config.color}>{config.icon}</Text>
73
+ <Text bold color={THEME.text}>
74
+ {title}
75
+ </Text>
76
+ <Text color={THEME.dim}>-</Text>
77
+ <Text color={THEME.dim}>{items.length}</Text>
78
+ </Box>
79
+ <Box flexDirection="column" paddingLeft={2}>
80
+ <UnorderedList>
81
+ {items.slice(0, limit).map((item, i) => (
82
+ <UnorderedList.Item key={i}>
83
+ <Text color={THEME.text}>{truncate(item.content, 58)}</Text>
84
+ </UnorderedList.Item>
85
+ ))}
86
+ </UnorderedList>
87
+ {items.length > limit && (
88
+ <Text color={THEME.dim} italic>
89
+ {" "}... {items.length - limit} more
90
+ </Text>
91
+ )}
92
+ </Box>
93
+ </Box>
94
+ );
95
+ }
96
+
97
+ // Search result item
98
+ function SearchResultItem({ item }: { item: KnowledgeItem }) {
99
+ const config = TYPE_CONFIG[item.type] || {
100
+ icon: "-",
101
+ label: item.type.slice(0, 3).toUpperCase(),
102
+ color: THEME.dim,
103
+ };
104
+
105
+ return (
106
+ <Box gap={1}>
107
+ <Badge color={config.color}>{config.label}</Badge>
108
+ <Text color={THEME.text}>{truncate(item.content, 52)}</Text>
109
+ </Box>
110
+ );
111
+ }
112
+
113
+ export default function KnowledgeView({ data }: KnowledgeViewProps) {
114
+ if (data.mode === "empty") {
115
+ return (
116
+ <Box flexDirection="column">
117
+ <Text bold color={THEME.primary}>
118
+ Project Knowledge
119
+ </Text>
120
+ <Box paddingY={1}>
121
+ <Text color={THEME.dim}>
122
+ {data.message ||
123
+ "No knowledge stored yet. Start a conversation to build context."}
124
+ </Text>
125
+ </Box>
126
+ <Box>
127
+ <Text color={THEME.dim}>Tip: Use </Text>
128
+ <Text color={THEME.primary}>/knowledge {"<query>"}</Text>
129
+ <Text color={THEME.dim}> to search</Text>
130
+ </Box>
131
+ </Box>
132
+ );
133
+ }
134
+
135
+ if (data.mode === "search") {
136
+ const { query, results } = data.data;
137
+
138
+ if (results.length === 0) {
139
+ return (
140
+ <Box flexDirection="column">
141
+ <Box gap={1}>
142
+ <Text bold color={THEME.primary}>
143
+ Search:
144
+ </Text>
145
+ <Text color={THEME.dim}>"{truncate(query, 40)}"</Text>
146
+ </Box>
147
+ <Box paddingY={1}>
148
+ <Text color={THEME.dim}>No results found</Text>
149
+ </Box>
150
+ <Box>
151
+ <Text color={THEME.dim}>Try a different query or use </Text>
152
+ <Text color={THEME.primary}>/knowledge</Text>
153
+ <Text color={THEME.dim}> to see all items</Text>
154
+ </Box>
155
+ </Box>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <Box flexDirection="column">
161
+ <Box gap={1} marginBottom={1}>
162
+ <Text bold color={THEME.primary}>
163
+ Search:
164
+ </Text>
165
+ <Text color={THEME.dim}>
166
+ "{truncate(query, 30)}" - {results.length} found
167
+ </Text>
168
+ </Box>
169
+ <Box flexDirection="column" gap={1}>
170
+ {results.map((item, i) => (
171
+ <SearchResultItem key={i} item={item} />
172
+ ))}
173
+ </Box>
174
+ </Box>
175
+ );
176
+ }
177
+
178
+ // Summary view
179
+ const { decisions, facts, concepts, references } = data.data;
180
+ const total =
181
+ decisions.length + facts.length + concepts.length + references.length;
182
+ // + summaries.length;
183
+
184
+ return (
185
+ <Box flexDirection="column">
186
+ <Box gap={1} marginBottom={1}>
187
+ <Text bold color={THEME.primary}>
188
+ Project Knowledge
189
+ </Text>
190
+ <Text color={THEME.dim}>- {total} items</Text>
191
+ </Box>
192
+ <Box flexDirection="column">
193
+ <KnowledgeSection
194
+ title="Decisions"
195
+ type="decision"
196
+ items={decisions}
197
+ limit={4}
198
+ />
199
+ <KnowledgeSection title="Facts" type="fact" items={facts} limit={4} />
200
+ <KnowledgeSection
201
+ title="Concepts"
202
+ type="concept"
203
+ items={concepts}
204
+ limit={3}
205
+ />
206
+ <KnowledgeSection
207
+ title="References"
208
+ type="reference"
209
+ items={references}
210
+ limit={3}
211
+ />
212
+ </Box>
213
+ <Box marginTop={1}>
214
+ <Text color={THEME.dim}>Tip: Use </Text>
215
+ <Text color={THEME.primary}>/knowledge {"<query>"}</Text>
216
+ <Text color={THEME.dim}> to search</Text>
217
+ </Box>
218
+ </Box>
219
+ );
220
+ }
@@ -0,0 +1,15 @@
1
+ import Markdown from "@inkkit/ink-markdown";
2
+ import Chalk from "chalk";
3
+ import { THEME } from "@/theme";
4
+
5
+ export default function MarkdownView({ markdown }: { markdown: string }) {
6
+ return (
7
+ <Markdown
8
+ code={Chalk.white}
9
+ codespan={Chalk.white}
10
+ heading={Chalk.hex(THEME.primary)}
11
+ >
12
+ {markdown}
13
+ </Markdown>
14
+ );
15
+ }
@@ -0,0 +1,71 @@
1
+ import { Box, Text } from "ink";
2
+ import { Select } from "@inkjs/ui";
3
+ import React from "react";
4
+ import useSettingsStore from "@/store/useSettingsStore";
5
+ import useFraudeStore from "@/store/useFraudeStore";
6
+ import { THEME } from "@/theme";
7
+
8
+ export default function ModelSelectView() {
9
+ const { models } = useSettingsStore();
10
+ const pendingSelection = useFraudeStore(
11
+ (state) => state.pendingModelSelection,
12
+ );
13
+ const resolveModelSelection = useFraudeStore(
14
+ (state) => state.resolveModelSelection,
15
+ );
16
+
17
+ if (!pendingSelection) {
18
+ return null;
19
+ }
20
+
21
+ const { originalModel, errorMessage } = pendingSelection;
22
+
23
+ // Filter out the model that failed and get available alternatives
24
+ const availableModels = models.filter((m) => m.name !== originalModel);
25
+
26
+ const options = [
27
+ { label: "✗ Cancel (abort the request)", value: "__cancel__" },
28
+ ...availableModels.map((model) => ({
29
+ label: `→ ${model.name} (${model.type})`,
30
+ value: model.name,
31
+ })),
32
+ ];
33
+
34
+ const handleSelect = (value: string) => {
35
+ if (value === "__cancel__") {
36
+ resolveModelSelection(null);
37
+ } else {
38
+ resolveModelSelection(value);
39
+ }
40
+ };
41
+
42
+ return (
43
+ <Box
44
+ flexDirection="column"
45
+ borderStyle="single"
46
+ borderColor={THEME.warning}
47
+ paddingX={1}
48
+ >
49
+ <Text color={THEME.warning}>⚠ Rate Limit Exceeded</Text>
50
+ <Box marginY={1} flexDirection="column">
51
+ <Text>
52
+ Model{" "}
53
+ <Text bold color={THEME.error}>
54
+ {originalModel}
55
+ </Text>{" "}
56
+ hit its rate limit.
57
+ </Text>
58
+ </Box>
59
+ <Box marginBottom={1}>
60
+ <Text dimColor wrap="wrap">
61
+ {errorMessage.slice(0, 150)}
62
+ {errorMessage.length > 150 ? "..." : ""}
63
+ </Text>
64
+ </Box>
65
+ <Text>Select an alternative model:</Text>
66
+ <Box marginTop={0}>
67
+ <Select options={options} onChange={handleSelect} />
68
+ </Box>
69
+ </Box>
70
+ );
71
+ }
@@ -0,0 +1,21 @@
1
+ import { Box, Text } from "ink";
2
+ import { THEME } from "@/theme";
3
+
4
+ interface ReasoningViewProps {
5
+ content: string;
6
+ duration?: number;
7
+ }
8
+
9
+ export default function ReasoningView({
10
+ content,
11
+ duration,
12
+ }: ReasoningViewProps) {
13
+ return (
14
+ <Box flexDirection="column" marginY={0}>
15
+ <Text color={THEME.dim}>
16
+ [thinking] {content}
17
+ {duration ? ` (${duration.toFixed(1)}s)` : ""}
18
+ </Text>
19
+ </Box>
20
+ );
21
+ }
@@ -0,0 +1,45 @@
1
+ import { Box, Text } from "ink";
2
+ import { THEME } from "@/theme";
3
+
4
+ interface ToolCallViewProps {
5
+ action: string;
6
+ details?: string;
7
+ result?: string;
8
+ duration?: string;
9
+ }
10
+
11
+ export default function ToolCallView({
12
+ action,
13
+ details,
14
+ result,
15
+ duration,
16
+ }: ToolCallViewProps) {
17
+ // Truncate result preview to 80 chars
18
+ const resultPreview = result
19
+ ? result.length > 80
20
+ ? result.slice(0, 77) + "..."
21
+ : result
22
+ : null;
23
+
24
+ return (
25
+ <Box flexDirection="column" marginY={0}>
26
+ <Box>
27
+ <Text color={THEME.primary}>◇</Text>
28
+ <Text> </Text>
29
+ <Text color={THEME.text}>{action}</Text>
30
+ {details && (
31
+ <Text color={THEME.dim}>
32
+ {" "}
33
+ {details.length > 50 ? details.slice(0, 47) + "..." : details}
34
+ </Text>
35
+ )}
36
+ {duration && <Text color={THEME.dim}> {duration}</Text>}
37
+ </Box>
38
+ {resultPreview && (
39
+ <Box paddingLeft={2}>
40
+ <Text color={THEME.dim}>↳ {resultPreview}</Text>
41
+ </Box>
42
+ )}
43
+ </Box>
44
+ );
45
+ }