@agentforge-ai/cli 0.4.2 → 0.5.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 (67) hide show
  1. package/dist/default/README.md +81 -81
  2. package/dist/default/convex/agents.ts +204 -0
  3. package/dist/default/convex/apiKeys.ts +133 -0
  4. package/dist/default/convex/cronJobs.ts +224 -0
  5. package/dist/default/convex/files.ts +103 -0
  6. package/dist/default/convex/folders.ts +110 -0
  7. package/dist/default/convex/heartbeat.ts +371 -0
  8. package/dist/default/convex/logs.ts +66 -0
  9. package/dist/default/convex/mastraIntegration.ts +184 -0
  10. package/dist/default/convex/mcpConnections.ts +127 -0
  11. package/dist/default/convex/messages.ts +90 -0
  12. package/dist/default/convex/projects.ts +114 -0
  13. package/dist/default/convex/sessions.ts +174 -0
  14. package/dist/default/convex/settings.ts +79 -0
  15. package/dist/default/convex/skills.ts +178 -0
  16. package/dist/default/convex/threads.ts +100 -0
  17. package/dist/default/convex/usage.ts +195 -0
  18. package/dist/default/convex/vault.ts +383 -0
  19. package/dist/default/dashboard/app/main.tsx +7 -3
  20. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  21. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  22. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  23. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  24. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  25. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  26. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  27. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  28. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  29. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  30. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  31. package/dist/default/dashboard/tsconfig.json +3 -2
  32. package/dist/default/dashboard/vite.config.ts +6 -0
  33. package/dist/index.js +279 -50
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/default/README.md +81 -81
  37. package/templates/default/convex/agents.ts +204 -0
  38. package/templates/default/convex/apiKeys.ts +133 -0
  39. package/templates/default/convex/cronJobs.ts +224 -0
  40. package/templates/default/convex/files.ts +103 -0
  41. package/templates/default/convex/folders.ts +110 -0
  42. package/templates/default/convex/heartbeat.ts +371 -0
  43. package/templates/default/convex/logs.ts +66 -0
  44. package/templates/default/convex/mastraIntegration.ts +184 -0
  45. package/templates/default/convex/mcpConnections.ts +127 -0
  46. package/templates/default/convex/messages.ts +90 -0
  47. package/templates/default/convex/projects.ts +114 -0
  48. package/templates/default/convex/sessions.ts +174 -0
  49. package/templates/default/convex/settings.ts +79 -0
  50. package/templates/default/convex/skills.ts +178 -0
  51. package/templates/default/convex/threads.ts +100 -0
  52. package/templates/default/convex/usage.ts +195 -0
  53. package/templates/default/convex/vault.ts +383 -0
  54. package/templates/default/dashboard/app/main.tsx +7 -3
  55. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  56. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  57. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  58. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  59. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  60. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  61. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  62. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  63. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  64. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  65. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  66. package/templates/default/dashboard/tsconfig.json +3 -2
  67. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -1,180 +1,137 @@
1
- import { createFileRoute } from "@tanstack/react-router";
2
- import { DashboardLayout } from "../components/DashboardLayout";
3
- import { useState } from "react";
4
- import { BarChart3, DollarSign, Cpu, Activity, TrendingUp, Calendar, Bot, Zap } from "lucide-react";
1
+ import { createFileRoute } from '@tanstack/react-router';
2
+ import { DashboardLayout } from '../components/DashboardLayout';
3
+ import { useMemo } from 'react';
4
+ import { useQuery } from 'convex/react';
5
+ import { api } from '@convex/_generated/api';
6
+ import { BarChart3, TrendingUp, DollarSign, Zap, Activity } from 'lucide-react';
5
7
 
6
- export const Route = createFileRoute("/usage")({ component: UsagePage });
8
+ export const Route = createFileRoute('/usage')({ component: UsagePage });
7
9
 
