@agent-analytics/paperclip-live-analytics-plugin 0.1.0 → 0.1.1

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.
@@ -0,0 +1,51 @@
1
+ // src/paperclip/manifest.js
2
+ var manifest = {
3
+ id: "agent-analytics.paperclip-live-analytics-plugin",
4
+ apiVersion: 1,
5
+ version: "0.1.0",
6
+ displayName: "Agent Analytics Live",
7
+ description: "Thin live monitor for Paperclip companies using Agent Analytics.",
8
+ author: "@agent-analytics",
9
+ categories: ["automation", "ui"],
10
+ capabilities: [
11
+ "http.outbound",
12
+ "plugin.state.read",
13
+ "plugin.state.write",
14
+ "companies.read",
15
+ "projects.read",
16
+ "ui.page.register",
17
+ "ui.dashboardWidget.register",
18
+ "instance.settings.register"
19
+ ],
20
+ entrypoints: {
21
+ worker: "./dist/worker.js",
22
+ ui: "./dist/ui"
23
+ },
24
+ ui: {
25
+ slots: [
26
+ {
27
+ type: "page",
28
+ id: "agent-analytics-live-page",
29
+ displayName: "Agent Analytics Live",
30
+ exportName: "LivePage",
31
+ routePath: "agent-analytics-live"
32
+ },
33
+ {
34
+ type: "dashboardWidget",
35
+ id: "agent-analytics-live-widget",
36
+ displayName: "Agent Analytics Live",
37
+ exportName: "LiveDashboardWidget"
38
+ },
39
+ {
40
+ type: "settingsPage",
41
+ id: "agent-analytics-live-settings",
42
+ displayName: "Agent Analytics Live Settings",
43
+ exportName: "LiveSettingsPage"
44
+ }
45
+ ]
46
+ }
47
+ };
48
+ var manifest_default = manifest;
49
+ export {
50
+ manifest_default as default
51
+ };
@@ -0,0 +1,578 @@
1
+ // src/paperclip/ui-entry.jsx
2
+ import React, { useEffect, useMemo, useState as useState2 } from "react";
3
+ import { useHostContext, usePluginAction, usePluginData, usePluginStream, usePluginToast } from "@paperclipai/plugin-sdk/ui";
4
+
5
+ // src/shared/constants.js
6
+ var LIVE_STREAM_CHANNEL = "agent-analytics-live";
7
+ var DATA_KEYS = {
8
+ livePageLoad: "live.page.load",
9
+ liveWidgetLoad: "live.widget.load",
10
+ settingsLoad: "settings.load"
11
+ };
12
+ var ACTION_KEYS = {
13
+ authStart: "auth.start",
14
+ authComplete: "auth.complete",
15
+ authDisconnect: "auth.disconnect",
16
+ authReconnect: "auth.reconnect",
17
+ settingsSave: "settings.save",
18
+ mappingUpsert: "mapping.upsert",
19
+ mappingRemove: "mapping.remove",
20
+ assetSnooze: "asset.snooze",
21
+ assetUnsnooze: "asset.unsnooze"
22
+ };
23
+
24
+ // src/ui/surfaces/PageSurface.jsx
25
+ import { jsx, jsxs } from "react/jsx-runtime";
26
+ function formatRelativeTime(timestamp) {
27
+ if (!timestamp) return "No updates yet";
28
+ const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1e3));
29
+ if (seconds < 5) return "Updated just now";
30
+ if (seconds < 60) return `Updated ${seconds}s ago`;
31
+ const minutes = Math.round(seconds / 60);
32
+ return `Updated ${minutes}m ago`;
33
+ }
34
+ function CountryPulse({ liveState }) {
35
+ const hotCountry = liveState.world.hotCountry || "World";
36
+ return /* @__PURE__ */ jsxs("section", { className: "aa-panel aa-world-panel", children: [
37
+ /* @__PURE__ */ jsxs("div", { className: "aa-panel-header", children: [
38
+ /* @__PURE__ */ jsxs("div", { children: [
39
+ /* @__PURE__ */ jsx("p", { className: "aa-kicker", children: "World / Country View" }),
40
+ /* @__PURE__ */ jsx("h2", { children: "Supporting geography, not dashboard wallpaper." })
41
+ ] }),
42
+ /* @__PURE__ */ jsx("div", { className: "aa-world-hot", children: hotCountry })
43
+ ] }),
44
+ /* @__PURE__ */ jsxs("div", { className: "aa-world-grid", children: [
45
+ /* @__PURE__ */ jsxs("div", { className: "aa-globe", children: [
46
+ /* @__PURE__ */ jsx("div", { className: "aa-globe-ring aa-globe-ring-one" }),
47
+ /* @__PURE__ */ jsx("div", { className: "aa-globe-ring aa-globe-ring-two" }),
48
+ /* @__PURE__ */ jsx("div", { className: "aa-globe-ring aa-globe-ring-three" }),
49
+ /* @__PURE__ */ jsx("div", { className: "aa-globe-core", children: /* @__PURE__ */ jsx("span", { children: "Live" }) })
50
+ ] }),
51
+ /* @__PURE__ */ jsx("div", { className: "aa-country-list", children: liveState.world.countries.map((country) => /* @__PURE__ */ jsxs("div", { className: "aa-country-row", children: [
52
+ /* @__PURE__ */ jsxs("div", { children: [
53
+ /* @__PURE__ */ jsx("strong", { children: country.country }),
54
+ /* @__PURE__ */ jsxs("span", { children: [
55
+ country.visitors,
56
+ " visitors"
57
+ ] })
58
+ ] }),
59
+ /* @__PURE__ */ jsx("div", { className: "aa-country-bar", children: /* @__PURE__ */ jsx(
60
+ "div",
61
+ {
62
+ className: "aa-country-bar-fill",
63
+ style: { width: `${Math.min(100, country.events / Math.max(1, liveState.metrics.eventsPerMinute) * 100)}%` }
64
+ }
65
+ ) })
66
+ ] }, country.country)) })
67
+ ] })
68
+ ] });
69
+ }
70
+ function EvidenceColumn({ liveState }) {
71
+ return /* @__PURE__ */ jsxs("section", { className: "aa-panel", children: [
72
+ /* @__PURE__ */ jsx("div", { className: "aa-panel-header", children: /* @__PURE__ */ jsxs("div", { children: [
73
+ /* @__PURE__ */ jsx("p", { className: "aa-kicker", children: "Operator Evidence" }),
74
+ /* @__PURE__ */ jsx("h2", { children: "Fast feed, top pages, and why the pulse changed." })
75
+ ] }) }),
76
+ /* @__PURE__ */ jsxs("div", { className: "aa-evidence-grid", children: [
77
+ /* @__PURE__ */ jsxs("div", { className: "aa-mini-panel", children: [
78
+ /* @__PURE__ */ jsx("h3", { children: "Top Pages" }),
79
+ liveState.evidence.topPages.map((page) => /* @__PURE__ */ jsxs("div", { className: "aa-mini-row", children: [
80
+ /* @__PURE__ */ jsx("span", { children: page.path }),
81
+ /* @__PURE__ */ jsx("strong", { children: page.visitors })
82
+ ] }, page.path))
83
+ ] }),
84
+ /* @__PURE__ */ jsxs("div", { className: "aa-mini-panel", children: [
85
+ /* @__PURE__ */ jsx("h3", { children: "Top Events" }),
86
+ liveState.evidence.topEvents.map((event) => /* @__PURE__ */ jsxs("div", { className: "aa-mini-row", children: [
87
+ /* @__PURE__ */ jsx("span", { children: event.event }),
88
+ /* @__PURE__ */ jsx("strong", { children: event.count })
89
+ ] }, event.event))
90
+ ] })
91
+ ] }),
92
+ /* @__PURE__ */ jsx("div", { className: "aa-feed", children: liveState.evidence.recentEvents.map((event) => /* @__PURE__ */ jsxs("div", { className: "aa-feed-row", children: [
93
+ /* @__PURE__ */ jsxs("div", { children: [
94
+ /* @__PURE__ */ jsx("strong", { children: event.event }),
95
+ /* @__PURE__ */ jsxs("span", { children: [
96
+ event.assetLabel || "Unmapped asset",
97
+ " \xB7 ",
98
+ event.path || "no path",
99
+ " \xB7 ",
100
+ event.country || "??"
101
+ ] })
102
+ ] }),
103
+ /* @__PURE__ */ jsx("time", { children: formatRelativeTime(event.timestamp) })
104
+ ] }, event.id)) })
105
+ ] });
106
+ }
107
+ function AssetCard({ asset, onSnooze, basePath }) {
108
+ return /* @__PURE__ */ jsxs("article", { className: `aa-asset-card aa-asset-card-${asset.kind}`, children: [
109
+ /* @__PURE__ */ jsxs("div", { className: "aa-asset-topline", children: [
110
+ /* @__PURE__ */ jsxs("div", { children: [
111
+ /* @__PURE__ */ jsx("p", { className: "aa-kicker", children: asset.kind }),
112
+ /* @__PURE__ */ jsx("h3", { children: asset.label })
113
+ ] }),
114
+ /* @__PURE__ */ jsx("span", { className: `aa-status-pill aa-status-${asset.status}`, children: asset.status })
115
+ ] }),
116
+ /* @__PURE__ */ jsxs("div", { className: "aa-asset-metrics", children: [
117
+ /* @__PURE__ */ jsxs("div", { children: [
118
+ /* @__PURE__ */ jsx("span", { children: "Visitors" }),
119
+ /* @__PURE__ */ jsx("strong", { children: asset.activeVisitors })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs("div", { children: [
122
+ /* @__PURE__ */ jsx("span", { children: "Sessions" }),
123
+ /* @__PURE__ */ jsx("strong", { children: asset.activeSessions })
124
+ ] }),
125
+ /* @__PURE__ */ jsxs("div", { children: [
126
+ /* @__PURE__ */ jsx("span", { children: "Events / min" }),
127
+ /* @__PURE__ */ jsx("strong", { children: asset.eventsPerMinute })
128
+ ] })
129
+ ] }),
130
+ /* @__PURE__ */ jsxs("div", { className: "aa-asset-details", children: [
131
+ /* @__PURE__ */ jsxs("div", { children: [
132
+ /* @__PURE__ */ jsx("span", { className: "aa-label", children: "Project" }),
133
+ /* @__PURE__ */ jsx("strong", { children: asset.agentAnalyticsProject })
134
+ ] }),
135
+ /* @__PURE__ */ jsxs("div", { children: [
136
+ /* @__PURE__ */ jsx("span", { className: "aa-label", children: "Hot country" }),
137
+ /* @__PURE__ */ jsx("strong", { children: asset.lastHotCountry || "Waiting for stream" })
138
+ ] }),
139
+ /* @__PURE__ */ jsxs("div", { children: [
140
+ /* @__PURE__ */ jsx("span", { className: "aa-label", children: "Updated" }),
141
+ /* @__PURE__ */ jsx("strong", { children: formatRelativeTime(asset.lastUpdatedAt) })
142
+ ] })
143
+ ] }),
144
+ /* @__PURE__ */ jsxs("div", { className: "aa-asset-evidence", children: [
145
+ /* @__PURE__ */ jsxs("div", { children: [
146
+ /* @__PURE__ */ jsx("span", { className: "aa-label", children: "Top page" }),
147
+ /* @__PURE__ */ jsx("strong", { children: asset.topPages[0]?.path || "No pages yet" })
148
+ ] }),
149
+ /* @__PURE__ */ jsxs("div", { children: [
150
+ /* @__PURE__ */ jsx("span", { className: "aa-label", children: "Top event" }),
151
+ /* @__PURE__ */ jsx("strong", { children: asset.topEvents[0]?.event || "No events yet" })
152
+ ] })
153
+ ] }),
154
+ /* @__PURE__ */ jsxs("div", { className: "aa-asset-actions", children: [
155
+ asset.paperclipProjectId ? /* @__PURE__ */ jsx("a", { className: "aa-button aa-button-secondary", href: `${basePath || ""}/projects/${asset.paperclipProjectId}`, children: "Open mapped project" }) : /* @__PURE__ */ jsx("span", { className: "aa-muted-note", children: "No Paperclip project linked yet" }),
156
+ /* @__PURE__ */ jsx("button", { className: "aa-button aa-button-ghost", onClick: () => onSnooze(asset.assetKey), children: "Snooze 30m" })
157
+ ] })
158
+ ] });
159
+ }
160
+ function PageSurface({ liveState, onSnooze, basePath = "" }) {
161
+ return /* @__PURE__ */ jsxs("div", { className: "aa-page-shell", children: [
162
+ /* @__PURE__ */ jsxs("header", { className: "aa-hero", children: [
163
+ /* @__PURE__ */ jsxs("div", { children: [
164
+ /* @__PURE__ */ jsx("p", { className: "aa-kicker", children: "Agent Analytics Live" }),
165
+ /* @__PURE__ */ jsx("h1", { children: "Ambient pulse for the company, backed by raw evidence." }),
166
+ /* @__PURE__ */ jsx("p", { className: "aa-hero-copy", children: liveState.connection.detail })
167
+ ] }),
168
+ /* @__PURE__ */ jsxs("div", { className: "aa-hero-status", children: [
169
+ /* @__PURE__ */ jsx("span", { className: `aa-status-pill aa-status-${liveState.connection.status}`, children: liveState.connection.label }),
170
+ /* @__PURE__ */ jsx("p", { children: liveState.account?.email || "No connected account" })
171
+ ] })
172
+ ] }),
173
+ /* @__PURE__ */ jsxs("section", { className: "aa-metric-grid", children: [
174
+ /* @__PURE__ */ jsxs("div", { className: "aa-metric-card", children: [
175
+ /* @__PURE__ */ jsx("span", { children: "Active visitors" }),
176
+ /* @__PURE__ */ jsx("strong", { children: liveState.metrics.activeVisitors })
177
+ ] }),
178
+ /* @__PURE__ */ jsxs("div", { className: "aa-metric-card", children: [
179
+ /* @__PURE__ */ jsx("span", { children: "Active sessions" }),
180
+ /* @__PURE__ */ jsx("strong", { children: liveState.metrics.activeSessions })
181
+ ] }),
182
+ /* @__PURE__ */ jsxs("div", { className: "aa-metric-card", children: [
183
+ /* @__PURE__ */ jsx("span", { children: "Events / min" }),
184
+ /* @__PURE__ */ jsx("strong", { children: liveState.metrics.eventsPerMinute })
185
+ ] }),
186
+ /* @__PURE__ */ jsxs("div", { className: "aa-metric-card", children: [
187
+ /* @__PURE__ */ jsx("span", { children: "Visible assets" }),
188
+ /* @__PURE__ */ jsx("strong", { children: liveState.metrics.assetsVisible })
189
+ ] })
190
+ ] }),
191
+ /* @__PURE__ */ jsxs("div", { className: "aa-main-grid", children: [
192
+ /* @__PURE__ */ jsx(CountryPulse, { liveState }),
193
+ /* @__PURE__ */ jsx(EvidenceColumn, { liveState })
194
+ ] }),
195
+ /* @__PURE__ */ jsxs("section", { className: "aa-assets-section", children: [
196
+ /* @__PURE__ */ jsx("div", { className: "aa-panel-header", children: /* @__PURE__ */ jsxs("div", { children: [
197
+ /* @__PURE__ */ jsx("p", { className: "aa-kicker", children: "Mapped Assets" }),
198
+ /* @__PURE__ */ jsx("h2", { children: "Each card ties live movement back to an owned company asset." })
199
+ ] }) }),
200
+ /* @__PURE__ */ jsx("div", { className: "aa-asset-grid", children: liveState.assets.map((asset) => /* @__PURE__ */ jsx(AssetCard, { asset, onSnooze, basePath }, asset.assetKey)) })
201
+ ] })
202
+ ] });
203
+ }
204
+
205
+ // src/ui/surfaces/SettingsSurface.jsx
206
+ import { useState } from "react";
207
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
208
+ var EMPTY_MAPPING = {
209
+ assetKey: "",
210
+ label: "",
211
+ kind: "website",
212
+ paperclipProjectId: "",
213
+ agentAnalyticsProject: "",
214
+ primaryHostname: "",
215
+ enabled: true
216
+ };
217
+ function MappingRow({ mapping, onRemove }) {
218
+ return /* @__PURE__ */ jsxs2("div", { className: "aa-settings-row", children: [
219
+ /* @__PURE__ */ jsxs2("div", { children: [
220
+ /* @__PURE__ */ jsx2("strong", { children: mapping.label }),
221
+ /* @__PURE__ */ jsxs2("span", { children: [
222
+ mapping.kind,
223
+ " \xB7 ",
224
+ mapping.agentAnalyticsProject
225
+ ] })
226
+ ] }),
227
+ /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-ghost", onClick: () => onRemove(mapping.assetKey), children: "Remove" })
228
+ ] });
229
+ }
230
+ function SettingsSurface({
231
+ settingsData,
232
+ onStartAuth,
233
+ onCompleteAuth,
234
+ onReconnect,
235
+ onDisconnect,
236
+ onSaveSettings,
237
+ onUpsertMapping,
238
+ onRemoveMapping
239
+ }) {
240
+ const [formState, setFormState] = useState(() => ({
241
+ agentAnalyticsBaseUrl: settingsData.settings.agentAnalyticsBaseUrl,
242
+ liveWindowSeconds: settingsData.settings.liveWindowSeconds,
243
+ pollIntervalSeconds: settingsData.settings.pollIntervalSeconds,
244
+ pluginEnabled: settingsData.settings.pluginEnabled
245
+ }));
246
+ const [mappingForm, setMappingForm] = useState(EMPTY_MAPPING);
247
+ const [exchangeCode, setExchangeCode] = useState("");
248
+ return /* @__PURE__ */ jsxs2("div", { className: "aa-settings-shell", children: [
249
+ /* @__PURE__ */ jsxs2("section", { className: "aa-panel", children: [
250
+ /* @__PURE__ */ jsxs2("div", { className: "aa-panel-header", children: [
251
+ /* @__PURE__ */ jsxs2("div", { children: [
252
+ /* @__PURE__ */ jsx2("p", { className: "aa-kicker", children: "Connection" }),
253
+ /* @__PURE__ */ jsx2("h2", { children: "Login-first auth, worker-held tokens." })
254
+ ] }),
255
+ /* @__PURE__ */ jsx2("span", { className: `aa-status-pill aa-status-${settingsData.auth.status}`, children: settingsData.auth.status })
256
+ ] }),
257
+ /* @__PURE__ */ jsxs2("div", { className: "aa-settings-grid", children: [
258
+ /* @__PURE__ */ jsxs2("div", { className: "aa-settings-stack", children: [
259
+ /* @__PURE__ */ jsxs2("div", { className: "aa-settings-row", children: [
260
+ /* @__PURE__ */ jsxs2("div", { children: [
261
+ /* @__PURE__ */ jsx2("strong", { children: "Connected account" }),
262
+ /* @__PURE__ */ jsx2("span", { children: settingsData.auth.accountSummary?.email || "Not connected" })
263
+ ] }),
264
+ settingsData.auth.connected ? /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-secondary", onClick: onDisconnect, children: "Disconnect" }) : /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-primary", onClick: onStartAuth, children: "Start login" })
265
+ ] }),
266
+ settingsData.auth.pendingAuthRequest ? /* @__PURE__ */ jsxs2("div", { className: "aa-auth-box", children: [
267
+ /* @__PURE__ */ jsxs2("label", { children: [
268
+ "Approval URL",
269
+ /* @__PURE__ */ jsx2("a", { href: settingsData.auth.pendingAuthRequest.authorizeUrl, target: "_blank", rel: "noreferrer", children: settingsData.auth.pendingAuthRequest.authorizeUrl })
270
+ ] }),
271
+ /* @__PURE__ */ jsxs2("label", { children: [
272
+ "Finish code",
273
+ /* @__PURE__ */ jsx2("input", { value: exchangeCode, onChange: (event) => setExchangeCode(event.target.value), placeholder: "Paste finish code" })
274
+ ] }),
275
+ /* @__PURE__ */ jsxs2("div", { className: "aa-inline-actions", children: [
276
+ /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-primary", onClick: () => onCompleteAuth(settingsData.auth.pendingAuthRequest.authRequestId, exchangeCode), children: "Complete login" }),
277
+ /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-ghost", onClick: onReconnect, children: "Refresh session" })
278
+ ] })
279
+ ] }) : null
280
+ ] }),
281
+ /* @__PURE__ */ jsxs2("div", { className: "aa-mini-panel", children: [
282
+ /* @__PURE__ */ jsx2("h3", { children: "Discovered projects" }),
283
+ (settingsData.discoveredProjects || []).map((project) => /* @__PURE__ */ jsxs2("div", { className: "aa-mini-row", children: [
284
+ /* @__PURE__ */ jsx2("span", { children: project.name }),
285
+ /* @__PURE__ */ jsx2("strong", { children: project.allowed_origins || "*" })
286
+ ] }, project.id || project.name))
287
+ ] })
288
+ ] })
289
+ ] }),
290
+ /* @__PURE__ */ jsxs2("section", { className: "aa-panel", children: [
291
+ /* @__PURE__ */ jsx2("div", { className: "aa-panel-header", children: /* @__PURE__ */ jsxs2("div", { children: [
292
+ /* @__PURE__ */ jsx2("p", { className: "aa-kicker", children: "Rollout Controls" }),
293
+ /* @__PURE__ */ jsx2("h2", { children: "Keep the live window short and the poll cadence explicit." })
294
+ ] }) }),
295
+ /* @__PURE__ */ jsxs2("div", { className: "aa-form-grid", children: [
296
+ /* @__PURE__ */ jsxs2("label", { children: [
297
+ "Agent Analytics base URL",
298
+ /* @__PURE__ */ jsx2(
299
+ "input",
300
+ {
301
+ value: formState.agentAnalyticsBaseUrl,
302
+ onChange: (event) => setFormState((current) => ({ ...current, agentAnalyticsBaseUrl: event.target.value }))
303
+ }
304
+ )
305
+ ] }),
306
+ /* @__PURE__ */ jsxs2("label", { children: [
307
+ "Live window seconds",
308
+ /* @__PURE__ */ jsx2(
309
+ "input",
310
+ {
311
+ type: "number",
312
+ value: formState.liveWindowSeconds,
313
+ onChange: (event) => setFormState((current) => ({ ...current, liveWindowSeconds: Number(event.target.value) }))
314
+ }
315
+ )
316
+ ] }),
317
+ /* @__PURE__ */ jsxs2("label", { children: [
318
+ "Poll interval seconds",
319
+ /* @__PURE__ */ jsx2(
320
+ "input",
321
+ {
322
+ type: "number",
323
+ value: formState.pollIntervalSeconds,
324
+ onChange: (event) => setFormState((current) => ({ ...current, pollIntervalSeconds: Number(event.target.value) }))
325
+ }
326
+ )
327
+ ] }),
328
+ /* @__PURE__ */ jsxs2("label", { className: "aa-checkbox", children: [
329
+ /* @__PURE__ */ jsx2(
330
+ "input",
331
+ {
332
+ type: "checkbox",
333
+ checked: formState.pluginEnabled,
334
+ onChange: (event) => setFormState((current) => ({ ...current, pluginEnabled: event.target.checked }))
335
+ }
336
+ ),
337
+ "Plugin enabled"
338
+ ] })
339
+ ] }),
340
+ /* @__PURE__ */ jsxs2("div", { className: "aa-inline-actions", children: [
341
+ /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-primary", onClick: () => onSaveSettings(formState), children: "Save controls" }),
342
+ /* @__PURE__ */ jsx2("button", { className: "aa-button aa-button-ghost", onClick: onReconnect, children: "Revalidate connection" })
343
+ ] })
344
+ ] }),
345
+ /* @__PURE__ */ jsxs2("section", { className: "aa-panel", children: [
346
+ /* @__PURE__ */ jsx2("div", { className: "aa-panel-header", children: /* @__PURE__ */ jsxs2("div", { children: [
347
+ /* @__PURE__ */ jsx2("p", { className: "aa-kicker", children: "Asset Mapping" }),
348
+ /* @__PURE__ */ jsx2("h2", { children: "Explicit Paperclip asset to Agent Analytics project links." })
349
+ ] }) }),
350
+ /* @__PURE__ */ jsxs2("div", { className: "aa-form-grid", children: [
351
+ /* @__PURE__ */ jsxs2("label", { children: [
352
+ "Asset key",
353
+ /* @__PURE__ */ jsx2("input", { value: mappingForm.assetKey, onChange: (event) => setMappingForm((current) => ({ ...current, assetKey: event.target.value })) })
354
+ ] }),
355
+ /* @__PURE__ */ jsxs2("label", { children: [
356
+ "Label",
357
+ /* @__PURE__ */ jsx2("input", { value: mappingForm.label, onChange: (event) => setMappingForm((current) => ({ ...current, label: event.target.value })) })
358
+ ] }),
359
+ /* @__PURE__ */ jsxs2("label", { children: [
360
+ "Kind",
361
+ /* @__PURE__ */ jsxs2("select", { value: mappingForm.kind, onChange: (event) => setMappingForm((current) => ({ ...current, kind: event.target.value })), children: [
362
+ /* @__PURE__ */ jsx2("option", { value: "website", children: "website" }),
363
+ /* @__PURE__ */ jsx2("option", { value: "docs", children: "docs" }),
364
+ /* @__PURE__ */ jsx2("option", { value: "app", children: "app" }),
365
+ /* @__PURE__ */ jsx2("option", { value: "api", children: "api" }),
366
+ /* @__PURE__ */ jsx2("option", { value: "other", children: "other" })
367
+ ] })
368
+ ] }),
369
+ /* @__PURE__ */ jsxs2("label", { children: [
370
+ "Paperclip project ID",
371
+ /* @__PURE__ */ jsx2("input", { value: mappingForm.paperclipProjectId, onChange: (event) => setMappingForm((current) => ({ ...current, paperclipProjectId: event.target.value })) })
372
+ ] }),
373
+ /* @__PURE__ */ jsxs2("label", { children: [
374
+ "Agent Analytics project",
375
+ /* @__PURE__ */ jsx2("input", { value: mappingForm.agentAnalyticsProject, onChange: (event) => setMappingForm((current) => ({ ...current, agentAnalyticsProject: event.target.value })) })
376
+ ] }),
377
+ /* @__PURE__ */ jsxs2("label", { children: [
378
+ "Primary hostname",
379
+ /* @__PURE__ */ jsx2("input", { value: mappingForm.primaryHostname, onChange: (event) => setMappingForm((current) => ({ ...current, primaryHostname: event.target.value })) })
380
+ ] }),
381
+ /* @__PURE__ */ jsxs2("label", { className: "aa-checkbox", children: [
382
+ /* @__PURE__ */ jsx2(
383
+ "input",
384
+ {
385
+ type: "checkbox",
386
+ checked: mappingForm.enabled,
387
+ onChange: (event) => setMappingForm((current) => ({ ...current, enabled: event.target.checked }))
388
+ }
389
+ ),
390
+ "Enabled"
391
+ ] })
392
+ ] }),
393
+ /* @__PURE__ */ jsx2("div", { className: "aa-inline-actions", children: /* @__PURE__ */ jsx2(
394
+ "button",
395
+ {
396
+ className: "aa-button aa-button-primary",
397
+ onClick: () => {
398
+ onUpsertMapping(mappingForm);
399
+ setMappingForm(EMPTY_MAPPING);
400
+ },
401
+ children: "Save mapping"
402
+ }
403
+ ) }),
404
+ /* @__PURE__ */ jsx2("div", { className: "aa-settings-stack", children: settingsData.settings.monitoredAssets.map((mapping) => /* @__PURE__ */ jsx2(MappingRow, { mapping, onRemove: onRemoveMapping }, mapping.assetKey)) })
405
+ ] }),
406
+ settingsData.validation.warnings.length > 0 ? /* @__PURE__ */ jsxs2("section", { className: "aa-panel aa-panel-warning", children: [
407
+ /* @__PURE__ */ jsx2("h3", { children: "Warnings" }),
408
+ settingsData.validation.warnings.map((warning) => /* @__PURE__ */ jsx2("p", { children: warning }, warning))
409
+ ] }) : null
410
+ ] });
411
+ }
412
+
413
+ // src/ui/surfaces/WidgetSurface.jsx
414
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
415
+ function WidgetSurface({ widget, fullPageHref = "?surface=page" }) {
416
+ return /* @__PURE__ */ jsxs3("section", { className: "aa-widget", children: [
417
+ /* @__PURE__ */ jsxs3("div", { className: "aa-widget-header", children: [
418
+ /* @__PURE__ */ jsxs3("div", { children: [
419
+ /* @__PURE__ */ jsx3("p", { className: "aa-kicker", children: "Live Status" }),
420
+ /* @__PURE__ */ jsx3("h2", { children: widget.connection.label })
421
+ ] }),
422
+ /* @__PURE__ */ jsx3("span", { className: `aa-status-pill aa-status-${widget.connection.status}`, children: widget.tier || "tier unknown" })
423
+ ] }),
424
+ /* @__PURE__ */ jsxs3("div", { className: "aa-widget-metrics", children: [
425
+ /* @__PURE__ */ jsxs3("div", { children: [
426
+ /* @__PURE__ */ jsx3("span", { children: "Visitors" }),
427
+ /* @__PURE__ */ jsx3("strong", { children: widget.metrics.activeVisitors })
428
+ ] }),
429
+ /* @__PURE__ */ jsxs3("div", { children: [
430
+ /* @__PURE__ */ jsx3("span", { children: "Sessions" }),
431
+ /* @__PURE__ */ jsx3("strong", { children: widget.metrics.activeSessions })
432
+ ] }),
433
+ /* @__PURE__ */ jsxs3("div", { children: [
434
+ /* @__PURE__ */ jsx3("span", { children: "EPM" }),
435
+ /* @__PURE__ */ jsx3("strong", { children: widget.metrics.eventsPerMinute })
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsxs3("div", { className: "aa-widget-footer", children: [
439
+ /* @__PURE__ */ jsxs3("div", { children: [
440
+ /* @__PURE__ */ jsx3("span", { className: "aa-label", children: "Top asset" }),
441
+ /* @__PURE__ */ jsx3("strong", { children: widget.topAsset?.label || "No live assets yet" })
442
+ ] }),
443
+ /* @__PURE__ */ jsx3("a", { className: "aa-button aa-button-secondary", href: fullPageHref, children: "Open full live page" })
444
+ ] })
445
+ ] });
446
+ }
447
+
448
+ // src/ui/styles.css
449
+ var styles_default = ':root {\n --aa-bg: #f0e8dc;\n --aa-bg-deep: #e4d6c0;\n --aa-ink: #1f2a2a;\n --aa-muted: #576665;\n --aa-panel: rgba(255, 250, 242, 0.78);\n --aa-panel-stroke: rgba(31, 42, 42, 0.11);\n --aa-accent: #0f8b8d;\n --aa-accent-warm: #dc6f36;\n --aa-accent-cool: #265f88;\n --aa-live: #1a9a63;\n --aa-error: #b24a2f;\n --aa-shadow: 0 18px 50px rgba(31, 42, 42, 0.08);\n --aa-serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;\n --aa-sans: "IBM Plex Sans", "Avenir Next", "Segoe UI", sans-serif;\n --aa-mono: "IBM Plex Mono", "SFMono-Regular", monospace;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n font-family: var(--aa-sans);\n color: var(--aa-ink);\n background:\n radial-gradient(circle at top left, rgba(15, 139, 141, 0.16), transparent 28%),\n radial-gradient(circle at top right, rgba(220, 111, 54, 0.16), transparent 22%),\n linear-gradient(180deg, #f8f3eb 0%, var(--aa-bg) 45%, var(--aa-bg-deep) 100%);\n}\n\na {\n color: inherit;\n text-decoration: none;\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\n.aa-app {\n min-height: 100vh;\n padding: 24px;\n}\n\n.aa-page-shell,\n.aa-settings-shell {\n max-width: 1400px;\n margin: 0 auto;\n}\n\n.aa-hero,\n.aa-panel,\n.aa-widget,\n.aa-metric-card,\n.aa-asset-card {\n background: var(--aa-panel);\n border: 1px solid var(--aa-panel-stroke);\n box-shadow: var(--aa-shadow);\n backdrop-filter: blur(18px);\n}\n\n.aa-hero {\n display: flex;\n justify-content: space-between;\n gap: 24px;\n padding: 28px;\n border-radius: 28px;\n}\n\n.aa-kicker {\n margin: 0 0 8px;\n font-family: var(--aa-mono);\n font-size: 11px;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--aa-muted);\n}\n\n.aa-hero h1,\n.aa-panel h2,\n.aa-widget h2 {\n margin: 0;\n font-family: var(--aa-serif);\n line-height: 1;\n}\n\n.aa-hero h1 {\n font-size: clamp(2.6rem, 5vw, 4.3rem);\n max-width: 12ch;\n}\n\n.aa-hero-copy {\n max-width: 58ch;\n color: var(--aa-muted);\n}\n\n.aa-hero-status {\n min-width: 220px;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 12px;\n}\n\n.aa-metric-grid,\n.aa-main-grid,\n.aa-evidence-grid,\n.aa-asset-grid,\n.aa-settings-grid,\n.aa-form-grid {\n display: grid;\n gap: 18px;\n}\n\n.aa-metric-grid {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n margin: 18px 0;\n}\n\n.aa-metric-card {\n border-radius: 22px;\n padding: 20px;\n}\n\n.aa-metric-card span,\n.aa-label {\n display: block;\n font-size: 0.82rem;\n color: var(--aa-muted);\n}\n\n.aa-metric-card strong {\n display: block;\n margin-top: 10px;\n font-size: 2.3rem;\n font-family: var(--aa-serif);\n}\n\n.aa-main-grid {\n grid-template-columns: 1.25fr 1fr;\n align-items: start;\n}\n\n.aa-panel,\n.aa-widget,\n.aa-asset-card {\n border-radius: 26px;\n padding: 22px;\n}\n\n.aa-panel-header,\n.aa-widget-header,\n.aa-asset-topline,\n.aa-widget-footer,\n.aa-inline-actions,\n.aa-settings-row {\n display: flex;\n justify-content: space-between;\n gap: 14px;\n align-items: flex-start;\n}\n\n.aa-world-grid {\n display: grid;\n grid-template-columns: minmax(260px, 0.9fr) 1.1fr;\n gap: 20px;\n align-items: center;\n}\n\n.aa-globe {\n position: relative;\n aspect-ratio: 1;\n border-radius: 999px;\n background:\n radial-gradient(circle at 30% 30%, rgba(15, 139, 141, 0.45), transparent 44%),\n radial-gradient(circle at 70% 65%, rgba(220, 111, 54, 0.24), transparent 32%),\n linear-gradient(180deg, #173838 0%, #0f6465 100%);\n overflow: hidden;\n box-shadow: inset 0 0 80px rgba(255, 255, 255, 0.15);\n}\n\n.aa-globe-ring {\n position: absolute;\n inset: 18%;\n border: 1px solid rgba(255, 255, 255, 0.22);\n border-radius: 999px;\n}\n\n.aa-globe-ring-two {\n inset: 8%;\n}\n\n.aa-globe-ring-three {\n inset: 32%;\n}\n\n.aa-globe-core {\n position: absolute;\n inset: 38%;\n display: grid;\n place-items: center;\n border-radius: 999px;\n background: rgba(255, 250, 242, 0.92);\n color: var(--aa-ink);\n font-family: var(--aa-mono);\n letter-spacing: 0.12em;\n text-transform: uppercase;\n font-size: 0.75rem;\n}\n\n.aa-country-list,\n.aa-feed,\n.aa-settings-stack {\n display: grid;\n gap: 12px;\n}\n\n.aa-country-row,\n.aa-feed-row,\n.aa-mini-row,\n.aa-settings-row {\n padding: 12px 0;\n border-top: 1px solid rgba(31, 42, 42, 0.08);\n}\n\n.aa-country-row:first-child,\n.aa-feed-row:first-child,\n.aa-mini-row:first-child,\n.aa-settings-row:first-child {\n border-top: 0;\n padding-top: 0;\n}\n\n.aa-country-row strong,\n.aa-mini-row strong,\n.aa-feed-row strong,\n.aa-settings-row strong {\n display: block;\n}\n\n.aa-country-row span,\n.aa-feed-row span,\n.aa-settings-row span {\n color: var(--aa-muted);\n font-size: 0.92rem;\n}\n\n.aa-country-bar {\n width: 100%;\n height: 8px;\n margin-top: 8px;\n border-radius: 999px;\n background: rgba(38, 95, 136, 0.12);\n overflow: hidden;\n}\n\n.aa-country-bar-fill {\n height: 100%;\n border-radius: 999px;\n background: linear-gradient(90deg, var(--aa-accent) 0%, var(--aa-accent-warm) 100%);\n}\n\n.aa-world-hot,\n.aa-status-pill {\n padding: 7px 12px;\n border-radius: 999px;\n font-size: 0.78rem;\n font-family: var(--aa-mono);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.aa-world-hot,\n.aa-status-live,\n.aa-status-connected,\n.aa-status-streaming {\n background: rgba(26, 154, 99, 0.14);\n color: var(--aa-live);\n}\n\n.aa-status-error,\n.aa-status-attention {\n background: rgba(178, 74, 47, 0.14);\n color: var(--aa-error);\n}\n\n.aa-status-idle,\n.aa-status-pending,\n.aa-status-disconnected {\n background: rgba(38, 95, 136, 0.12);\n color: var(--aa-accent-cool);\n}\n\n.aa-mini-panel {\n background: rgba(255, 255, 255, 0.42);\n border-radius: 20px;\n padding: 18px;\n border: 1px solid rgba(31, 42, 42, 0.08);\n}\n\n.aa-mini-panel h3,\n.aa-assets-section h2 {\n margin: 0 0 12px;\n font-family: var(--aa-serif);\n}\n\n.aa-feed-row {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n}\n\n.aa-feed-row time {\n white-space: nowrap;\n font-size: 0.85rem;\n color: var(--aa-muted);\n}\n\n.aa-assets-section {\n margin-top: 18px;\n}\n\n.aa-asset-grid {\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n margin-top: 18px;\n}\n\n.aa-asset-card {\n display: grid;\n gap: 16px;\n}\n\n.aa-asset-metrics,\n.aa-asset-details,\n.aa-asset-evidence,\n.aa-widget-metrics {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n\n.aa-asset-metrics strong,\n.aa-widget-metrics strong {\n display: block;\n margin-top: 8px;\n font-family: var(--aa-serif);\n font-size: 1.6rem;\n}\n\n.aa-button {\n border: 0;\n border-radius: 999px;\n padding: 10px 14px;\n cursor: pointer;\n transition: transform 120ms ease, opacity 120ms ease;\n}\n\n.aa-button:hover {\n transform: translateY(-1px);\n}\n\n.aa-button-primary {\n background: var(--aa-ink);\n color: #fff;\n}\n\n.aa-button-secondary {\n background: rgba(15, 139, 141, 0.12);\n color: var(--aa-accent);\n}\n\n.aa-button-ghost {\n background: transparent;\n color: var(--aa-muted);\n border: 1px solid rgba(31, 42, 42, 0.12);\n}\n\n.aa-widget {\n max-width: 520px;\n margin: 0 auto;\n}\n\n.aa-widget-metrics {\n margin: 18px 0;\n}\n\n.aa-settings-grid {\n grid-template-columns: 1.15fr 0.85fr;\n}\n\n.aa-form-grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n margin-top: 14px;\n}\n\n.aa-form-grid label,\n.aa-auth-box label {\n display: grid;\n gap: 8px;\n font-size: 0.92rem;\n color: var(--aa-muted);\n}\n\n.aa-form-grid input,\n.aa-form-grid select,\n.aa-auth-box input {\n border-radius: 14px;\n border: 1px solid rgba(31, 42, 42, 0.12);\n padding: 12px 14px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.aa-auth-box {\n display: grid;\n gap: 12px;\n margin-top: 16px;\n padding: 16px;\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.48);\n}\n\n.aa-checkbox {\n display: flex !important;\n align-items: center;\n gap: 10px;\n}\n\n.aa-muted-note {\n color: var(--aa-muted);\n font-size: 0.92rem;\n display: inline-flex;\n align-items: center;\n}\n\n.aa-panel-warning {\n border-color: rgba(220, 111, 54, 0.28);\n}\n\n@media (max-width: 1100px) {\n .aa-metric-grid,\n .aa-main-grid,\n .aa-settings-grid,\n .aa-world-grid {\n grid-template-columns: 1fr;\n }\n\n .aa-hero {\n flex-direction: column;\n }\n\n .aa-hero-status {\n align-items: flex-start;\n }\n}\n\n@media (max-width: 720px) {\n .aa-app {\n padding: 14px;\n }\n\n .aa-metric-grid,\n .aa-form-grid,\n .aa-asset-metrics,\n .aa-asset-details,\n .aa-asset-evidence,\n .aa-widget-metrics {\n grid-template-columns: 1fr 1fr;\n }\n\n .aa-feed-row,\n .aa-panel-header,\n .aa-widget-header,\n .aa-widget-footer,\n .aa-settings-row,\n .aa-inline-actions {\n flex-direction: column;\n }\n}\n\n';
450
+
451
+ // src/paperclip/ui-entry.jsx
452
+ function useInjectedStyles() {
453
+ useEffect(() => {
454
+ const styleId = "agent-analytics-live-plugin-styles";
455
+ if (document.getElementById(styleId)) return;
456
+ const style = document.createElement("style");
457
+ style.id = styleId;
458
+ style.textContent = styles_default;
459
+ document.head.appendChild(style);
460
+ return () => {
461
+ };
462
+ }, []);
463
+ }
464
+ function useCompanyId(explicitContext) {
465
+ const hostContext = useHostContext();
466
+ return explicitContext?.companyId || hostContext.companyId;
467
+ }
468
+ function PageInner({ context }) {
469
+ const companyId = useCompanyId(context);
470
+ const toast = usePluginToast();
471
+ const { data, loading, error, refresh } = usePluginData(DATA_KEYS.livePageLoad, { companyId });
472
+ const snoozeAsset = usePluginAction(ACTION_KEYS.assetSnooze);
473
+ const unsnoozeAsset = usePluginAction(ACTION_KEYS.assetUnsnooze);
474
+ const stream = usePluginStream(LIVE_STREAM_CHANNEL, { companyId });
475
+ const [liveState, setLiveState] = useState2(data);
476
+ useEffect(() => {
477
+ if (data) setLiveState(data);
478
+ }, [data]);
479
+ useEffect(() => {
480
+ if (stream.lastEvent) setLiveState(stream.lastEvent);
481
+ }, [stream.lastEvent]);
482
+ const content = useMemo(() => liveState || data, [liveState, data]);
483
+ if (loading && !content) return React.createElement("div", { className: "aa-panel" }, "Loading live data\u2026");
484
+ if (error && !content) return React.createElement("div", { className: "aa-panel" }, `Live page failed: ${error.message}`);
485
+ if (!content) return React.createElement("div", { className: "aa-panel" }, "No live data yet.");
486
+ return React.createElement(PageSurface, {
487
+ liveState: content,
488
+ onSnooze: async (assetKey) => {
489
+ await snoozeAsset({ companyId, assetKey });
490
+ toast({ title: "Asset snoozed", body: assetKey, tone: "success" });
491
+ refresh();
492
+ },
493
+ onUnsnooze: async (assetKey) => {
494
+ await unsnoozeAsset({ companyId, assetKey });
495
+ toast({ title: "Asset unsnoozed", body: assetKey, tone: "success" });
496
+ refresh();
497
+ },
498
+ basePath: context?.companyPrefix || ""
499
+ });
500
+ }
501
+ function WidgetInner({ context }) {
502
+ const companyId = useCompanyId(context);
503
+ const { data, loading, error } = usePluginData(DATA_KEYS.liveWidgetLoad, { companyId });
504
+ if (loading && !data) return React.createElement("div", { className: "aa-widget" }, "Loading\u2026");
505
+ if (error && !data) return React.createElement("div", { className: "aa-widget" }, `Widget failed: ${error.message}`);
506
+ if (!data) return React.createElement("div", { className: "aa-widget" }, "No live summary yet.");
507
+ return React.createElement(WidgetSurface, {
508
+ widget: data,
509
+ fullPageHref: `${context?.companyPrefix || ""}/plugins/agent-analytics.paperclip-live-analytics-plugin`
510
+ });
511
+ }
512
+ function SettingsInner({ context }) {
513
+ const companyId = useCompanyId(context);
514
+ const toast = usePluginToast();
515
+ const { data, loading, error, refresh } = usePluginData(DATA_KEYS.settingsLoad, { companyId });
516
+ const authStart = usePluginAction(ACTION_KEYS.authStart);
517
+ const authComplete = usePluginAction(ACTION_KEYS.authComplete);
518
+ const authReconnect = usePluginAction(ACTION_KEYS.authReconnect);
519
+ const authDisconnect = usePluginAction(ACTION_KEYS.authDisconnect);
520
+ const settingsSave = usePluginAction(ACTION_KEYS.settingsSave);
521
+ const mappingUpsert = usePluginAction(ACTION_KEYS.mappingUpsert);
522
+ const mappingRemove = usePluginAction(ACTION_KEYS.mappingRemove);
523
+ if (loading && !data) return React.createElement("div", { className: "aa-panel" }, "Loading settings\u2026");
524
+ if (error && !data) return React.createElement("div", { className: "aa-panel" }, `Settings failed: ${error.message}`);
525
+ if (!data) return React.createElement("div", { className: "aa-panel" }, "No settings data yet.");
526
+ return React.createElement(SettingsSurface, {
527
+ settingsData: data,
528
+ onStartAuth: async () => {
529
+ await authStart({ companyId });
530
+ refresh();
531
+ },
532
+ onCompleteAuth: async (authRequestId, exchangeCode) => {
533
+ await authComplete({ companyId, authRequestId, exchangeCode });
534
+ toast({ title: "Agent Analytics connected", tone: "success" });
535
+ refresh();
536
+ },
537
+ onReconnect: async () => {
538
+ await authReconnect({ companyId });
539
+ refresh();
540
+ },
541
+ onDisconnect: async () => {
542
+ await authDisconnect({ companyId });
543
+ refresh();
544
+ },
545
+ onSaveSettings: async (settings) => {
546
+ await settingsSave({ companyId, settings });
547
+ toast({ title: "Settings saved", tone: "success" });
548
+ refresh();
549
+ },
550
+ onUpsertMapping: async (mapping) => {
551
+ await mappingUpsert({ companyId, mapping });
552
+ toast({ title: "Mapping saved", tone: "success" });
553
+ refresh();
554
+ },
555
+ onRemoveMapping: async (assetKey) => {
556
+ await mappingRemove({ companyId, assetKey });
557
+ toast({ title: "Mapping removed", body: assetKey, tone: "info" });
558
+ refresh();
559
+ }
560
+ });
561
+ }
562
+ function LivePage(props) {
563
+ useInjectedStyles();
564
+ return React.createElement(PageInner, props);
565
+ }
566
+ function LiveDashboardWidget(props) {
567
+ useInjectedStyles();
568
+ return React.createElement(WidgetInner, props);
569
+ }
570
+ function LiveSettingsPage(props) {
571
+ useInjectedStyles();
572
+ return React.createElement(SettingsInner, props);
573
+ }
574
+ export {
575
+ LiveDashboardWidget,
576
+ LivePage,
577
+ LiveSettingsPage
578
+ };