@frogbotai/plugin-usage-reports 0.12.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FrogBot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @frogbotai/plugin-usage-reports
2
+
3
+ Add adapter-neutral usage analytics to FrogBot.
4
+
5
+ ```ts
6
+ import { usageReportsPlugin } from '@frogbotai/plugin-usage-reports';
7
+ import { buildConfig } from 'frogbot';
8
+
9
+ export default buildConfig({
10
+ secret: process.env.FROGBOT_SECRET!,
11
+ db: databaseAdapter,
12
+ collections: [{ slug: 'users', auth: true, fields: [] }],
13
+ ai: { providers },
14
+ plugins: [usageReportsPlugin()],
15
+ });
16
+ ```
17
+
18
+ The plugin adds a Usage Analytics admin view with the Firmware date range control and sortable Models and Users tables. Reports page through the marker-resolved usage collection with Payload's local API and aggregate in memory, so SQLite, PostgreSQL, and MongoDB use the same path.
19
+
20
+ The usage-log list gains Payload's experimental visual grouping and export-only CSV support from `@payloadcms/plugin-import-export`. Set `rawExport: false` to omit import/export integration. Set `pageSize` to change the local API batch size from its default of `5000`.
21
+
22
+ The authenticated `GET /api/usage/report` endpoint accepts `groupBy=model|user|day|apiKey`, `from`, and `to`. API-key groups contain data only when an attribution plugin, such as `@frogbotai/plugin-api-keys`, populated the `apiKey` relationship before usage was logged.
@@ -0,0 +1,4 @@
1
+ import './styles.css';
2
+ export declare function UsageReportsNavLink(): import("react/jsx-runtime").JSX.Element;
3
+ export declare function UsageReports(): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=UsageReports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageReports.d.ts","sourceRoot":"","sources":["../../src/client/UsageReports.tsx"],"names":[],"mappings":"AAMA,OAAO,cAAc,CAAC;AAqCtB,wBAAgB,mBAAmB,4CAGlC;AAED,wBAAgB,YAAY,4CA+F3B"}
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useConfig } from '@payloadcms/ui';
4
+ import { useEffect, useState } from 'react';
5
+ import './styles.css';
6
+ const presets = [
7
+ { label: 'Last 7 days', days: 7 },
8
+ { label: 'Last 30 days', days: 30 },
9
+ { label: 'Last 90 days', days: 90 },
10
+ ];
11
+ function rangeForDays(days, label) {
12
+ const to = new Date();
13
+ const from = new Date(to);
14
+ from.setUTCDate(from.getUTCDate() - days);
15
+ return { from: from.toISOString(), to: to.toISOString(), label };
16
+ }
17
+ function dateInput(value) {
18
+ return value.slice(0, 10);
19
+ }
20
+ function customRange(from, to) {
21
+ return {
22
+ from: new Date(`${from}T00:00:00.000Z`).toISOString(),
23
+ to: new Date(`${to}T23:59:59.999Z`).toISOString(),
24
+ label: `${from} - ${to}`,
25
+ };
26
+ }
27
+ function formatTokens(value) {
28
+ if (value >= 1_000_000_000)
29
+ return `${(value / 1_000_000_000).toFixed(1)}B`;
30
+ if (value >= 1_000_000)
31
+ return `${(value / 1_000_000).toFixed(1)}M`;
32
+ if (value >= 1_000)
33
+ return `${(value / 1_000).toFixed(1)}K`;
34
+ return value.toLocaleString();
35
+ }
36
+ export function UsageReportsNavLink() {
37
+ const { config } = useConfig();
38
+ return _jsx("a", { className: "usage-reports-nav-link", href: `${config.routes.admin}/usage-analytics`, children: "Usage Analytics" });
39
+ }
40
+ export function UsageReports() {
41
+ const { config } = useConfig();
42
+ const [groupBy, setGroupBy] = useState('model');
43
+ const [range, setRange] = useState(() => rangeForDays(30, 'Last 30 days'));
44
+ const [showCustom, setShowCustom] = useState(false);
45
+ const [customFrom, setCustomFrom] = useState(dateInput(range.from));
46
+ const [customTo, setCustomTo] = useState(dateInput(range.to));
47
+ const [report, setReport] = useState();
48
+ const [error, setError] = useState();
49
+ const [loading, setLoading] = useState(true);
50
+ const [sortField, setSortField] = useState(groupBy === 'model' ? 'totalTokens' : 'costUSD');
51
+ const [ascending, setAscending] = useState(false);
52
+ useEffect(() => {
53
+ const controller = new AbortController();
54
+ const params = new URLSearchParams({ groupBy, from: range.from, to: range.to });
55
+ setLoading(true);
56
+ setError(undefined);
57
+ fetch(`${config.routes.api}/usage/report?${params}`, { signal: controller.signal })
58
+ .then(async (response) => {
59
+ if (!response.ok)
60
+ throw new Error(`${response.status} ${response.statusText}`);
61
+ setReport(await response.json());
62
+ })
63
+ .catch((reason) => {
64
+ if (!controller.signal.aborted)
65
+ setError(reason instanceof Error ? reason.message : 'Failed to load usage report');
66
+ })
67
+ .finally(() => {
68
+ if (!controller.signal.aborted)
69
+ setLoading(false);
70
+ });
71
+ return () => controller.abort();
72
+ }, [config.routes.api, groupBy, range]);
73
+ const rows = [...(report?.rows ?? [])].sort((a, b) => {
74
+ const left = a[sortField];
75
+ const right = b[sortField];
76
+ const result = typeof left === 'string'
77
+ ? left.localeCompare(String(right))
78
+ : left - Number(right);
79
+ return ascending ? result : -result;
80
+ });
81
+ const columns = [
82
+ { field: 'label', label: groupBy === 'model' ? 'Model' : 'User', value: (row) => row.label },
83
+ { field: 'requestCount', label: 'Requests', value: (row) => row.requestCount.toLocaleString() },
84
+ { field: 'totalTokens', label: 'Total Tokens', value: (row) => formatTokens(row.totalTokens) },
85
+ { field: 'inputTokens', label: 'Input', value: (row) => formatTokens(row.inputTokens) },
86
+ { field: 'outputTokens', label: 'Output', value: (row) => formatTokens(row.outputTokens) },
87
+ { field: 'cachedInputTokens', label: 'Cached', value: (row) => formatTokens(row.cachedInputTokens) },
88
+ { field: 'reasoningTokens', label: 'Reasoning', value: (row) => formatTokens(row.reasoningTokens) },
89
+ { field: 'costUSD', label: 'Cost', value: (row) => `$${row.costUSD.toFixed(2)}` },
90
+ ];
91
+ function sort(field) {
92
+ if (sortField === field)
93
+ setAscending(!ascending);
94
+ else {
95
+ setSortField(field);
96
+ setAscending(false);
97
+ }
98
+ }
99
+ return (_jsxs("div", { className: "usage-reports", children: [_jsxs("header", { className: "usage-reports__header", children: [_jsxs("div", { children: [_jsx("h1", { children: "Usage Analytics" }), _jsx("p", { children: range.label })] }), _jsxs("div", { className: "usage-reports__ranges", children: [presets.map((preset) => _jsx("button", { type: "button", onClick: () => { setShowCustom(false); setRange(rangeForDays(preset.days, preset.label)); }, children: preset.label }, preset.days)), _jsx("button", { type: "button", onClick: () => setShowCustom(!showCustom), children: "Custom" })] })] }), showCustom && _jsxs("div", { className: "usage-reports__custom", children: [_jsxs("label", { children: ["From", _jsx("input", { type: "date", value: customFrom, onChange: (event) => setCustomFrom(event.target.value) })] }), _jsxs("label", { children: ["To", _jsx("input", { type: "date", value: customTo, onChange: (event) => setCustomTo(event.target.value) })] }), _jsx("button", { type: "button", disabled: !customFrom || !customTo || customFrom > customTo, onClick: () => { setRange(customRange(customFrom, customTo)); setShowCustom(false); }, children: "Apply" })] }), _jsx("nav", { className: "usage-reports__tabs", "aria-label": "Usage report", children: ['model', 'user'].map((group) => _jsx("button", { className: groupBy === group ? 'active' : '', type: "button", onClick: () => { setGroupBy(group); setSortField(group === 'model' ? 'totalTokens' : 'costUSD'); setAscending(false); }, children: group === 'model' ? 'Models' : 'Users' }, group)) }), _jsxs("section", { className: "usage-reports__card", children: [_jsxs("div", { className: "usage-reports__totals", children: [_jsxs("span", { children: [_jsx("strong", { children: report?.totals.requestCount.toLocaleString() ?? '0' }), " requests"] }), _jsxs("span", { children: [_jsx("strong", { children: formatTokens(report?.totals.totalTokens ?? 0) }), " tokens"] }), _jsxs("span", { children: [_jsxs("strong", { children: ["$", (report?.totals.costUSD ?? 0).toFixed(2)] }), " cost"] })] }), loading && _jsx("p", { className: "usage-reports__state", children: "Loading usage..." }), error && _jsxs("p", { className: "usage-reports__state usage-reports__error", children: ["Failed to load: ", error] }), !loading && !error && rows.length === 0 && _jsx("p", { className: "usage-reports__state", children: "No usage in this date range." }), !loading && !error && rows.length > 0 && _jsx("div", { className: "usage-reports__table-wrap", children: _jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column) => _jsx("th", { children: _jsxs("button", { type: "button", onClick: () => sort(column.field), children: [column.label, sortField === column.field ? (ascending ? ' ▲' : ' ▼') : ''] }) }, column.field)) }) }), _jsx("tbody", { children: rows.map((row) => _jsx("tr", { children: columns.map((column) => _jsx("td", { children: column.value(row) }, column.field)) }, row.key)) })] }) })] })] }));
100
+ }
101
+ //# sourceMappingURL=UsageReports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageReports.js","sourceRoot":"","sources":["../../src/client/UsageReports.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG5C,OAAO,cAAc,CAAC;AAKtB,MAAM,OAAO,GAAG;IACd,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE;IACjC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE;IACnC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE;CACpC,CAAC;AAEF,SAAS,YAAY,CAAC,IAAY,EAAE,KAAa;IAC/C,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,EAAU;IAC3C,OAAO;QACL,IAAI,EAAE,IAAI,IAAI,CAAC,GAAG,IAAI,gBAAgB,CAAC,CAAC,WAAW,EAAE;QACrD,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,WAAW,EAAE;QACjD,KAAK,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,KAAK,IAAI,aAAa;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5E,IAAI,KAAK,IAAI,SAAS;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpE,IAAI,KAAK,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5D,OAAO,KAAK,CAAC,cAAc,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAC/B,OAAO,YAAG,SAAS,EAAC,wBAAwB,EAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,kBAAkB,gCAAqB,CAAC;AACnH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAC/B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAA8C,OAAO,CAAC,CAAC;IAC7F,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAY,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IACtF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,EAAe,CAAC;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAU,CAAC;IAC7C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAY,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACvG,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAChF,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,iBAAiB,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;aAChF,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YACvB,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YAC/E,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAiB,CAAC,CAAC;QAClD,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,MAAe,EAAE,EAAE;YACzB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,QAAQ,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC;QACrH,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QACL,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAExC,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACnD,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QAC1B,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,IAAI,KAAK,QAAQ;YACrC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAuF;QAClG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE;QAC5F,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE;QAC/F,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;QAC9F,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;QACvF,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE;QAC1F,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE;QACpG,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE;QACnG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;KAClF,CAAC;IAEF,SAAS,IAAI,CAAC,KAAgB;QAC5B,IAAI,SAAS,KAAK,KAAK;YAAE,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC;aAC7C,CAAC;YACJ,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,eAAe,aAC5B,kBAAQ,SAAS,EAAC,uBAAuB,aACvC,0BACE,2CAAwB,EACxB,sBAAI,KAAK,CAAC,KAAK,GAAK,IAChB,EACN,eAAK,SAAS,EAAC,uBAAuB,aACnC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,iBAA0B,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAG,MAAM,CAAC,KAAK,IAArI,MAAM,CAAC,IAAI,CAAoI,CAAC,EACtL,iBAAQ,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,uBAAiB,IAC5E,IACC,EACR,UAAU,IAAI,eAAK,SAAS,EAAC,uBAAuB,aACnD,oCAAW,gBAAO,IAAI,EAAC,MAAM,EAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAI,IAAQ,EACnH,kCAAS,gBAAO,IAAI,EAAC,MAAM,EAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAI,IAAQ,EAC7G,iBAAQ,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,UAAU,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,sBAAgB,IACpL,EACN,cAAK,SAAS,EAAC,qBAAqB,gBAAY,cAAc,YAC1D,CAAC,OAAO,EAAE,MAAM,CAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAQ,SAAS,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAc,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,IAArL,KAAK,CAA0L,CAAC,GACpS,EACN,mBAAS,SAAS,EAAC,qBAAqB,aACtC,eAAK,SAAS,EAAC,uBAAuB,aACpC,2BAAM,2BAAS,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,cAAc,EAAE,IAAI,GAAG,GAAU,iBAAgB,EAC5F,2BAAM,2BAAS,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC,GAAU,eAAc,EACpF,2BAAM,kCAAU,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAU,aAAY,IAC1E,EACL,OAAO,IAAI,YAAG,SAAS,EAAC,sBAAsB,iCAAqB,EACnE,KAAK,IAAI,aAAG,SAAS,EAAC,2CAA2C,iCAAkB,KAAK,IAAK,EAC7F,CAAC,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,YAAG,SAAS,EAAC,sBAAsB,6CAAiC,EAC/G,CAAC,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,cAAK,SAAS,EAAC,2BAA2B,YAAC,4BACnF,0BAAO,uBAAK,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,uBAAuB,kBAAQ,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAG,MAAM,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAU,IAA3J,MAAM,CAAC,KAAK,CAAoJ,CAAC,GAAM,GAAQ,EAC5N,0BAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,uBAAmB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,uBAAwB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAhC,MAAM,CAAC,KAAK,CAA0B,CAAC,IAAlF,GAAG,CAAC,GAAG,CAAiF,CAAC,GAAS,IAC/H,GAAM,IACN,IACN,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=UsageReports.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageReports.spec.d.ts","sourceRoot":"","sources":["../../src/client/UsageReports.spec.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import { UsageReports } from './UsageReports.js';
5
+ vi.mock('@payloadcms/ui', () => ({
6
+ useConfig: () => ({ config: { routes: { admin: '/admin', api: '/api' } } }),
7
+ }));
8
+ const modelRows = [
9
+ { key: 'small', label: 'Small', requestCount: 1, inputTokens: 1, outputTokens: 1, cachedInputTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, totalTokens: 2, costUSD: 0.1 },
10
+ { key: 'large', label: 'Large', requestCount: 2, inputTokens: 6, outputTokens: 4, cachedInputTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, totalTokens: 10, costUSD: 0.5 },
11
+ ];
12
+ describe('UsageReports', () => {
13
+ afterEach(() => vi.unstubAllGlobals());
14
+ it('loads the Firmware models/users and date range slice', async () => {
15
+ const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({
16
+ groupBy: 'model',
17
+ from: '2026-01-01T00:00:00.000Z',
18
+ to: '2026-02-01T00:00:00.000Z',
19
+ rows: modelRows,
20
+ totals: { requestCount: 3, inputTokens: 7, outputTokens: 5, cachedInputTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, totalTokens: 12, costUSD: 0.6 },
21
+ }), { status: 200 }));
22
+ vi.stubGlobal('fetch', fetch);
23
+ render(_jsx(UsageReports, {}));
24
+ expect(screen.getByRole('heading', { name: 'Usage Analytics' })).toBeTruthy();
25
+ expect(screen.getByRole('button', { name: 'Last 7 days' })).toBeTruthy();
26
+ await waitFor(() => expect(screen.getByText('Large')).toBeTruthy());
27
+ expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/usage/report?groupBy=model'), expect.any(Object));
28
+ fireEvent.click(screen.getByRole('button', { name: 'Users' }));
29
+ await waitFor(() => expect(fetch).toHaveBeenCalledWith(expect.stringContaining('groupBy=user'), expect.any(Object)));
30
+ fireEvent.click(screen.getByRole('button', { name: 'Custom' }));
31
+ expect(screen.getByLabelText('From')).toBeTruthy();
32
+ expect(screen.getByLabelText('To')).toBeTruthy();
33
+ });
34
+ });
35
+ //# sourceMappingURL=UsageReports.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageReports.spec.js","sourceRoot":"","sources":["../../src/client/UsageReports.spec.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ .usage-reports {
2
+ display: grid;
3
+ gap: 24px;
4
+ padding: 40px;
5
+ }
6
+
7
+ .usage-reports__header {
8
+ align-items: flex-start;
9
+ display: flex;
10
+ gap: 24px;
11
+ justify-content: space-between;
12
+ }
13
+
14
+ .usage-reports__header h1,
15
+ .usage-reports__header p {
16
+ margin: 0;
17
+ }
18
+
19
+ .usage-reports__header p {
20
+ color: var(--theme-elevation-500);
21
+ margin-top: 6px;
22
+ }
23
+
24
+ .usage-reports__ranges,
25
+ .usage-reports__tabs,
26
+ .usage-reports__custom {
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ gap: 8px;
30
+ }
31
+
32
+ .usage-reports button,
33
+ .usage-reports-nav-link {
34
+ background: var(--theme-elevation-100);
35
+ border: 1px solid var(--theme-elevation-200);
36
+ border-radius: var(--style-radius-s);
37
+ color: var(--theme-text);
38
+ cursor: pointer;
39
+ padding: 9px 14px;
40
+ }
41
+
42
+ .usage-reports-nav-link {
43
+ display: block;
44
+ margin: 4px 8px;
45
+ text-decoration: none;
46
+ }
47
+
48
+ .usage-reports__tabs button.active {
49
+ background: var(--theme-elevation-800);
50
+ color: var(--theme-elevation-0);
51
+ }
52
+
53
+ .usage-reports__custom {
54
+ align-items: end;
55
+ background: var(--theme-elevation-50);
56
+ border: 1px solid var(--theme-elevation-150);
57
+ border-radius: var(--style-radius-m);
58
+ padding: 16px;
59
+ }
60
+
61
+ .usage-reports__custom label {
62
+ display: grid;
63
+ gap: 6px;
64
+ }
65
+
66
+ .usage-reports__custom input {
67
+ background: var(--theme-input-bg);
68
+ border: 1px solid var(--theme-elevation-200);
69
+ border-radius: var(--style-radius-s);
70
+ color: var(--theme-text);
71
+ padding: 9px;
72
+ }
73
+
74
+ .usage-reports__card {
75
+ background: var(--theme-elevation-50);
76
+ border: 1px solid var(--theme-elevation-150);
77
+ border-radius: var(--style-radius-m);
78
+ overflow: hidden;
79
+ }
80
+
81
+ .usage-reports__totals {
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ gap: 32px;
85
+ padding: 20px;
86
+ }
87
+
88
+ .usage-reports__totals span {
89
+ color: var(--theme-elevation-500);
90
+ display: grid;
91
+ gap: 4px;
92
+ }
93
+
94
+ .usage-reports__totals strong {
95
+ color: var(--theme-text);
96
+ font-size: 22px;
97
+ }
98
+
99
+ .usage-reports__table-wrap {
100
+ overflow-x: auto;
101
+ }
102
+
103
+ .usage-reports table {
104
+ border-collapse: collapse;
105
+ min-width: 900px;
106
+ width: 100%;
107
+ }
108
+
109
+ .usage-reports th,
110
+ .usage-reports td {
111
+ border-top: 1px solid var(--theme-elevation-150);
112
+ padding: 13px 16px;
113
+ text-align: right;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ .usage-reports th:first-child,
118
+ .usage-reports td:first-child {
119
+ font-weight: 600;
120
+ text-align: left;
121
+ }
122
+
123
+ .usage-reports th button {
124
+ background: none;
125
+ border: 0;
126
+ font-weight: 600;
127
+ padding: 0;
128
+ }
129
+
130
+ .usage-reports__state {
131
+ color: var(--theme-elevation-500);
132
+ padding: 32px;
133
+ text-align: center;
134
+ }
135
+
136
+ .usage-reports__error {
137
+ color: var(--theme-error-500);
138
+ }
139
+
140
+ @media (max-width: 768px) {
141
+ .usage-reports {
142
+ padding: 20px;
143
+ }
144
+
145
+ .usage-reports__header {
146
+ flex-direction: column;
147
+ }
148
+ }
@@ -0,0 +1,3 @@
1
+ export { UsageReports, UsageReportsNavLink } from './client/UsageReports.js';
2
+ export type { UsageReport, UsageReportGroup, UsageReportRow } from './index.js';
3
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,2 @@
1
+ export { UsageReports, UsageReportsNavLink } from './client/UsageReports.js';
2
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { Plugin } from 'frogbot';
2
+ export type UsageReportGroup = 'apiKey' | 'day' | 'model' | 'user';
3
+ export type UsageReportRow = {
4
+ key: string;
5
+ label: string;
6
+ requestCount: number;
7
+ inputTokens: number;
8
+ outputTokens: number;
9
+ cachedInputTokens: number;
10
+ cacheWriteTokens: number;
11
+ reasoningTokens: number;
12
+ totalTokens: number;
13
+ costUSD: number;
14
+ };
15
+ export type UsageReport = {
16
+ groupBy: UsageReportGroup;
17
+ from: string;
18
+ to: string;
19
+ rows: UsageReportRow[];
20
+ totals: Omit<UsageReportRow, 'key' | 'label'>;
21
+ };
22
+ export type UsageReportsPluginOptions = {
23
+ pageSize?: number;
24
+ rawExport?: boolean;
25
+ };
26
+ export declare function usageReportsPlugin(options?: UsageReportsPluginOptions): Plugin;
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA4B,MAAM,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,OAAO,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA+HF,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,MAAM,CAuClF"}
package/dist/index.js ADDED
@@ -0,0 +1,171 @@
1
+ import { importExportPlugin } from '@payloadcms/plugin-import-export';
2
+ const groups = new Set(['apiKey', 'day', 'model', 'user']);
3
+ function idAndLabel(value) {
4
+ if (typeof value === 'string' || typeof value === 'number') {
5
+ const key = String(value);
6
+ return { key, label: key };
7
+ }
8
+ if (!value || typeof value !== 'object')
9
+ return;
10
+ const record = value;
11
+ if (record.id === undefined)
12
+ return;
13
+ const key = String(record.id);
14
+ const labelValue = record.name ?? record.email ?? record.title ?? record.id;
15
+ return { key, label: String(labelValue) };
16
+ }
17
+ function groupValue(doc, groupBy) {
18
+ if (groupBy === 'day') {
19
+ const date = new Date(String(doc.requestedAt));
20
+ if (Number.isNaN(date.getTime()))
21
+ return;
22
+ const key = date.toISOString().slice(0, 10);
23
+ return { key, label: key };
24
+ }
25
+ if (groupBy === 'model') {
26
+ const key = typeof doc.model === 'string' && doc.model ? doc.model : 'unknown';
27
+ return { key, label: key };
28
+ }
29
+ return idAndLabel(doc[groupBy]);
30
+ }
31
+ function number(doc, field) {
32
+ const value = Number(doc[field] ?? 0);
33
+ return Number.isFinite(value) ? value : 0;
34
+ }
35
+ function emptyRow(key, label) {
36
+ return {
37
+ key,
38
+ label,
39
+ requestCount: 0,
40
+ inputTokens: 0,
41
+ outputTokens: 0,
42
+ cachedInputTokens: 0,
43
+ cacheWriteTokens: 0,
44
+ reasoningTokens: 0,
45
+ totalTokens: 0,
46
+ costUSD: 0,
47
+ };
48
+ }
49
+ function addDoc(row, doc) {
50
+ row.requestCount += 1;
51
+ row.inputTokens += number(doc, 'inputTokens');
52
+ row.outputTokens += number(doc, 'outputTokens');
53
+ row.cachedInputTokens += number(doc, 'cachedInputTokens');
54
+ row.cacheWriteTokens += number(doc, 'cacheWriteTokens');
55
+ row.reasoningTokens += number(doc, 'reasoningTokens');
56
+ row.totalTokens += number(doc, 'totalTokens');
57
+ row.costUSD += number(doc, 'costUSD');
58
+ }
59
+ function parseRequest(req) {
60
+ const search = new URL(req.url ?? '', 'http://localhost').searchParams;
61
+ const groupBy = search.get('groupBy');
62
+ const fromDate = new Date(search.get('from') ?? '');
63
+ const toDate = new Date(search.get('to') ?? '');
64
+ if (!groupBy || !groups.has(groupBy) || Number.isNaN(fromDate.getTime()) || Number.isNaN(toDate.getTime()) || fromDate > toDate)
65
+ return;
66
+ return { groupBy, from: fromDate.toISOString(), to: toDate.toISOString() };
67
+ }
68
+ function buildReportEndpoint(slug, pageSize) {
69
+ return {
70
+ method: 'get',
71
+ path: '/usage/report',
72
+ handler: async (req) => {
73
+ if (!req.user)
74
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
75
+ const query = parseRequest(req);
76
+ if (!query)
77
+ return Response.json({ error: 'Invalid groupBy or date range' }, { status: 400 });
78
+ const rows = new Map();
79
+ let page = 1;
80
+ while (true) {
81
+ const result = await req.frogbot.find({
82
+ collection: slug,
83
+ where: {
84
+ and: [
85
+ { requestedAt: { greater_than_equal: query.from } },
86
+ { requestedAt: { less_than_equal: query.to } },
87
+ ],
88
+ },
89
+ page,
90
+ limit: pageSize,
91
+ depth: 1,
92
+ sort: 'requestedAt',
93
+ overrideAccess: true,
94
+ req,
95
+ });
96
+ for (const value of result.docs) {
97
+ const doc = value;
98
+ const group = groupValue(doc, query.groupBy);
99
+ if (!group)
100
+ continue;
101
+ const row = rows.get(group.key) ?? emptyRow(group.key, group.label);
102
+ if (row.label === row.key && group.label !== group.key)
103
+ row.label = group.label;
104
+ addDoc(row, doc);
105
+ rows.set(group.key, row);
106
+ }
107
+ if (!result.hasNextPage)
108
+ break;
109
+ page = result.nextPage ?? page + 1;
110
+ }
111
+ const reportRows = [...rows.values()].sort((a, b) => b.costUSD - a.costUSD || b.totalTokens - a.totalTokens || a.label.localeCompare(b.label));
112
+ const totals = emptyRow('', '');
113
+ for (const row of reportRows) {
114
+ totals.requestCount += row.requestCount;
115
+ totals.inputTokens += row.inputTokens;
116
+ totals.outputTokens += row.outputTokens;
117
+ totals.cachedInputTokens += row.cachedInputTokens;
118
+ totals.cacheWriteTokens += row.cacheWriteTokens;
119
+ totals.reasoningTokens += row.reasoningTokens;
120
+ totals.totalTokens += row.totalTokens;
121
+ totals.costUSD += row.costUSD;
122
+ }
123
+ const { key: _key, label: _label, ...reportTotals } = totals;
124
+ return Response.json({ ...query, rows: reportRows, totals: reportTotals });
125
+ },
126
+ };
127
+ }
128
+ export function usageReportsPlugin(options = {}) {
129
+ const pageSize = options.pageSize ?? 5000;
130
+ if (!Number.isInteger(pageSize) || pageSize < 1)
131
+ throw new Error('[plugin-usage-reports] pageSize must be a positive integer.');
132
+ return async (config) => {
133
+ if (!config.ai)
134
+ throw new Error('[plugin-usage-reports] AI configuration is required.');
135
+ const existing = config.collections.find((collection) => collection.usageLog === true);
136
+ const usage = existing ?? { slug: 'usage-logs', usageLog: true, fields: [] };
137
+ const collections = existing
138
+ ? config.collections.map((collection) => collection === existing
139
+ ? { ...collection, admin: { ...collection.admin, groupBy: true } }
140
+ : collection)
141
+ : [...config.collections, { ...usage, admin: { groupBy: true } }];
142
+ const next = {
143
+ ...config,
144
+ collections,
145
+ endpoints: [...(config.endpoints ?? []), buildReportEndpoint(usage.slug, pageSize)],
146
+ admin: {
147
+ ...config.admin,
148
+ components: {
149
+ ...config.admin?.components,
150
+ afterNavLinks: [
151
+ ...(config.admin?.components?.afterNavLinks ?? []),
152
+ '@frogbotai/plugin-usage-reports/client#UsageReportsNavLink',
153
+ ],
154
+ views: {
155
+ ...(config.admin?.components?.views ?? {}),
156
+ usageReports: {
157
+ Component: '@frogbotai/plugin-usage-reports/client#UsageReports',
158
+ path: '/usage-analytics',
159
+ },
160
+ },
161
+ },
162
+ },
163
+ };
164
+ if (options.rawExport === false)
165
+ return next;
166
+ return await importExportPlugin({
167
+ collections: [{ slug: usage.slug, import: false, export: { format: 'csv' } }],
168
+ })(next);
169
+ };
170
+ }
171
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@frogbotai/plugin-usage-reports",
3
+ "version": "0.12.0",
4
+ "description": "Usage analytics reports for FrogBot.",
5
+ "license": "MIT",
6
+ "author": "Colby Gilbert",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/frogbotai/frogbot.git",
10
+ "directory": "packages/plugins/plugin-usage-reports"
11
+ },
12
+ "homepage": "https://github.com/frogbotai/frogbot#readme",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./client": {
23
+ "import": "./dist/client.js",
24
+ "types": "./dist/client.d.ts",
25
+ "default": "./dist/client.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "sideEffects": [
32
+ "*.css"
33
+ ],
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "peerDependencies": {
38
+ "@payloadcms/plugin-import-export": "3.85.1",
39
+ "@payloadcms/ui": "3.85.1",
40
+ "react": "^19.0.1 || ^19.1.2 || ^19.2.1",
41
+ "react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1",
42
+ "frogbot": "0.12.0"
43
+ },
44
+ "devDependencies": {
45
+ "@payloadcms/plugin-import-export": "3.85.1",
46
+ "@payloadcms/ui": "3.85.1",
47
+ "@testing-library/react": "^16.3.0",
48
+ "@types/react": "19.2.14",
49
+ "react": "19.2.6",
50
+ "react-dom": "19.2.6",
51
+ "typescript": "5.6.2",
52
+ "frogbot": "0.12.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.json && cp src/client/styles.css dist/client/styles.css",
59
+ "clean": "rm -rf dist",
60
+ "typecheck": "tsc --noEmit"
61
+ }
62
+ }