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,250 @@
1
+ import { Box, Text } from "ink";
2
+ import { useEffect, useMemo } from "react";
3
+ import { type Model, parseModelUniqueId } from "@/types/Model";
4
+ import useSettingsStore from "@/store/useSettingsStore";
5
+
6
+ import { THEME as SHARED_THEME } from "@/theme";
7
+
8
+ // Simplified Theme mapped to Shared Theme
9
+ const THEME = {
10
+ active: SHARED_THEME.primary,
11
+ primary: SHARED_THEME.primary,
12
+ secondary: SHARED_THEME.primaryDim,
13
+ text: SHARED_THEME.text,
14
+ dim: SHARED_THEME.dim,
15
+ border: SHARED_THEME.border,
16
+ };
17
+
18
+ const formatContext = (contextLength: number | undefined) => {
19
+ if (!contextLength) return "";
20
+ return `${(contextLength / 1000).toFixed(0)}k`;
21
+ };
22
+
23
+ /**
24
+ * Check if a model matches a stored model reference.
25
+ */
26
+ const modelMatchesReference = (model: Model, reference: string): boolean => {
27
+ const parsed = parseModelUniqueId(reference);
28
+ if (parsed) {
29
+ return model.name === parsed.name && model.type === parsed.type;
30
+ }
31
+ return model.name === reference;
32
+ };
33
+
34
+ const ModelRow = ({
35
+ model,
36
+ isPrimary,
37
+ isSecondary,
38
+ }: {
39
+ model: Model;
40
+ isPrimary: boolean;
41
+ isSecondary: boolean;
42
+ }) => {
43
+ const isActive = isPrimary || isSecondary;
44
+
45
+ return (
46
+ <Box flexDirection="row" gap={1}>
47
+ <Box width={1}>{isActive && <Text color={THEME.active}>•</Text>}</Box>
48
+
49
+ <Box flexGrow={1}>
50
+ <Text color={isActive ? THEME.active : THEME.text} wrap="truncate-end">
51
+ {model.name}
52
+ </Text>
53
+ </Box>
54
+
55
+ {/* Tags */}
56
+ <Box gap={1} flexShrink={0}>
57
+ {/* Context Size */}
58
+ {model.details?.context_length && (
59
+ <Text color={THEME.dim}>
60
+ {formatContext(model.details.context_length)}
61
+ </Text>
62
+ )}
63
+
64
+ {isPrimary && (
65
+ <Text color={THEME.primary} bold>
66
+ PRI
67
+ </Text>
68
+ )}
69
+ {isSecondary && (
70
+ <Text color={THEME.secondary} bold>
71
+ SEC
72
+ </Text>
73
+ )}
74
+ </Box>
75
+ </Box>
76
+ );
77
+ };
78
+
79
+ const ProviderSection = ({
80
+ provider,
81
+ models,
82
+ primaryModelRef,
83
+ secondaryModelRef,
84
+ showAll = false,
85
+ }: {
86
+ provider: string;
87
+ models: Model[];
88
+ primaryModelRef: string;
89
+ secondaryModelRef: string;
90
+ showAll?: boolean;
91
+ }) => {
92
+ // Show all active models + up to 5 others
93
+ const { visibleModels, hiddenCount } = useMemo(() => {
94
+ const active: Model[] = [];
95
+ const others: Model[] = [];
96
+
97
+ models.forEach((m) => {
98
+ const isPri = modelMatchesReference(m, primaryModelRef);
99
+ const isSec = modelMatchesReference(m, secondaryModelRef);
100
+ if (isPri || isSec) {
101
+ active.push(m);
102
+ } else {
103
+ others.push(m);
104
+ }
105
+ });
106
+
107
+ // Sort others alphabetically
108
+ others.sort((a, b) => a.name.localeCompare(b.name));
109
+
110
+ // Combine: Active first, then top 3 others
111
+ // If we have very few active models, show more others to fill space, but let's keep it simple.
112
+ // Let's just show up to 5 'others'.
113
+ const visibleOthers = showAll ? others : others.slice(0, 5);
114
+ return {
115
+ visibleModels: [...active, ...visibleOthers],
116
+ hiddenCount: others.length - visibleOthers.length,
117
+ };
118
+ }, [models, primaryModelRef, secondaryModelRef, showAll]);
119
+
120
+ if (models.length === 0) return null;
121
+
122
+ return (
123
+ <Box flexDirection="column">
124
+ <Text color={THEME.dim} bold>
125
+ {provider.toUpperCase()}
126
+ </Text>
127
+ {visibleModels.map((model) => (
128
+ <ModelRow
129
+ key={model.digest || `${model.type}-${model.name}`}
130
+ model={model}
131
+ isPrimary={modelMatchesReference(model, primaryModelRef)}
132
+ isSecondary={modelMatchesReference(model, secondaryModelRef)}
133
+ />
134
+ ))}
135
+ {hiddenCount > 0 && (
136
+ <Box marginLeft={2}>
137
+ <Text color={THEME.dim} italic>
138
+ + {hiddenCount} more models...
139
+ </Text>
140
+ </Box>
141
+ )}
142
+ </Box>
143
+ );
144
+ };
145
+
146
+ const CurrentConfig = ({
147
+ primary,
148
+ secondary,
149
+ }: {
150
+ primary: string;
151
+ secondary: string;
152
+ }) => (
153
+ <Box
154
+ flexDirection="column"
155
+ borderStyle="single"
156
+ borderColor={THEME.border}
157
+ paddingX={1}
158
+ >
159
+ <Box flexDirection="row" gap={2}>
160
+ <Text color={THEME.primary} bold>
161
+ PRIMARY
162
+ </Text>
163
+ <Text color={THEME.text}>{primary}</Text>
164
+ </Box>
165
+ <Box flexDirection="row" gap={2}>
166
+ <Text color={THEME.secondary} bold>
167
+ SECONDARY
168
+ </Text>
169
+ <Text color={THEME.text}>{secondary}</Text>
170
+ </Box>
171
+ </Box>
172
+ );
173
+
174
+ const ModelList = ({
175
+ providerFilter,
176
+ showAll = false,
177
+ }: {
178
+ providerFilter?: string;
179
+ showAll?: boolean;
180
+ }) => {
181
+ const { primaryModel, secondaryModel, models, syncWithSettings } =
182
+ useSettingsStore();
183
+
184
+ useEffect(() => {
185
+ syncWithSettings();
186
+ }, [syncWithSettings]);
187
+
188
+ // Group models
189
+ const groups = useMemo(() => {
190
+ const g: Record<string, Model[]> = {};
191
+ models.forEach((m) => {
192
+ // Clean up provider name
193
+ let type = (m.type || "ollama").toLowerCase();
194
+
195
+ // Filter by provider if requested
196
+ if (providerFilter && type !== providerFilter.toLowerCase()) {
197
+ return;
198
+ }
199
+
200
+ // Skip ollama embedding models
201
+ if (
202
+ type === "ollama" &&
203
+ m.capabilities?.length === 1 &&
204
+ m.capabilities[0] === "embedding"
205
+ )
206
+ return;
207
+
208
+ if (!g[type]) g[type] = [];
209
+ g[type].push(m);
210
+ });
211
+ return g;
212
+ }, [models, providerFilter]);
213
+
214
+ if (models.length === 0) {
215
+ return (
216
+ <Text color={THEME.dim}>
217
+ No models found{providerFilter ? ` for ${providerFilter}` : ""}.
218
+ </Text>
219
+ );
220
+ }
221
+
222
+ return (
223
+ <Box flexDirection="column" gap={1}>
224
+ <CurrentConfig primary={primaryModel} secondary={secondaryModel} />
225
+
226
+ <Box flexDirection="column" gap={1}>
227
+ {Object.entries(groups).map(([provider, providerModels]) => (
228
+ <ProviderSection
229
+ key={provider}
230
+ provider={provider}
231
+ models={providerModels}
232
+ primaryModelRef={primaryModel}
233
+ secondaryModelRef={secondaryModel}
234
+ showAll={showAll || !!providerFilter}
235
+ />
236
+ ))}
237
+ </Box>
238
+
239
+ {!providerFilter && (
240
+ <Box marginTop={1}>
241
+ <Text color={THEME.dim}>
242
+ Use <Text color="white">/model &lt;name&gt;</Text> to switch
243
+ </Text>
244
+ </Box>
245
+ )}
246
+ </Box>
247
+ );
248
+ };
249
+
250
+ export default ModelList;
@@ -0,0 +1,274 @@
1
+ import { Box, Text } from "ink";
2
+ import { useMemo } from "react";
3
+ import { type Model } from "@/types/Model";
4
+ import useSettingsStore from "@/store/useSettingsStore";
5
+ import log from "@/utils/logger";
6
+
7
+ import { THEME as SHARED_THEME } from "@/theme";
8
+
9
+ // Minimalist Theme mapped to Shared Theme
10
+ const THEME = {
11
+ text: SHARED_THEME.text,
12
+ dim: SHARED_THEME.dim,
13
+ accent: SHARED_THEME.primaryLight,
14
+ error: SHARED_THEME.error,
15
+ header: SHARED_THEME.text,
16
+ barFilled: SHARED_THEME.primary,
17
+ barEmpty: SHARED_THEME.border,
18
+ };
19
+
20
+ // Format token count with K/M suffix (Compact)
21
+ const formatTokens = (tokens: number): string => {
22
+ if (tokens === 0) return "0";
23
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
24
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
25
+ return tokens.toLocaleString();
26
+ };
27
+
28
+ interface ModelUsage {
29
+ name: string;
30
+ promptTokens: number;
31
+ completionTokens: number;
32
+ totalTokens: number;
33
+ percentOfProvider: number;
34
+ }
35
+
36
+ interface ProviderUsage {
37
+ provider: string;
38
+ models: ModelUsage[];
39
+ totalTokens: number;
40
+ percentOfTotal: number;
41
+ }
42
+
43
+ const ProgressBar = ({
44
+ percent,
45
+ width = 15,
46
+ }: {
47
+ percent: number;
48
+ width?: number;
49
+ }) => {
50
+ const safePercent = Math.min(100, Math.max(0, percent));
51
+ const filled = Math.round((safePercent / 100) * width);
52
+ const empty = width - filled;
53
+
54
+ return (
55
+ <Text>
56
+ <Text color={THEME.barFilled}>{"━".repeat(filled)}</Text>
57
+ <Text color={THEME.barEmpty}>{"─".repeat(empty)}</Text>
58
+ </Text>
59
+ );
60
+ };
61
+
62
+ // Unified Column Widths
63
+ const COL_WIDTHS = {
64
+ NAME: 24,
65
+ BAR: 18, // Widen for bar + text
66
+ TOTAL: 10,
67
+ BREAKDOWN: 24,
68
+ };
69
+
70
+ const HeaderRow = () => (
71
+ <Box flexDirection="row" gap={2} marginBottom={0}>
72
+ <Box width={COL_WIDTHS.NAME}>
73
+ <Text color={THEME.dim}>Model Name</Text>
74
+ </Box>
75
+ <Box width={COL_WIDTHS.BAR}>
76
+ <Text color={THEME.dim}>% of Provider</Text>
77
+ </Box>
78
+ <Box width={COL_WIDTHS.TOTAL} justifyContent="flex-end">
79
+ <Text color={THEME.dim}>Total</Text>
80
+ </Box>
81
+ <Box width={COL_WIDTHS.BREAKDOWN} justifyContent="flex-end">
82
+ <Text color={THEME.dim}>Breakdown</Text>
83
+ </Box>
84
+ </Box>
85
+ );
86
+
87
+ const ModelRow = ({ model }: { model: ModelUsage }) => {
88
+ return (
89
+ <Box flexDirection="row" gap={2}>
90
+ {/* Name */}
91
+ <Box width={COL_WIDTHS.NAME}>
92
+ <Text color={THEME.text} wrap="truncate-end">
93
+ {model.name}
94
+ </Text>
95
+ </Box>
96
+
97
+ {/* Bar */}
98
+ <Box width={COL_WIDTHS.BAR} flexDirection="row" gap={1}>
99
+ <ProgressBar percent={model.percentOfProvider} width={8} />
100
+ <Text color={THEME.dim}>{model.percentOfProvider.toFixed(0)}%</Text>
101
+ </Box>
102
+
103
+ {/* Total */}
104
+ <Box width={COL_WIDTHS.TOTAL} justifyContent="flex-end">
105
+ <Text color={THEME.text} bold>
106
+ {formatTokens(model.totalTokens)}
107
+ </Text>
108
+ </Box>
109
+
110
+ {/* Breakdown */}
111
+ <Box width={COL_WIDTHS.BREAKDOWN} justifyContent="flex-end">
112
+ <Text color={THEME.dim}>
113
+ P: {formatTokens(model.promptTokens)} · C:{" "}
114
+ {formatTokens(model.completionTokens)}
115
+ </Text>
116
+ </Box>
117
+ </Box>
118
+ );
119
+ };
120
+
121
+ const ProviderSection = ({ usage }: { usage: ProviderUsage }) => {
122
+ if (usage.totalTokens === 0) return null;
123
+
124
+ return (
125
+ <Box flexDirection="column" marginTop={1}>
126
+ <Box marginBottom={0}>
127
+ <Text bold color={THEME.header}>
128
+ {usage.provider.toUpperCase()}
129
+ <Text color={THEME.dim}>
130
+ {" "}
131
+ · {usage.percentOfTotal.toFixed(1)}% of all usage
132
+ </Text>
133
+ </Text>
134
+ </Box>
135
+
136
+ {/* Column Headers for this section */}
137
+ <HeaderRow />
138
+
139
+ <Box flexDirection="column">
140
+ {usage.models
141
+ .sort((a, b) => b.totalTokens - a.totalTokens)
142
+ .map((model) => (
143
+ <ModelRow key={model.name} model={model} />
144
+ ))}
145
+ </Box>
146
+ </Box>
147
+ );
148
+ };
149
+
150
+ const TokenUsage = () => {
151
+ const { models } = useSettingsStore();
152
+ log(JSON.stringify(models, null, 2));
153
+
154
+ const stats = useMemo(() => {
155
+ let globalTotal = 0;
156
+ let globalPrompt = 0;
157
+ let globalCompletion = 0;
158
+
159
+ const grouped: Record<string, Model[]> = {} as Record<string, Model[]>;
160
+ Object.keys(grouped).forEach((p) => (grouped[p] = []));
161
+
162
+ // Aggregate
163
+ for (const model of models) {
164
+ const provider = model.type || "ollama";
165
+ if (!grouped[provider]) grouped[provider] = []; // Safety
166
+ grouped[provider].push(model);
167
+
168
+ const u = model.usage || {
169
+ totalTokens: 0,
170
+ promptTokens: 0,
171
+ completionTokens: 0,
172
+ };
173
+ globalTotal += u.totalTokens;
174
+ globalPrompt += u.promptTokens;
175
+ globalCompletion += u.completionTokens;
176
+ }
177
+
178
+ // Process per provider
179
+ const providers: ProviderUsage[] = Object.keys(grouped)
180
+ .map((provider) => {
181
+ const pModels = grouped[provider] || [];
182
+ let pTotal = 0;
183
+
184
+ const mUsages: ModelUsage[] = pModels
185
+ .filter((m) => (m.usage?.totalTokens ?? 0) > 0)
186
+ .map((m) => {
187
+ const t = m.usage?.totalTokens ?? 0;
188
+ pTotal += t;
189
+ return {
190
+ name: m.name,
191
+ totalTokens: t,
192
+ promptTokens: m.usage?.promptTokens ?? 0,
193
+ completionTokens: m.usage?.completionTokens ?? 0,
194
+ percentOfProvider: 0, // Calc later
195
+ };
196
+ });
197
+
198
+ // Calc percents
199
+ mUsages.forEach(
200
+ (m) =>
201
+ (m.percentOfProvider =
202
+ pTotal > 0 ? (m.totalTokens / pTotal) * 100 : 0),
203
+ );
204
+
205
+ return {
206
+ provider,
207
+ models: mUsages,
208
+ totalTokens: pTotal,
209
+ percentOfTotal: globalTotal > 0 ? (pTotal / globalTotal) * 100 : 0,
210
+ };
211
+ })
212
+ .filter((p) => p.totalTokens > 0)
213
+ .sort((a, b) => b.totalTokens - a.totalTokens);
214
+
215
+ return { globalTotal, globalPrompt, globalCompletion, providers };
216
+ }, [models]);
217
+
218
+ if (stats.globalTotal === 0) {
219
+ return (
220
+ <Box padding={1}>
221
+ <Text color={THEME.dim}>
222
+ No token usage data available. Start a chat to see stats!
223
+ </Text>
224
+ </Box>
225
+ );
226
+ }
227
+
228
+ return (
229
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
230
+ {/* Header Stats */}
231
+ <Box
232
+ borderStyle="single"
233
+ borderColor={THEME.dim}
234
+ paddingX={1}
235
+ marginBottom={1}
236
+ justifyContent="space-between"
237
+ >
238
+ <Text color={THEME.header} bold>
239
+ USAGE STATS
240
+ </Text>
241
+ <Box gap={2}>
242
+ <Text>
243
+ TOTAL:{" "}
244
+ <Text color={SHARED_THEME.primary} bold>
245
+ {formatTokens(stats.globalTotal)}
246
+ </Text>
247
+ </Text>
248
+ <Text color={THEME.dim}>P: {formatTokens(stats.globalPrompt)}</Text>
249
+ <Text color={THEME.dim}>
250
+ C: {formatTokens(stats.globalCompletion)}
251
+ </Text>
252
+ </Box>
253
+ </Box>
254
+
255
+ {/* Provider Lists */}
256
+ {stats.providers.map((p) => (
257
+ <ProviderSection key={p.provider} usage={p} />
258
+ ))}
259
+
260
+ {/* Legend Footer */}
261
+ <Box marginTop={1} paddingX={1} flexDirection="column">
262
+ <Text color={THEME.dim} italic>
263
+ • P / C = Prompt vs Completion tokens
264
+ </Text>
265
+ <Text color={THEME.dim} italic>
266
+ • % of Provider = How much this model contributed to that provider's
267
+ total cost/usage
268
+ </Text>
269
+ </Box>
270
+ </Box>
271
+ );
272
+ };
273
+
274
+ export default TokenUsage;
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ import { ModelSchema } from "../types/Model";
3
+
4
+ export const SettingsSchema = z.object({
5
+ lastOpened: z.iso.datetime().optional(),
6
+ ollamaUrl: z.string().default("http://localhost:11434"),
7
+ primaryModel: z.string().default("qwen3:8b|ollama"),
8
+ secondaryModel: z.string().default("llama3.1:latest|ollama"),
9
+ models: z.array(ModelSchema).default([]),
10
+ history: z.array(z.string()).default([]),
11
+ openrouter_api_key: z.string().optional(),
12
+ groq_api_key: z.string().optional(),
13
+ mistral_api_key: z.string().optional(),
14
+ cerebras_api_key: z.string().optional(),
15
+ google_api_key: z.string().optional(),
16
+ pluginSettings: z.any().default({}),
17
+ });
18
+
19
+ export type Config = z.infer<typeof SettingsSchema>;