@agentforge-ai/cli 0.4.3 → 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.
- package/dist/default/convex/agents.ts +204 -0
- package/dist/default/convex/apiKeys.ts +133 -0
- package/dist/default/convex/cronJobs.ts +224 -0
- package/dist/default/convex/files.ts +103 -0
- package/dist/default/convex/folders.ts +110 -0
- package/dist/default/convex/heartbeat.ts +371 -0
- package/dist/default/convex/logs.ts +66 -0
- package/dist/default/convex/mastraIntegration.ts +184 -0
- package/dist/default/convex/mcpConnections.ts +127 -0
- package/dist/default/convex/messages.ts +90 -0
- package/dist/default/convex/projects.ts +114 -0
- package/dist/default/convex/sessions.ts +174 -0
- package/dist/default/convex/settings.ts +79 -0
- package/dist/default/convex/skills.ts +178 -0
- package/dist/default/convex/threads.ts +100 -0
- package/dist/default/convex/usage.ts +195 -0
- package/dist/default/convex/vault.ts +383 -0
- package/dist/default/dashboard/app/main.tsx +7 -3
- package/dist/default/dashboard/app/routes/agents.tsx +103 -161
- package/dist/default/dashboard/app/routes/chat.tsx +163 -317
- package/dist/default/dashboard/app/routes/connections.tsx +247 -386
- package/dist/default/dashboard/app/routes/cron.tsx +127 -286
- package/dist/default/dashboard/app/routes/files.tsx +184 -167
- package/dist/default/dashboard/app/routes/index.tsx +63 -96
- package/dist/default/dashboard/app/routes/projects.tsx +106 -225
- package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
- package/dist/default/dashboard/app/routes/settings.tsx +316 -532
- package/dist/default/dashboard/app/routes/skills.tsx +329 -216
- package/dist/default/dashboard/app/routes/usage.tsx +107 -150
- package/dist/default/dashboard/tsconfig.json +3 -2
- package/dist/default/dashboard/vite.config.ts +6 -0
- package/dist/index.js +256 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/convex/agents.ts +204 -0
- package/templates/default/convex/apiKeys.ts +133 -0
- package/templates/default/convex/cronJobs.ts +224 -0
- package/templates/default/convex/files.ts +103 -0
- package/templates/default/convex/folders.ts +110 -0
- package/templates/default/convex/heartbeat.ts +371 -0
- package/templates/default/convex/logs.ts +66 -0
- package/templates/default/convex/mastraIntegration.ts +184 -0
- package/templates/default/convex/mcpConnections.ts +127 -0
- package/templates/default/convex/messages.ts +90 -0
- package/templates/default/convex/projects.ts +114 -0
- package/templates/default/convex/sessions.ts +174 -0
- package/templates/default/convex/settings.ts +79 -0
- package/templates/default/convex/skills.ts +178 -0
- package/templates/default/convex/threads.ts +100 -0
- package/templates/default/convex/usage.ts +195 -0
- package/templates/default/convex/vault.ts +383 -0
- package/templates/default/dashboard/app/main.tsx +7 -3
- package/templates/default/dashboard/app/routes/agents.tsx +103 -161
- package/templates/default/dashboard/app/routes/chat.tsx +163 -317
- package/templates/default/dashboard/app/routes/connections.tsx +247 -386
- package/templates/default/dashboard/app/routes/cron.tsx +127 -286
- package/templates/default/dashboard/app/routes/files.tsx +184 -167
- package/templates/default/dashboard/app/routes/index.tsx +63 -96
- package/templates/default/dashboard/app/routes/projects.tsx +106 -225
- package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
- package/templates/default/dashboard/app/routes/settings.tsx +316 -532
- package/templates/default/dashboard/app/routes/skills.tsx +329 -216
- package/templates/default/dashboard/app/routes/usage.tsx +107 -150
- package/templates/default/dashboard/tsconfig.json +3 -2
- package/templates/default/dashboard/vite.config.ts +6 -0
|
@@ -1,180 +1,137 @@
|
|
|
1
|
-
import { createFileRoute } from
|
|
2
|
-
import { DashboardLayout } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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(
|
|
8
|
+
export const Route = createFileRoute('/usage')({ component: UsagePage });
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
50
|
-
const
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
70
|
-
<
|
|
71
|
-
<
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
{/*
|
|
102
|
-
<div className="bg-card border rounded-lg p-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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 · ${data.cost.toFixed(4)} · {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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<div
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 · ${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
|
-
|
|
99
|
+
))}
|
|
152
100
|
</div>
|
|
153
|
-
)
|
|
101
|
+
)}
|
|
154
102
|
</div>
|
|
155
103
|
</div>
|
|
156
104
|
|
|
157
105
|
{/* Recent Usage Records */}
|
|
158
|
-
|
|
159
|
-
<div className="
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
<
|
|
170
|
-
<
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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,
|