8
- interface UsageRecord {
9
- id: string; agentName: string; provider: string; model: string;
10
- promptTokens: number; completionTokens: number; totalTokens: number;
11
- cost: number; timestamp: number;
10
+ function StatCard({ icon: Icon, title, value, subtitle }: { icon: any; title: string; value: string; subtitle?: string }) {
11
+ return (
12
+ <div className="bg-card border border-border rounded-lg p-6 shadow-sm">
13
+ <div className="flex items-center justify-between mb-2">
14
+ <span className="text-sm font-medium text-muted-foreground">{title}</span>
15
+ <Icon className="w-5 h-5 text-primary" />
16
+ </div>
17
+ <p className="text-3xl font-bold text-foreground">{value}</p>
18
+ {subtitle && <p className="text-xs text-muted-foreground mt-1">{subtitle}</p>}
19
+ </div>
20
+ );
12
21
  }
13
22
 
14
23
  function UsagePage() {
15
- const [dateRange, setDateRange] = useState<"7d" | "30d" | "90d">("30d");
16
-
17
- const [records] = useState<UsageRecord[]>([
18
- { id: "u1", agentName: "Customer Support", provider: "openai", model: "gpt-4o", promptTokens: 12500, completionTokens: 8200, totalTokens: 20700, cost: 0.62, timestamp: Date.now() - 3600000 },
19
- { id: "u2", agentName: "Code Review", provider: "anthropic", model: "claude-3.5-sonnet", promptTokens: 45000, completionTokens: 15000, totalTokens: 60000, cost: 1.35, timestamp: Date.now() - 7200000 },
20
- { id: "u3", agentName: "Data Analyst", provider: "openrouter", model: "mixtral-8x7b", promptTokens: 8000, completionTokens: 4500, totalTokens: 12500, cost: 0.08, timestamp: Date.now() - 14400000 },
21
- { id: "u4", agentName: "Customer Support", provider: "openai", model: "gpt-4o-mini", promptTokens: 6000, completionTokens: 3200, totalTokens: 9200, cost: 0.09, timestamp: Date.now() - 28800000 },
22
- { id: "u5", agentName: "Research Agent", provider: "google", model: "gemini-pro", promptTokens: 22000, completionTokens: 11000, totalTokens: 33000, cost: 0.17, timestamp: Date.now() - 43200000 },
23
- { id: "u6", agentName: "Code Review", provider: "anthropic", model: "claude-3.5-sonnet", promptTokens: 38000, completionTokens: 12000, totalTokens: 50000, cost: 1.13, timestamp: Date.now() - 86400000 },
24
- { id: "u7", agentName: "Writer Agent", provider: "openai", model: "gpt-4o", promptTokens: 15000, completionTokens: 20000, totalTokens: 35000, cost: 1.05, timestamp: Date.now() - 172800000 },
25
- { id: "u8", agentName: "Data Analyst", provider: "xai", model: "grok-2", promptTokens: 10000, completionTokens: 5000, totalTokens: 15000, cost: 0.30, timestamp: Date.now() - 259200000 },
26
- ]);
27
-
28
- const totalTokens = records.reduce((s, r) => s + r.totalTokens, 0);
29
- const totalCost = records.reduce((s, r) => s + r.cost, 0);
30
- const uniqueAgents = new Set(records.map((r) => r.agentName)).size;
31
- const totalSessions = records.length;
32
-
33
- // Cost by provider
34
- const costByProvider: Record<string, number> = {};
35
- records.forEach((r) => { costByProvider[r.provider] = (costByProvider[r.provider] || 0) + r.cost; });
36
- const maxProviderCost = Math.max(...Object.values(costByProvider), 1);
37
- const providerColors: Record<string, string> = { openai: "bg-green-500", anthropic: "bg-orange-500", openrouter: "bg-blue-500", google: "bg-yellow-500", xai: "bg-purple-500" };
24
+ const stats = useQuery(api.usage.getStats, {});
25
+ const usageRecords = useQuery(api.usage.list, {}) ?? [];
38
26
 
39
- // Top agents by usage
40
- const agentUsage: Record<string, { tokens: number; cost: number }> = {};
41
- records.forEach((r) => {
42
- if (!agentUsage[r.agentName]) agentUsage[r.agentName] = { tokens: 0, cost: 0 };
43
- agentUsage[r.agentName].tokens += r.totalTokens;
44
- agentUsage[r.agentName].cost += r.cost;
45
- });
46
- const topAgents = Object.entries(agentUsage).sort((a, b) => b[1].tokens - a[1].tokens);
47
- const maxAgentTokens = topAgents.length > 0 ? topAgents[0][1].tokens : 1;
27
+ const totalTokens = stats?.totalTokens ?? 0;
28
+ const totalCost = stats?.totalCost ?? 0;
29
+ const totalRequests = stats?.totalRequests ?? 0;
30
+ const byProvider = stats?.byProvider ?? {};
31
+ const byModel = stats?.byModel ?? {};
48
32
 
49
- // Tokens over time (last 7 days)
50
- const days = Array.from({ length: 7 }, (_, i) => {
51
- const d = new Date(); d.setDate(d.getDate() - (6 - i));
52
- return { label: d.toLocaleDateString("en-US", { weekday: "short" }), date: d.toDateString() };
53
- });
54
- const tokensByDay = days.map((day) => {
55
- const dayTokens = records.filter((r) => new Date(r.timestamp).toDateString() === day.date).reduce((s, r) => s + r.totalTokens, 0);
56
- return { ...day, tokens: dayTokens };
57
- });
58
- const maxDayTokens = Math.max(...tokensByDay.map((d) => d.tokens), 1);
33
+ const providerEntries = useMemo(() => Object.entries(byProvider).sort((a: any, b: any) => b[1].tokens - a[1].tokens), [byProvider]);
34
+ const modelEntries = useMemo(() => Object.entries(byModel).sort((a: any, b: any) => b[1].tokens - a[1].tokens), [byModel]);
59
35
 
60
- const formatTokens = (n: number) => {
61
- if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
62
- if (n >= 1000) return (n / 1000).toFixed(1) + "K";
63
- return n.toString();
64
- };
36
+ // Calculate max for bar chart scaling
37
+ const maxProviderTokens = providerEntries.length > 0 ? Math.max(...providerEntries.map(([, v]: any) => v.tokens)) : 1;
38
+ const maxModelTokens = modelEntries.length > 0 ? Math.max(...modelEntries.map(([, v]: any) => v.tokens)) : 1;
65
39
 
66
40
  return (
67
41
  <DashboardLayout>
68
42
  <div className="space-y-6">
69
- <div className="flex items-center justify-between">
70
- <div><h1 className="text-3xl font-bold">Usage & Metrics</h1><p className="text-muted-foreground mt-1">Monitor token usage, costs, and agent performance</p></div>
71
- <div className="flex items-center gap-1 bg-card border rounded-lg p-1">
72
- {(["7d", "30d", "90d"] as const).map((range) => (
73
- <button key={range} onClick={() => setDateRange(range)} className={`px-3 py-1.5 text-sm rounded-md transition-colors ${dateRange === range ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`}>
74
- {range === "7d" ? "7 Days" : range === "30d" ? "30 Days" : "90 Days"}
75
- </button>
76
- ))}
77
- </div>
43
+ <div>
44
+ <h1 className="text-3xl font-bold">Usage</h1>
45
+ <p className="text-muted-foreground">Monitor token consumption, costs, and API usage across providers.</p>
78
46
  </div>
79
47
 
80
- {/* Summary Cards */}
81
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
82
- {[
83
- { label: "Total Tokens", value: formatTokens(totalTokens), icon: Zap, color: "text-blue-400", sub: `${records.length} requests` },
84
- { label: "Total Cost", value: `$${totalCost.toFixed(2)}`, icon: DollarSign, color: "text-green-400", sub: `Avg $${(totalCost / Math.max(records.length, 1)).toFixed(3)}/req` },
85
- { label: "Active Agents", value: uniqueAgents.toString(), icon: Bot, color: "text-purple-400", sub: "Using LLM providers" },
86
- { label: "Total Requests", value: totalSessions.toString(), icon: Activity, color: "text-orange-400", sub: "In selected period" },
87
- ].map((stat) => (
88
- <div key={stat.label} className="bg-card border rounded-lg p-5">
89
- <div className="flex items-center justify-between mb-3">
90
- <span className="text-sm text-muted-foreground">{stat.label}</span>
91
- <stat.icon className={`h-5 w-5 ${stat.color}`} />
92
- </div>
93
- <p className="text-2xl font-bold">{stat.value}</p>
94
- <p className="text-xs text-muted-foreground mt-1">{stat.sub}</p>
95
- </div>
96
- ))}
48
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
49
+ <StatCard icon={Zap} title="Total Tokens" value={totalTokens.toLocaleString()} />
50
+ <StatCard icon={DollarSign} title="Total Cost" value={`$${totalCost.toFixed(4)}`} />
51
+ <StatCard icon={Activity} title="Total Requests" value={totalRequests.toLocaleString()} />
52
+ <StatCard icon={TrendingUp} title="Avg Tokens/Request" value={totalRequests > 0 ? Math.round(totalTokens / totalRequests).toLocaleString() : '0'} />
97
53
  </div>
98
54
 
99
- {/* Charts Row */}
100
55
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
101
- {/* Tokens Over Time */}
102
- <div className="bg-card border rounded-lg p-5">
103
- <h3 className="text-sm font-medium mb-4 flex items-center gap-2"><BarChart3 className="h-4 w-4 text-muted-foreground" />Tokens Over Time</h3>
104
- <div className="flex items-end gap-2 h-40">
105
- {tokensByDay.map((day) => (
106
- <div key={day.label} className="flex-1 flex flex-col items-center gap-1">
107
- <span className="text-xs text-muted-foreground">{day.tokens > 0 ? formatTokens(day.tokens) : ""}</span>
108
- <div className="w-full bg-primary/20 rounded-t-sm relative" style={{ height: `${Math.max((day.tokens / maxDayTokens) * 120, 4)}px` }}>
109
- <div className="absolute inset-0 bg-primary rounded-t-sm" style={{ height: "100%" }} />
56
+ {/* By Provider */}
57
+ <div className="bg-card border border-border rounded-lg p-6 shadow-sm">
58
+ <h2 className="text-lg font-semibold mb-4 flex items-center gap-2"><BarChart3 className="w-5 h-5 text-primary" /> Usage by Provider</h2>
59
+ {providerEntries.length === 0 ? (
60
+ <div className="text-center py-8 text-muted-foreground">No usage data yet. Start chatting with your agents.</div>
61
+ ) : (
62
+ <div className="space-y-3">
63
+ {providerEntries.map(([provider, data]: any) => (
64
+ <div key={provider}>
65
+ <div className="flex items-center justify-between mb-1">
66
+ <span className="text-sm font-medium">{provider}</span>
67
+ <div className="text-xs text-muted-foreground">
68
+ {data.tokens.toLocaleString()} tokens &middot; ${data.cost.toFixed(4)} &middot; {data.requests} req
69
+ </div>
70
+ </div>
71
+ <div className="w-full bg-muted rounded-full h-2">
72
+ <div className="bg-primary rounded-full h-2 transition-all" style={{ width: `${(data.tokens / maxProviderTokens) * 100}%` }} />
73
+ </div>
110
74
  </div>
111
- <span className="text-xs text-muted-foreground">{day.label}</span>
112
- </div>
113
- ))}
114
- </div>
115
- </div>
116
-
117
- {/* Cost by Provider */}
118
- <div className="bg-card border rounded-lg p-5">
119
- <h3 className="text-sm font-medium mb-4 flex items-center gap-2"><DollarSign className="h-4 w-4 text-muted-foreground" />Cost by Provider</h3>
120
- <div className="space-y-3">
121
- {Object.entries(costByProvider).sort((a, b) => b[1] - a[1]).map(([provider, cost]) => (
122
- <div key={provider} className="space-y-1">
123
- <div className="flex items-center justify-between text-sm">
124
- <span className="capitalize">{provider}</span>
125
- <span className="font-medium">${cost.toFixed(2)}</span>
126
- </div>
127
- <div className="w-full h-2 bg-accent rounded-full overflow-hidden">
128
- <div className={`h-full rounded-full ${providerColors[provider] || "bg-primary"}`} style={{ width: `${(cost / maxProviderCost) * 100}%` }} />
129
- </div>
130
- </div>
131
- ))}
132
- </div>
75
+ ))}
76
+ </div>
77
+ )}
133
78
  </div>
134
- </div>
135
79
 
136
- {/* Top Agents */}
137
- <div className="bg-card border rounded-lg p-5">
138
- <h3 className="text-sm font-medium mb-4 flex items-center gap-2"><TrendingUp className="h-4 w-4 text-muted-foreground" />Top Agents by Usage</h3>
139
- <div className="space-y-3">
140
- {topAgents.map(([name, data], i) => (
141
- <div key={name} className="flex items-center gap-4">
142
- <span className="text-sm text-muted-foreground w-6 text-right">#{i + 1}</span>
143
- <div className="flex-1">
144
- <div className="flex items-center justify-between text-sm mb-1">
145
- <span className="font-medium">{name}</span>
146
- <span className="text-muted-foreground">{formatTokens(data.tokens)} tokens · ${data.cost.toFixed(2)}</span>
147
- </div>
148
- <div className="w-full h-1.5 bg-accent rounded-full overflow-hidden">
149
- <div className="h-full bg-primary rounded-full" style={{ width: `${(data.tokens / maxAgentTokens) * 100}%` }} />
80
+ {/* By Model */}
81
+ <div className="bg-card border border-border rounded-lg p-6 shadow-sm">
82
+ <h2 className="text-lg font-semibold mb-4 flex items-center gap-2"><BarChart3 className="w-5 h-5 text-primary" /> Usage by Model</h2>
83
+ {modelEntries.length === 0 ? (
84
+ <div className="text-center py-8 text-muted-foreground">No usage data yet.</div>
85
+ ) : (
86
+ <div className="space-y-3">
87
+ {modelEntries.map(([model, data]: any) => (
88
+ <div key={model}>
89
+ <div className="flex items-center justify-between mb-1">
90
+ <span className="text-sm font-medium font-mono">{model}</span>
91
+ <div className="text-xs text-muted-foreground">
92
+ {data.tokens.toLocaleString()} tokens &middot; ${data.cost.toFixed(4)}
93
+ </div>
94
+ </div>
95
+ <div className="w-full bg-muted rounded-full h-2">
96
+ <div className="bg-primary rounded-full h-2 transition-all" style={{ width: `${(data.tokens / maxModelTokens) * 100}%` }} />
97
+ </div>
150
98
  </div>
151
- </div>
99
+ ))}
152
100
  </div>
153
- ))}
101
+ )}
154
102
  </div>
155
103
  </div>
156
104
 
157
105
  {/* Recent Usage Records */}
158
- <div className="bg-card border rounded-lg overflow-hidden">
159
- <div className="px-5 py-4 border-b"><h3 className="text-sm font-medium flex items-center gap-2"><Calendar className="h-4 w-4 text-muted-foreground" />Recent Usage Records</h3></div>
160
- <table className="w-full text-sm">
161
- <thead><tr className="border-b text-muted-foreground text-left"><th className="px-5 py-3 font-medium">Agent</th><th className="px-5 py-3 font-medium">Provider</th><th className="px-5 py-3 font-medium">Model</th><th className="px-5 py-3 font-medium text-right">Prompt</th><th className="px-5 py-3 font-medium text-right">Completion</th><th className="px-5 py-3 font-medium text-right">Total</th><th className="px-5 py-3 font-medium text-right">Cost</th><th className="px-5 py-3 font-medium">Time</th></tr></thead>
162
- <tbody>
163
- {records.map((r) => (
164
- <tr key={r.id} className="border-b hover:bg-accent/50">
165
- <td className="px-5 py-3 font-medium">{r.agentName}</td>
166
- <td className="px-5 py-3"><span className="capitalize px-2 py-0.5 bg-accent rounded text-xs">{r.provider}</span></td>
167
- <td className="px-5 py-3 text-muted-foreground">{r.model}</td>
168
- <td className="px-5 py-3 text-right text-muted-foreground">{r.promptTokens.toLocaleString()}</td>
169
- <td className="px-5 py-3 text-right text-muted-foreground">{r.completionTokens.toLocaleString()}</td>
170
- <td className="px-5 py-3 text-right font-medium">{r.totalTokens.toLocaleString()}</td>
171
- <td className="px-5 py-3 text-right font-medium text-green-400">${r.cost.toFixed(3)}</td>
172
- <td className="px-5 py-3 text-muted-foreground">{new Date(r.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}</td>
106
+ {usageRecords.length > 0 && (
107
+ <div className="bg-card border border-border rounded-lg overflow-hidden">
108
+ <div className="p-4 border-b border-border">
109
+ <h2 className="text-lg font-semibold">Recent API Calls</h2>
110
+ </div>
111
+ <table className="w-full text-sm">
112
+ <thead className="bg-muted/50">
113
+ <tr>
114
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Model</th>
115
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Provider</th>
116
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Tokens</th>
117
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Cost</th>
118
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Time</th>
173
119
  </tr>
174
- ))}
175
- </tbody>
176
- </table>
177
- </div>
120
+ </thead>
121
+ <tbody>
122
+ {usageRecords.slice(0, 20).map((record: any) => (
123
+ <tr key={record._id} className="border-t border-border hover:bg-muted/30">
124
+ <td className="px-4 py-3 font-mono text-xs">{record.model}</td>
125
+ <td className="px-4 py-3">{record.provider}</td>
126
+ <td className="px-4 py-3">{record.totalTokens.toLocaleString()}</td>
127
+ <td className="px-4 py-3">${(record.cost || 0).toFixed(4)}</td>
128
+ <td className="px-4 py-3 text-xs text-muted-foreground">{new Date(record.timestamp).toLocaleString()}</td>
129
+ </tr>
130
+ ))}
131
+ </tbody>
132
+ </table>
133
+ </div>
134
+ )}
178
135
  </div>
179
136
  </DashboardLayout>
180
137
  );
@@ -16,9 +16,10 @@
16
16
  "noFallthroughCasesInSwitch": true,
17
17
  "baseUrl": ".",
18
18
  "paths": {
19
- "~/*": ["./app/*"]
19
+ "~/*": ["./app/*"],
20
+ "@convex/*": ["../convex/*"]
20
21
  }
21
22
  },
22
- "include": ["app"],
23
+ "include": ["app", "../convex"],
23
24
  "exclude": ["node_modules", "dist"]
24
25
  }
@@ -1,9 +1,15 @@
1
1
  import { defineConfig } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
3
  import tsconfigPaths from "vite-tsconfig-paths";
4
+ import path from "path";
4
5
 
5
6
  export default defineConfig({
6
7
  plugins: [react(), tsconfigPaths()],
8
+ resolve: {
9
+ alias: {
10
+ "@convex": path.resolve(__dirname, "../convex"),
11
+ },
12
+ },
7
13
  build: {
8
14
  outDir: "dist",
9
15
  sourcemap: true,