@bopen-io/clawnet-plugin 0.0.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,1373 @@
1
+ // src/ui/index.tsx
2
+ import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
3
+ import {
4
+ usePluginAction as usePluginAction2,
5
+ usePluginData as usePluginData2,
6
+ usePluginStream,
7
+ usePluginToast as usePluginToast2
8
+ } from "@paperclipai/plugin-sdk/ui";
9
+
10
+ // src/ui/settings.tsx
11
+ import { useState, useEffect } from "react";
12
+ import {
13
+ usePluginData,
14
+ usePluginAction,
15
+ usePluginToast
16
+ } from "@paperclipai/plugin-sdk/ui";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ var PLUGIN_ID = "bopen-io.clawnet-plugin";
19
+ var DEFAULT_CONFIG = {
20
+ clawnetApiUrl: "https://clawnet.sh",
21
+ clawnetApiKey: "",
22
+ syncIntervalMinutes: 15
23
+ };
24
+ var layoutStack = {
25
+ display: "grid",
26
+ gap: "12px"
27
+ };
28
+ var cardStyle = {
29
+ border: "1px solid var(--border)",
30
+ borderRadius: "12px",
31
+ padding: "14px",
32
+ background: "var(--card, transparent)"
33
+ };
34
+ var rowStyle = {
35
+ display: "flex",
36
+ flexWrap: "wrap",
37
+ alignItems: "center",
38
+ gap: "8px"
39
+ };
40
+ var buttonStyle = {
41
+ appearance: "none",
42
+ border: "1px solid var(--border)",
43
+ borderRadius: "999px",
44
+ background: "transparent",
45
+ color: "inherit",
46
+ padding: "6px 12px",
47
+ fontSize: "12px",
48
+ cursor: "pointer"
49
+ };
50
+ var primaryButtonStyle = {
51
+ ...buttonStyle,
52
+ background: "var(--foreground)",
53
+ color: "var(--background)",
54
+ borderColor: "var(--foreground)"
55
+ };
56
+ var inputStyle = {
57
+ width: "100%",
58
+ border: "1px solid var(--border)",
59
+ borderRadius: "8px",
60
+ padding: "8px 10px",
61
+ background: "transparent",
62
+ color: "inherit",
63
+ fontSize: "12px",
64
+ boxSizing: "border-box"
65
+ };
66
+ var labelStyle = {
67
+ display: "grid",
68
+ gap: "6px"
69
+ };
70
+ var labelTextStyle = {
71
+ fontSize: "12px",
72
+ fontWeight: 500
73
+ };
74
+ var helpTextStyle = {
75
+ fontSize: "11px",
76
+ opacity: 0.65,
77
+ lineHeight: 1.45
78
+ };
79
+ var sectionHeaderStyle = {
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "space-between",
83
+ gap: "8px",
84
+ marginBottom: "10px"
85
+ };
86
+ var statusDotStyle = (color) => ({
87
+ display: "inline-block",
88
+ width: "8px",
89
+ height: "8px",
90
+ borderRadius: "50%",
91
+ backgroundColor: color,
92
+ flexShrink: 0
93
+ });
94
+ function hostFetchJson(path, init) {
95
+ return fetch(path, {
96
+ credentials: "include",
97
+ headers: {
98
+ "content-type": "application/json",
99
+ ...init?.headers ?? {}
100
+ },
101
+ ...init
102
+ }).then(async (response) => {
103
+ if (!response.ok) {
104
+ const text = await response.text();
105
+ throw new Error(text || `Request failed: ${response.status}`);
106
+ }
107
+ return await response.json();
108
+ });
109
+ }
110
+ function useSettingsConfig() {
111
+ const [config, setConfig] = useState({ ...DEFAULT_CONFIG });
112
+ const [loading, setLoading] = useState(true);
113
+ const [saving, setSaving] = useState(false);
114
+ const [error, setError] = useState(null);
115
+ useEffect(() => {
116
+ let cancelled = false;
117
+ setLoading(true);
118
+ hostFetchJson(
119
+ `/api/plugins/${PLUGIN_ID}/config`
120
+ ).then((result) => {
121
+ if (cancelled) return;
122
+ const raw = result?.configJson ?? {};
123
+ setConfig({
124
+ clawnetApiUrl: typeof raw.clawnetApiUrl === "string" ? raw.clawnetApiUrl : DEFAULT_CONFIG.clawnetApiUrl,
125
+ clawnetApiKey: typeof raw.clawnetApiKey === "string" ? raw.clawnetApiKey : DEFAULT_CONFIG.clawnetApiKey,
126
+ syncIntervalMinutes: typeof raw.syncIntervalMinutes === "number" && Number.isFinite(raw.syncIntervalMinutes) ? raw.syncIntervalMinutes : DEFAULT_CONFIG.syncIntervalMinutes
127
+ });
128
+ setError(null);
129
+ }).catch((nextError) => {
130
+ if (cancelled) return;
131
+ setError(
132
+ nextError instanceof Error ? nextError.message : String(nextError)
133
+ );
134
+ }).finally(() => {
135
+ if (!cancelled) setLoading(false);
136
+ });
137
+ return () => {
138
+ cancelled = true;
139
+ };
140
+ }, []);
141
+ async function save(nextConfig) {
142
+ setSaving(true);
143
+ try {
144
+ await hostFetchJson(`/api/plugins/${PLUGIN_ID}/config`, {
145
+ method: "POST",
146
+ body: JSON.stringify({ configJson: nextConfig })
147
+ });
148
+ setConfig(nextConfig);
149
+ setError(null);
150
+ } catch (nextError) {
151
+ setError(
152
+ nextError instanceof Error ? nextError.message : String(nextError)
153
+ );
154
+ throw nextError;
155
+ } finally {
156
+ setSaving(false);
157
+ }
158
+ }
159
+ return { config, setConfig, loading, saving, error, save };
160
+ }
161
+ function ClawNetSettingsPage({ context }) {
162
+ const { config, setConfig, loading, saving, error, save } = useSettingsConfig();
163
+ const toast = usePluginToast();
164
+ const syncStatus = usePluginData("sync-status", {
165
+ companyId: context.companyId
166
+ });
167
+ const validateConfig = usePluginAction("validate-config");
168
+ const triggerSync = usePluginAction("trigger-sync");
169
+ const [testing, setTesting] = useState(false);
170
+ const [syncing, setSyncing] = useState(false);
171
+ function setField(key, value) {
172
+ setConfig((current) => ({ ...current, [key]: value }));
173
+ }
174
+ async function onSubmit(event) {
175
+ event.preventDefault();
176
+ try {
177
+ await save(config);
178
+ toast({ title: "Settings saved", tone: "success" });
179
+ } catch {
180
+ toast({
181
+ title: "Failed to save settings",
182
+ body: error ?? "Unknown error",
183
+ tone: "error"
184
+ });
185
+ }
186
+ }
187
+ async function handleTestConnection() {
188
+ setTesting(true);
189
+ try {
190
+ const result = await validateConfig({
191
+ clawnetApiUrl: config.clawnetApiUrl,
192
+ clawnetApiKey: config.clawnetApiKey
193
+ });
194
+ if (result.ok) {
195
+ toast({ title: "Connection successful", tone: "success" });
196
+ } else {
197
+ toast({
198
+ title: "Connection failed",
199
+ body: result.message ?? "ClawNet API did not respond",
200
+ tone: "error"
201
+ });
202
+ }
203
+ } catch (err) {
204
+ toast({
205
+ title: "Connection test failed",
206
+ body: err instanceof Error ? err.message : String(err),
207
+ tone: "error"
208
+ });
209
+ } finally {
210
+ setTesting(false);
211
+ }
212
+ }
213
+ async function handleManualSync() {
214
+ setSyncing(true);
215
+ try {
216
+ await triggerSync({ companyId: context.companyId });
217
+ toast({ title: "Sync started", tone: "info" });
218
+ setTimeout(() => syncStatus.refresh(), 2e3);
219
+ } catch (err) {
220
+ toast({
221
+ title: "Sync failed",
222
+ body: err instanceof Error ? err.message : String(err),
223
+ tone: "error"
224
+ });
225
+ } finally {
226
+ setSyncing(false);
227
+ }
228
+ }
229
+ if (loading) {
230
+ return /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", opacity: 0.7 }, children: "Loading ClawNet configuration..." });
231
+ }
232
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "18px" }, children: [
233
+ /* @__PURE__ */ jsxs("form", { onSubmit, style: layoutStack, children: [
234
+ /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
235
+ /* @__PURE__ */ jsx("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx("strong", { children: "ClawNet Connection" }) }),
236
+ /* @__PURE__ */ jsxs("div", { style: layoutStack, children: [
237
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
238
+ /* @__PURE__ */ jsx("span", { style: labelTextStyle, children: "ClawNet API URL" }),
239
+ /* @__PURE__ */ jsx(
240
+ "input",
241
+ {
242
+ style: inputStyle,
243
+ type: "url",
244
+ value: config.clawnetApiUrl,
245
+ onChange: (e) => setField("clawnetApiUrl", e.target.value),
246
+ placeholder: "https://clawnet.sh"
247
+ }
248
+ ),
249
+ /* @__PURE__ */ jsx("span", { style: helpTextStyle, children: "The base URL for the ClawNet registry API." })
250
+ ] }),
251
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
252
+ /* @__PURE__ */ jsx("span", { style: labelTextStyle, children: "ClawNet API Key (secret ref)" }),
253
+ /* @__PURE__ */ jsx(
254
+ "input",
255
+ {
256
+ style: inputStyle,
257
+ type: "text",
258
+ value: config.clawnetApiKey,
259
+ onChange: (e) => setField("clawnetApiKey", e.target.value),
260
+ placeholder: "e.g. clawnet-api-key"
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsx("span", { style: helpTextStyle, children: "This is a reference name for a Paperclip secret, not the API key itself. Create the secret in Paperclip Settings and enter its reference name here. The worker resolves the actual value at runtime via ctx.secrets.resolve()." })
264
+ ] }),
265
+ /* @__PURE__ */ jsxs("label", { style: labelStyle, children: [
266
+ /* @__PURE__ */ jsx("span", { style: labelTextStyle, children: "Sync Interval (minutes)" }),
267
+ /* @__PURE__ */ jsx(
268
+ "input",
269
+ {
270
+ style: { ...inputStyle, maxWidth: "120px" },
271
+ type: "number",
272
+ min: 1,
273
+ max: 1440,
274
+ value: config.syncIntervalMinutes,
275
+ onChange: (e) => {
276
+ const val = Number.parseInt(e.target.value, 10);
277
+ if (Number.isFinite(val) && val > 0) {
278
+ setField("syncIntervalMinutes", val);
279
+ }
280
+ }
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsx("span", { style: helpTextStyle, children: "How often to sync agents and skills from the ClawNet registry. Default: 15 minutes." })
284
+ ] })
285
+ ] })
286
+ ] }),
287
+ error ? /* @__PURE__ */ jsx(
288
+ "div",
289
+ {
290
+ style: {
291
+ color: "var(--destructive, #c00)",
292
+ fontSize: "12px"
293
+ },
294
+ children: error
295
+ }
296
+ ) : null,
297
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
298
+ /* @__PURE__ */ jsx("button", { type: "submit", style: primaryButtonStyle, disabled: saving, children: saving ? "Saving..." : "Save settings" }),
299
+ /* @__PURE__ */ jsx(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ style: buttonStyle,
304
+ disabled: testing || !config.clawnetApiUrl,
305
+ onClick: handleTestConnection,
306
+ children: testing ? "Testing..." : "Test connection"
307
+ }
308
+ )
309
+ ] })
310
+ ] }),
311
+ /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
312
+ /* @__PURE__ */ jsxs("div", { style: sectionHeaderStyle, children: [
313
+ /* @__PURE__ */ jsx("strong", { children: "Sync Status" }),
314
+ /* @__PURE__ */ jsx(
315
+ "button",
316
+ {
317
+ type: "button",
318
+ style: buttonStyle,
319
+ disabled: syncing,
320
+ onClick: handleManualSync,
321
+ children: syncing ? "Syncing..." : "Sync now"
322
+ }
323
+ )
324
+ ] }),
325
+ /* @__PURE__ */ jsx(
326
+ SyncStatusDisplay,
327
+ {
328
+ data: syncStatus.data,
329
+ loading: syncStatus.loading,
330
+ error: syncStatus.error?.message ?? null
331
+ }
332
+ )
333
+ ] })
334
+ ] });
335
+ }
336
+ function SyncStatusDisplay({
337
+ data,
338
+ loading,
339
+ error
340
+ }) {
341
+ if (loading) {
342
+ return /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", opacity: 0.7 }, children: "Loading sync status..." });
343
+ }
344
+ if (error) {
345
+ return /* @__PURE__ */ jsxs("div", { style: { fontSize: "12px", color: "var(--destructive, #c00)" }, children: [
346
+ "Failed to load sync status: ",
347
+ error
348
+ ] });
349
+ }
350
+ if (!data) {
351
+ return /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", opacity: 0.7 }, children: "No sync data available. Run a sync to populate." });
352
+ }
353
+ const statusColor = data.status === "idle" ? "#16a34a" : data.status === "syncing" ? "#2563eb" : "#dc2626";
354
+ const statusLabel = data.status === "idle" ? "Idle" : data.status === "syncing" ? "Syncing..." : "Error";
355
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "10px", fontSize: "12px" }, children: [
356
+ /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
357
+ /* @__PURE__ */ jsx("span", { style: statusDotStyle(statusColor) }),
358
+ /* @__PURE__ */ jsx("span", { children: statusLabel })
359
+ ] }),
360
+ /* @__PURE__ */ jsxs(
361
+ "div",
362
+ {
363
+ style: {
364
+ display: "grid",
365
+ gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))",
366
+ gap: "10px"
367
+ },
368
+ children: [
369
+ /* @__PURE__ */ jsx(StatBlock, { label: "Last sync", value: formatTimestamp(data.lastSyncAt) }),
370
+ /* @__PURE__ */ jsx(StatBlock, { label: "Agents", value: String(data.agentCount) }),
371
+ /* @__PURE__ */ jsx(StatBlock, { label: "Skills", value: String(data.skillCount) })
372
+ ]
373
+ }
374
+ ),
375
+ data.error ? /* @__PURE__ */ jsxs("div", { style: { color: "var(--destructive, #c00)" }, children: [
376
+ "Last error: ",
377
+ data.error
378
+ ] }) : null
379
+ ] });
380
+ }
381
+ function StatBlock({ label, value }) {
382
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "2px" }, children: [
383
+ /* @__PURE__ */ jsx(
384
+ "span",
385
+ {
386
+ style: {
387
+ fontSize: "11px",
388
+ opacity: 0.65,
389
+ textTransform: "uppercase",
390
+ letterSpacing: "0.06em"
391
+ },
392
+ children: label
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", fontWeight: 500 }, children: value })
396
+ ] });
397
+ }
398
+ function formatTimestamp(iso) {
399
+ if (!iso) return "Never";
400
+ try {
401
+ const date = new Date(iso);
402
+ if (Number.isNaN(date.getTime())) return "Invalid date";
403
+ return date.toLocaleString();
404
+ } catch {
405
+ return iso;
406
+ }
407
+ }
408
+
409
+ // src/ui/index.tsx
410
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
411
+ var PAGE_ROUTE = "clawnet";
412
+ var layoutStack2 = {
413
+ display: "grid",
414
+ gap: "12px"
415
+ };
416
+ var cardStyle2 = {
417
+ border: "1px solid var(--border)",
418
+ borderRadius: "12px",
419
+ padding: "14px",
420
+ background: "var(--card, transparent)"
421
+ };
422
+ var subtleCardStyle = {
423
+ border: "1px solid color-mix(in srgb, var(--border) 75%, transparent)",
424
+ borderRadius: "10px",
425
+ padding: "12px"
426
+ };
427
+ var rowStyle2 = {
428
+ display: "flex",
429
+ flexWrap: "wrap",
430
+ alignItems: "center",
431
+ gap: "8px"
432
+ };
433
+ var buttonStyle2 = {
434
+ appearance: "none",
435
+ border: "1px solid var(--border)",
436
+ borderRadius: "999px",
437
+ background: "transparent",
438
+ color: "inherit",
439
+ padding: "6px 12px",
440
+ fontSize: "12px",
441
+ cursor: "pointer"
442
+ };
443
+ var primaryButtonStyle2 = {
444
+ ...buttonStyle2,
445
+ background: "var(--foreground)",
446
+ color: "var(--background)",
447
+ borderColor: "var(--foreground)"
448
+ };
449
+ var inputStyle2 = {
450
+ width: "100%",
451
+ border: "1px solid var(--border)",
452
+ borderRadius: "8px",
453
+ padding: "8px 10px",
454
+ background: "transparent",
455
+ color: "inherit",
456
+ fontSize: "12px"
457
+ };
458
+ var mutedTextStyle = {
459
+ fontSize: "12px",
460
+ opacity: 0.72,
461
+ lineHeight: 1.45
462
+ };
463
+ var eyebrowStyle = {
464
+ fontSize: "11px",
465
+ opacity: 0.65,
466
+ textTransform: "uppercase",
467
+ letterSpacing: "0.06em"
468
+ };
469
+ var sectionHeaderStyle2 = {
470
+ display: "flex",
471
+ alignItems: "center",
472
+ justifyContent: "space-between",
473
+ gap: "8px",
474
+ marginBottom: "10px"
475
+ };
476
+ var statValueStyle = {
477
+ fontSize: "24px",
478
+ fontWeight: 700,
479
+ lineHeight: 1
480
+ };
481
+ var statLabelStyle = {
482
+ fontSize: "11px",
483
+ opacity: 0.6,
484
+ textTransform: "uppercase",
485
+ letterSpacing: "0.06em",
486
+ marginTop: "4px"
487
+ };
488
+ function hostPath(companyPrefix, suffix) {
489
+ return companyPrefix ? `/${companyPrefix}${suffix}` : suffix;
490
+ }
491
+ function pluginPagePath(companyPrefix) {
492
+ return hostPath(companyPrefix, `/${PAGE_ROUTE}`);
493
+ }
494
+ function relativeTime(isoString) {
495
+ if (!isoString) return "never";
496
+ const then = new Date(isoString).getTime();
497
+ if (Number.isNaN(then)) return "unknown";
498
+ const diffMs = Date.now() - then;
499
+ if (diffMs < 0) return "just now";
500
+ const seconds = Math.floor(diffMs / 1e3);
501
+ if (seconds < 60) return `${seconds}s ago`;
502
+ const minutes = Math.floor(seconds / 60);
503
+ if (minutes < 60) return `${minutes}m ago`;
504
+ const hours = Math.floor(minutes / 60);
505
+ if (hours < 24) return `${hours}h ago`;
506
+ const days = Math.floor(hours / 24);
507
+ return `${days}d ago`;
508
+ }
509
+ function truncateText(text, maxLen) {
510
+ if (text.length <= maxLen) return text;
511
+ return `${text.slice(0, maxLen - 1)}...`;
512
+ }
513
+ function Pill({ label, color }) {
514
+ return /* @__PURE__ */ jsx2(
515
+ "span",
516
+ {
517
+ style: {
518
+ display: "inline-flex",
519
+ alignItems: "center",
520
+ gap: "4px",
521
+ borderRadius: "999px",
522
+ border: "1px solid var(--border)",
523
+ padding: "2px 8px",
524
+ fontSize: "11px",
525
+ background: color ? `color-mix(in srgb, ${color} 14%, transparent)` : void 0,
526
+ borderColor: color ? `color-mix(in srgb, ${color} 40%, var(--border))` : void 0
527
+ },
528
+ children: label
529
+ }
530
+ );
531
+ }
532
+ function StatusDot({ status }) {
533
+ const colorMap = {
534
+ online: "#16a34a",
535
+ idle: "#d97706",
536
+ offline: "#6b7280",
537
+ error: "#dc2626",
538
+ running: "#2563eb"
539
+ };
540
+ const dotColor = colorMap[status.toLowerCase()] ?? "#6b7280";
541
+ return /* @__PURE__ */ jsx2(
542
+ "span",
543
+ {
544
+ style: {
545
+ display: "inline-block",
546
+ width: "7px",
547
+ height: "7px",
548
+ borderRadius: "50%",
549
+ background: dotColor,
550
+ flexShrink: 0
551
+ },
552
+ "aria-label": status
553
+ }
554
+ );
555
+ }
556
+ function StarCount({ count }) {
557
+ if (count <= 0) return null;
558
+ return /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", alignItems: "center", gap: "3px", fontSize: "11px", opacity: 0.7 }, children: [
559
+ /* @__PURE__ */ jsx2("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" }) }),
560
+ count
561
+ ] });
562
+ }
563
+ function TrustBadge({ score }) {
564
+ if (score === null || score === void 0) return null;
565
+ const level = score >= 80 ? "high" : score >= 50 ? "medium" : "low";
566
+ const colorMap = { high: "#16a34a", medium: "#d97706", low: "#dc2626" };
567
+ const labelMap = { high: "Trusted", medium: "Verified", low: "Unverified" };
568
+ return /* @__PURE__ */ jsxs2(
569
+ "span",
570
+ {
571
+ style: {
572
+ display: "inline-flex",
573
+ alignItems: "center",
574
+ gap: "4px",
575
+ fontSize: "10px",
576
+ fontWeight: 600,
577
+ textTransform: "uppercase",
578
+ letterSpacing: "0.04em",
579
+ color: colorMap[level]
580
+ },
581
+ children: [
582
+ /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z" }) }),
583
+ labelMap[level]
584
+ ]
585
+ }
586
+ );
587
+ }
588
+ function EmptyState({ message }) {
589
+ return /* @__PURE__ */ jsx2(
590
+ "div",
591
+ {
592
+ style: {
593
+ padding: "32px 16px",
594
+ textAlign: "center",
595
+ fontSize: "13px",
596
+ opacity: 0.55
597
+ },
598
+ children: message
599
+ }
600
+ );
601
+ }
602
+ function LoadingIndicator({ message }) {
603
+ return /* @__PURE__ */ jsx2(
604
+ "div",
605
+ {
606
+ style: {
607
+ padding: "24px 16px",
608
+ textAlign: "center",
609
+ fontSize: "12px",
610
+ opacity: 0.6
611
+ },
612
+ children: message ?? "Loading..."
613
+ }
614
+ );
615
+ }
616
+ function ErrorBanner({ message }) {
617
+ return /* @__PURE__ */ jsx2(
618
+ "div",
619
+ {
620
+ style: {
621
+ ...subtleCardStyle,
622
+ borderColor: "color-mix(in srgb, #dc2626 45%, var(--border))",
623
+ fontSize: "12px",
624
+ color: "var(--destructive, #dc2626)"
625
+ },
626
+ children: message
627
+ }
628
+ );
629
+ }
630
+ function Section({
631
+ title,
632
+ action,
633
+ children
634
+ }) {
635
+ return /* @__PURE__ */ jsxs2("section", { style: cardStyle2, children: [
636
+ /* @__PURE__ */ jsxs2("div", { style: sectionHeaderStyle2, children: [
637
+ /* @__PURE__ */ jsx2("strong", { children: title }),
638
+ action
639
+ ] }),
640
+ /* @__PURE__ */ jsx2("div", { style: layoutStack2, children })
641
+ ] });
642
+ }
643
+ function ClawNetFleetWidget({ context }) {
644
+ const companyId = context.companyId;
645
+ const toast = usePluginToast2();
646
+ const syncParams = useMemo(
647
+ () => companyId ? { companyId } : {},
648
+ [companyId]
649
+ );
650
+ const { data: syncStatus, loading, error, refresh } = usePluginData2(
651
+ "sync-status",
652
+ syncParams
653
+ );
654
+ const fleetStream = usePluginStream("fleet-status", {
655
+ companyId: companyId ?? void 0
656
+ });
657
+ const triggerSync = usePluginAction2("trigger-sync");
658
+ const [syncing, setSyncing] = useState2(false);
659
+ async function handleSync() {
660
+ if (!companyId || syncing) return;
661
+ setSyncing(true);
662
+ try {
663
+ await triggerSync({ companyId });
664
+ refresh();
665
+ toast({
666
+ title: "ClawNet sync started",
667
+ body: "Agents and skills are being refreshed from the registry.",
668
+ tone: "success"
669
+ });
670
+ } catch (err) {
671
+ toast({
672
+ title: "Sync failed",
673
+ body: err instanceof Error ? err.message : String(err),
674
+ tone: "error"
675
+ });
676
+ } finally {
677
+ setSyncing(false);
678
+ }
679
+ }
680
+ const onlineCount = useMemo(() => {
681
+ const latestByAgent = /* @__PURE__ */ new Map();
682
+ for (const event of fleetStream.events) {
683
+ latestByAgent.set(event.agentId, event.status);
684
+ }
685
+ let count = 0;
686
+ for (const status of latestByAgent.values()) {
687
+ if (status === "online" || status === "running") count++;
688
+ }
689
+ return count;
690
+ }, [fleetStream.events]);
691
+ if (loading) return /* @__PURE__ */ jsx2(LoadingIndicator, { message: "Loading fleet status..." });
692
+ if (error) return /* @__PURE__ */ jsx2(ErrorBanner, { message: error.message });
693
+ return /* @__PURE__ */ jsxs2("div", { style: layoutStack2, children: [
694
+ /* @__PURE__ */ jsxs2("div", { style: rowStyle2, children: [
695
+ /* @__PURE__ */ jsx2("strong", { children: "ClawNet Fleet" }),
696
+ fleetStream.connected ? /* @__PURE__ */ jsx2(StatusDot, { status: "online" }) : null
697
+ ] }),
698
+ /* @__PURE__ */ jsxs2(
699
+ "div",
700
+ {
701
+ style: {
702
+ display: "grid",
703
+ gridTemplateColumns: "1fr 1fr 1fr",
704
+ gap: "8px"
705
+ },
706
+ children: [
707
+ /* @__PURE__ */ jsxs2("div", { children: [
708
+ /* @__PURE__ */ jsx2("div", { style: statValueStyle, children: syncStatus?.agentCount ?? 0 }),
709
+ /* @__PURE__ */ jsx2("div", { style: statLabelStyle, children: "Agents" })
710
+ ] }),
711
+ /* @__PURE__ */ jsxs2("div", { children: [
712
+ /* @__PURE__ */ jsx2("div", { style: statValueStyle, children: syncStatus?.skillCount ?? 0 }),
713
+ /* @__PURE__ */ jsx2("div", { style: statLabelStyle, children: "Skills" })
714
+ ] }),
715
+ /* @__PURE__ */ jsxs2("div", { children: [
716
+ /* @__PURE__ */ jsx2("div", { style: statValueStyle, children: onlineCount }),
717
+ /* @__PURE__ */ jsx2("div", { style: statLabelStyle, children: "Online" })
718
+ ] })
719
+ ]
720
+ }
721
+ ),
722
+ /* @__PURE__ */ jsxs2("div", { style: { ...mutedTextStyle, fontSize: "11px" }, children: [
723
+ "Last sync: ",
724
+ relativeTime(syncStatus?.lastSyncAt ?? null)
725
+ ] }),
726
+ /* @__PURE__ */ jsxs2("div", { style: rowStyle2, children: [
727
+ /* @__PURE__ */ jsx2(
728
+ "a",
729
+ {
730
+ href: pluginPagePath(context.companyPrefix),
731
+ style: { fontSize: "12px", color: "inherit" },
732
+ children: "Browse marketplace"
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsx2(
736
+ "button",
737
+ {
738
+ type: "button",
739
+ style: buttonStyle2,
740
+ onClick: () => void handleSync(),
741
+ disabled: syncing,
742
+ children: syncing ? "Syncing..." : "Sync Now"
743
+ }
744
+ )
745
+ ] })
746
+ ] });
747
+ }
748
+ var AGENTS_PER_PAGE = 20;
749
+ function TabBar({
750
+ active,
751
+ onChange
752
+ }) {
753
+ const tabs = [
754
+ { key: "agents", label: "Agents" },
755
+ { key: "skills", label: "Skills" }
756
+ ];
757
+ return /* @__PURE__ */ jsx2(
758
+ "div",
759
+ {
760
+ style: {
761
+ display: "flex",
762
+ gap: "0",
763
+ borderBottom: "1px solid var(--border)"
764
+ },
765
+ children: tabs.map((tab) => /* @__PURE__ */ jsx2(
766
+ "button",
767
+ {
768
+ type: "button",
769
+ onClick: () => onChange(tab.key),
770
+ style: {
771
+ appearance: "none",
772
+ background: "transparent",
773
+ border: "none",
774
+ borderBottom: active === tab.key ? "2px solid var(--foreground)" : "2px solid transparent",
775
+ color: active === tab.key ? "var(--foreground)" : "var(--muted-foreground, inherit)",
776
+ padding: "10px 16px",
777
+ fontSize: "13px",
778
+ fontWeight: active === tab.key ? 600 : 400,
779
+ cursor: "pointer",
780
+ transition: "color 0.15s, border-color 0.15s"
781
+ },
782
+ children: tab.label
783
+ },
784
+ tab.key
785
+ ))
786
+ }
787
+ );
788
+ }
789
+ function SearchBar({
790
+ value,
791
+ onChange,
792
+ placeholder
793
+ }) {
794
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
795
+ /* @__PURE__ */ jsxs2(
796
+ "svg",
797
+ {
798
+ width: "14",
799
+ height: "14",
800
+ viewBox: "0 0 24 24",
801
+ fill: "none",
802
+ stroke: "currentColor",
803
+ strokeWidth: "2",
804
+ strokeLinecap: "round",
805
+ strokeLinejoin: "round",
806
+ "aria-hidden": "true",
807
+ style: {
808
+ position: "absolute",
809
+ left: "10px",
810
+ top: "50%",
811
+ transform: "translateY(-50%)",
812
+ opacity: 0.45
813
+ },
814
+ children: [
815
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
816
+ /* @__PURE__ */ jsx2("path", { d: "M21 21l-4.35-4.35" })
817
+ ]
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsx2(
821
+ "input",
822
+ {
823
+ type: "text",
824
+ value,
825
+ onChange: (e) => onChange(e.target.value),
826
+ placeholder,
827
+ style: {
828
+ ...inputStyle2,
829
+ paddingLeft: "30px"
830
+ }
831
+ }
832
+ )
833
+ ] });
834
+ }
835
+ function AgentCard({
836
+ agent,
837
+ onSelect,
838
+ onHire
839
+ }) {
840
+ const colorIndicator = agent.color ?? "var(--muted-foreground)";
841
+ return /* @__PURE__ */ jsxs2(
842
+ "div",
843
+ {
844
+ style: {
845
+ ...subtleCardStyle,
846
+ display: "grid",
847
+ gap: "10px",
848
+ cursor: "pointer",
849
+ transition: "border-color 0.15s"
850
+ },
851
+ onClick: onSelect,
852
+ onKeyDown: (e) => {
853
+ if (e.key === "Enter" || e.key === " ") {
854
+ e.preventDefault();
855
+ onSelect();
856
+ }
857
+ },
858
+ role: "button",
859
+ tabIndex: 0,
860
+ children: [
861
+ /* @__PURE__ */ jsxs2(
862
+ "div",
863
+ {
864
+ style: {
865
+ display: "flex",
866
+ alignItems: "center",
867
+ gap: "10px"
868
+ },
869
+ children: [
870
+ /* @__PURE__ */ jsx2(
871
+ "span",
872
+ {
873
+ style: {
874
+ display: "inline-block",
875
+ width: "10px",
876
+ height: "10px",
877
+ borderRadius: "3px",
878
+ background: colorIndicator,
879
+ flexShrink: 0
880
+ }
881
+ }
882
+ ),
883
+ /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0, flex: 1 }, children: [
884
+ /* @__PURE__ */ jsx2(
885
+ "div",
886
+ {
887
+ style: {
888
+ fontSize: "13px",
889
+ fontWeight: 600,
890
+ overflow: "hidden",
891
+ textOverflow: "ellipsis",
892
+ whiteSpace: "nowrap"
893
+ },
894
+ children: agent.displayName
895
+ }
896
+ ),
897
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "11px", opacity: 0.55 }, children: agent.slug })
898
+ ] }),
899
+ /* @__PURE__ */ jsxs2(
900
+ "div",
901
+ {
902
+ style: {
903
+ display: "flex",
904
+ alignItems: "center",
905
+ gap: "6px",
906
+ flexShrink: 0
907
+ },
908
+ children: [
909
+ /* @__PURE__ */ jsx2(StarCount, { count: agent.starCount }),
910
+ /* @__PURE__ */ jsx2(TrustBadge, { score: agent.trustScore })
911
+ ]
912
+ }
913
+ )
914
+ ]
915
+ }
916
+ ),
917
+ agent.description ? /* @__PURE__ */ jsx2("div", { style: mutedTextStyle, children: truncateText(agent.description, 120) }) : null,
918
+ /* @__PURE__ */ jsxs2(
919
+ "div",
920
+ {
921
+ style: {
922
+ display: "flex",
923
+ alignItems: "center",
924
+ justifyContent: "space-between",
925
+ gap: "8px"
926
+ },
927
+ children: [
928
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexWrap: "wrap", gap: "4px" }, children: [
929
+ agent.model ? /* @__PURE__ */ jsx2(Pill, { label: agent.model }) : null,
930
+ agent.attestations.slice(0, 2).map((att) => /* @__PURE__ */ jsx2(Pill, { label: att, color: "#2563eb" }, att)),
931
+ agent.skills.length > 0 ? /* @__PURE__ */ jsx2(
932
+ Pill,
933
+ {
934
+ label: `${agent.skills.length} skill${agent.skills.length === 1 ? "" : "s"}`
935
+ }
936
+ ) : null
937
+ ] }),
938
+ /* @__PURE__ */ jsx2(
939
+ "button",
940
+ {
941
+ type: "button",
942
+ style: primaryButtonStyle2,
943
+ onClick: (e) => {
944
+ e.stopPropagation();
945
+ onHire();
946
+ },
947
+ children: "Hire Agent"
948
+ }
949
+ )
950
+ ]
951
+ }
952
+ )
953
+ ]
954
+ }
955
+ );
956
+ }
957
+ function AgentDetail({
958
+ agent,
959
+ onBack,
960
+ onHire
961
+ }) {
962
+ return /* @__PURE__ */ jsxs2("div", { style: layoutStack2, children: [
963
+ /* @__PURE__ */ jsxs2("div", { style: rowStyle2, children: [
964
+ /* @__PURE__ */ jsx2("button", { type: "button", style: buttonStyle2, onClick: onBack, children: "Back" }),
965
+ /* @__PURE__ */ jsx2("strong", { style: { fontSize: "16px" }, children: agent.displayName }),
966
+ /* @__PURE__ */ jsx2(Pill, { label: agent.slug })
967
+ ] }),
968
+ /* @__PURE__ */ jsxs2(
969
+ "div",
970
+ {
971
+ style: {
972
+ display: "grid",
973
+ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
974
+ gap: "12px"
975
+ },
976
+ children: [
977
+ /* @__PURE__ */ jsxs2("div", { style: subtleCardStyle, children: [
978
+ /* @__PURE__ */ jsx2("div", { style: eyebrowStyle, children: "Model" }),
979
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "13px", marginTop: "4px" }, children: agent.model ?? "Not specified" })
980
+ ] }),
981
+ /* @__PURE__ */ jsxs2("div", { style: subtleCardStyle, children: [
982
+ /* @__PURE__ */ jsx2("div", { style: eyebrowStyle, children: "Stars" }),
983
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "13px", marginTop: "4px" }, children: agent.starCount })
984
+ ] }),
985
+ /* @__PURE__ */ jsxs2("div", { style: subtleCardStyle, children: [
986
+ /* @__PURE__ */ jsx2("div", { style: eyebrowStyle, children: "Trust Score" }),
987
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "6px", marginTop: "4px" }, children: [
988
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "13px" }, children: agent.trustScore !== null ? `${agent.trustScore}/100` : "N/A" }),
989
+ /* @__PURE__ */ jsx2(TrustBadge, { score: agent.trustScore })
990
+ ] })
991
+ ] }),
992
+ /* @__PURE__ */ jsxs2("div", { style: subtleCardStyle, children: [
993
+ /* @__PURE__ */ jsx2("div", { style: eyebrowStyle, children: "Created" }),
994
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "13px", marginTop: "4px" }, children: relativeTime(agent.createdAt) })
995
+ ] })
996
+ ]
997
+ }
998
+ ),
999
+ agent.description ? /* @__PURE__ */ jsx2(Section, { title: "Description", children: /* @__PURE__ */ jsx2("div", { style: { fontSize: "13px", lineHeight: 1.55 }, children: agent.description }) }) : null,
1000
+ agent.attestations.length > 0 ? /* @__PURE__ */ jsx2(Section, { title: "Attestations", children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: agent.attestations.map((att) => /* @__PURE__ */ jsx2(Pill, { label: att, color: "#2563eb" }, att)) }) }) : null,
1001
+ agent.skills.length > 0 ? /* @__PURE__ */ jsx2(Section, { title: "Skills", children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: agent.skills.map((skill) => /* @__PURE__ */ jsx2(Pill, { label: skill }, skill)) }) }) : null,
1002
+ agent.color ? /* @__PURE__ */ jsx2(Section, { title: "Theme Color", children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1003
+ /* @__PURE__ */ jsx2(
1004
+ "span",
1005
+ {
1006
+ style: {
1007
+ display: "inline-block",
1008
+ width: "20px",
1009
+ height: "20px",
1010
+ borderRadius: "4px",
1011
+ background: agent.color,
1012
+ border: "1px solid var(--border)"
1013
+ }
1014
+ }
1015
+ ),
1016
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", fontFamily: "monospace" }, children: agent.color })
1017
+ ] }) }) : null,
1018
+ /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2("button", { type: "button", style: primaryButtonStyle2, onClick: onHire, children: "Hire This Agent" }) })
1019
+ ] });
1020
+ }
1021
+ function SkillCard({ skill }) {
1022
+ return /* @__PURE__ */ jsxs2("div", { style: { ...subtleCardStyle, display: "grid", gap: "8px" }, children: [
1023
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1024
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
1025
+ /* @__PURE__ */ jsx2(
1026
+ "div",
1027
+ {
1028
+ style: {
1029
+ fontSize: "13px",
1030
+ fontWeight: 600,
1031
+ overflow: "hidden",
1032
+ textOverflow: "ellipsis",
1033
+ whiteSpace: "nowrap"
1034
+ },
1035
+ children: skill.displayName
1036
+ }
1037
+ ),
1038
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "11px", opacity: 0.55 }, children: skill.slug })
1039
+ ] }),
1040
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 }, children: [
1041
+ skill.category ? /* @__PURE__ */ jsx2(Pill, { label: skill.category }) : null,
1042
+ /* @__PURE__ */ jsx2(StarCount, { count: skill.starCount })
1043
+ ] })
1044
+ ] }),
1045
+ skill.description ? /* @__PURE__ */ jsx2("div", { style: mutedTextStyle, children: truncateText(skill.description, 140) }) : null
1046
+ ] });
1047
+ }
1048
+ function SyncProgressBar({
1049
+ companyId
1050
+ }) {
1051
+ const syncProgress = usePluginStream("sync-progress", {
1052
+ companyId: companyId ?? void 0
1053
+ });
1054
+ const latest = syncProgress.lastEvent;
1055
+ if (!latest || !syncProgress.connected) return null;
1056
+ const pct = Math.min(100, Math.max(0, latest.progress));
1057
+ return /* @__PURE__ */ jsxs2("div", { style: { ...subtleCardStyle, display: "grid", gap: "6px" }, children: [
1058
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
1059
+ /* @__PURE__ */ jsx2("span", { style: eyebrowStyle, children: latest.phase }),
1060
+ /* @__PURE__ */ jsxs2("span", { style: { fontSize: "11px", opacity: 0.6 }, children: [
1061
+ pct,
1062
+ "%"
1063
+ ] })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsx2(
1066
+ "div",
1067
+ {
1068
+ style: {
1069
+ height: "4px",
1070
+ borderRadius: "2px",
1071
+ background: "color-mix(in srgb, var(--border) 50%, transparent)",
1072
+ overflow: "hidden"
1073
+ },
1074
+ children: /* @__PURE__ */ jsx2(
1075
+ "div",
1076
+ {
1077
+ style: {
1078
+ height: "100%",
1079
+ width: `${pct}%`,
1080
+ borderRadius: "2px",
1081
+ background: "var(--foreground)",
1082
+ transition: "width 0.3s ease"
1083
+ }
1084
+ }
1085
+ )
1086
+ }
1087
+ ),
1088
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "11px", opacity: 0.6 }, children: latest.message })
1089
+ ] });
1090
+ }
1091
+ function ClawNetMarketplacePage({ context }) {
1092
+ const companyId = context.companyId;
1093
+ const toast = usePluginToast2();
1094
+ const [activeTab, setActiveTab] = useState2("agents");
1095
+ const [search, setSearch] = useState2("");
1096
+ const [debouncedSearch, setDebouncedSearch] = useState2("");
1097
+ const [page, setPage] = useState2(1);
1098
+ const [detailView, setDetailView] = useState2(null);
1099
+ useEffect2(() => {
1100
+ const timer = setTimeout(() => {
1101
+ setDebouncedSearch(search);
1102
+ setPage(1);
1103
+ }, 300);
1104
+ return () => clearTimeout(timer);
1105
+ }, [search]);
1106
+ const agentParams = useMemo(
1107
+ () => companyId ? {
1108
+ companyId,
1109
+ search: debouncedSearch || void 0,
1110
+ page,
1111
+ limit: AGENTS_PER_PAGE
1112
+ } : {},
1113
+ [companyId, debouncedSearch, page]
1114
+ );
1115
+ const {
1116
+ data: agentData,
1117
+ loading: agentsLoading,
1118
+ error: agentsError
1119
+ } = usePluginData2("clawnet-agents", agentParams);
1120
+ const skillParams = useMemo(
1121
+ () => companyId ? { companyId, search: debouncedSearch || void 0 } : {},
1122
+ [companyId, debouncedSearch]
1123
+ );
1124
+ const {
1125
+ data: skillData,
1126
+ loading: skillsLoading,
1127
+ error: skillsError
1128
+ } = usePluginData2("clawnet-skills", skillParams);
1129
+ const syncParams = useMemo(
1130
+ () => companyId ? { companyId } : {},
1131
+ [companyId]
1132
+ );
1133
+ const { data: syncStatus, refresh: refreshSync } = usePluginData2(
1134
+ "sync-status",
1135
+ syncParams
1136
+ );
1137
+ const triggerSync = usePluginAction2("trigger-sync");
1138
+ const [syncing, setSyncing] = useState2(false);
1139
+ async function handleSync() {
1140
+ if (!companyId || syncing) return;
1141
+ setSyncing(true);
1142
+ try {
1143
+ await triggerSync({ companyId });
1144
+ refreshSync();
1145
+ toast({
1146
+ title: "Sync started",
1147
+ body: "Refreshing agent and skill data from ClawNet registry.",
1148
+ tone: "success"
1149
+ });
1150
+ } catch (err) {
1151
+ toast({
1152
+ title: "Sync failed",
1153
+ body: err instanceof Error ? err.message : String(err),
1154
+ tone: "error"
1155
+ });
1156
+ } finally {
1157
+ setSyncing(false);
1158
+ }
1159
+ }
1160
+ function handleHireAgent(agent) {
1161
+ toast({
1162
+ title: `Ready to hire: ${agent.displayName}`,
1163
+ body: "Go to the Agents page and create a new agent. The template data from this ClawNet agent is ready to use.",
1164
+ tone: "info",
1165
+ ttlMs: 8e3,
1166
+ action: {
1167
+ label: "Go to Agents",
1168
+ href: hostPath(context.companyPrefix, "/agents")
1169
+ }
1170
+ });
1171
+ }
1172
+ if (detailView) {
1173
+ return /* @__PURE__ */ jsx2("div", { style: { ...layoutStack2, maxWidth: "800px" }, children: /* @__PURE__ */ jsx2(
1174
+ AgentDetail,
1175
+ {
1176
+ agent: detailView.agent,
1177
+ onBack: () => setDetailView(null),
1178
+ onHire: () => handleHireAgent(detailView.agent)
1179
+ }
1180
+ ) });
1181
+ }
1182
+ if (!companyId) {
1183
+ return /* @__PURE__ */ jsx2("div", { style: layoutStack2, children: /* @__PURE__ */ jsx2(Section, { title: "ClawNet Marketplace", children: /* @__PURE__ */ jsx2(EmptyState, { message: "Select a company to browse the agent marketplace." }) }) });
1184
+ }
1185
+ const agents = agentData?.agents ?? [];
1186
+ const agentTotal = agentData?.total ?? 0;
1187
+ const hasMoreAgents = agents.length < agentTotal && page * AGENTS_PER_PAGE < agentTotal;
1188
+ const skills = skillData?.skills ?? [];
1189
+ const skillTotal = skillData?.total ?? 0;
1190
+ return /* @__PURE__ */ jsxs2("div", { style: layoutStack2, children: [
1191
+ /* @__PURE__ */ jsxs2(
1192
+ "div",
1193
+ {
1194
+ style: {
1195
+ display: "flex",
1196
+ alignItems: "center",
1197
+ justifyContent: "space-between",
1198
+ gap: "12px",
1199
+ flexWrap: "wrap"
1200
+ },
1201
+ children: [
1202
+ /* @__PURE__ */ jsxs2("div", { children: [
1203
+ /* @__PURE__ */ jsx2("h1", { style: { fontSize: "18px", fontWeight: 700, margin: 0 }, children: "ClawNet Marketplace" }),
1204
+ /* @__PURE__ */ jsx2("div", { style: mutedTextStyle, children: syncStatus ? `${syncStatus.agentCount} agents, ${syncStatus.skillCount} skills available. Last sync: ${relativeTime(syncStatus.lastSyncAt)}` : "Loading registry status..." })
1205
+ ] }),
1206
+ /* @__PURE__ */ jsx2(
1207
+ "button",
1208
+ {
1209
+ type: "button",
1210
+ style: buttonStyle2,
1211
+ onClick: () => void handleSync(),
1212
+ disabled: syncing,
1213
+ children: syncing ? "Syncing..." : "Refresh Registry"
1214
+ }
1215
+ )
1216
+ ]
1217
+ }
1218
+ ),
1219
+ /* @__PURE__ */ jsx2(SyncProgressBar, { companyId }),
1220
+ /* @__PURE__ */ jsx2(
1221
+ SearchBar,
1222
+ {
1223
+ value: search,
1224
+ onChange: setSearch,
1225
+ placeholder: activeTab === "agents" ? "Search agents by name, skill, or model..." : "Search skills by name or category..."
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ jsx2(
1229
+ TabBar,
1230
+ {
1231
+ active: activeTab,
1232
+ onChange: (tab) => {
1233
+ setActiveTab(tab);
1234
+ setSearch("");
1235
+ setPage(1);
1236
+ }
1237
+ }
1238
+ ),
1239
+ activeTab === "agents" ? /* @__PURE__ */ jsx2("div", { style: layoutStack2, children: agentsLoading && agents.length === 0 ? /* @__PURE__ */ jsx2(LoadingIndicator, { message: "Loading agents from registry..." }) : agentsError ? /* @__PURE__ */ jsx2(ErrorBanner, { message: agentsError.message }) : agents.length === 0 ? /* @__PURE__ */ jsx2(
1240
+ EmptyState,
1241
+ {
1242
+ message: debouncedSearch ? `No agents found matching "${debouncedSearch}".` : "No agents available. Try syncing the registry."
1243
+ }
1244
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1245
+ /* @__PURE__ */ jsxs2("div", { style: { fontSize: "12px", opacity: 0.6 }, children: [
1246
+ "Showing ",
1247
+ agents.length,
1248
+ " of ",
1249
+ agentTotal,
1250
+ " agent",
1251
+ agentTotal === 1 ? "" : "s",
1252
+ debouncedSearch ? ` matching "${debouncedSearch}"` : ""
1253
+ ] }),
1254
+ /* @__PURE__ */ jsx2("div", { style: { display: "grid", gap: "10px" }, children: agents.map((agent) => /* @__PURE__ */ jsx2(
1255
+ AgentCard,
1256
+ {
1257
+ agent,
1258
+ onSelect: () => setDetailView({ agent }),
1259
+ onHire: () => handleHireAgent(agent)
1260
+ },
1261
+ agent.id
1262
+ )) }),
1263
+ hasMoreAgents ? /* @__PURE__ */ jsx2("div", { style: { display: "flex", justifyContent: "center", paddingTop: "8px" }, children: /* @__PURE__ */ jsx2(
1264
+ "button",
1265
+ {
1266
+ type: "button",
1267
+ style: buttonStyle2,
1268
+ onClick: () => setPage((p) => p + 1),
1269
+ disabled: agentsLoading,
1270
+ children: agentsLoading ? "Loading..." : `Load more (${agentTotal - agents.length} remaining)`
1271
+ }
1272
+ ) }) : null
1273
+ ] }) }) : null,
1274
+ activeTab === "skills" ? /* @__PURE__ */ jsx2("div", { style: layoutStack2, children: skillsLoading ? /* @__PURE__ */ jsx2(LoadingIndicator, { message: "Loading skills..." }) : skillsError ? /* @__PURE__ */ jsx2(ErrorBanner, { message: skillsError.message }) : skills.length === 0 ? /* @__PURE__ */ jsx2(
1275
+ EmptyState,
1276
+ {
1277
+ message: debouncedSearch ? `No skills found matching "${debouncedSearch}".` : "No skills available. Try syncing the registry."
1278
+ }
1279
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1280
+ /* @__PURE__ */ jsxs2("div", { style: { fontSize: "12px", opacity: 0.6 }, children: [
1281
+ skillTotal,
1282
+ " skill",
1283
+ skillTotal === 1 ? "" : "s",
1284
+ " available",
1285
+ debouncedSearch ? ` matching "${debouncedSearch}"` : ""
1286
+ ] }),
1287
+ /* @__PURE__ */ jsx2("div", { style: { display: "grid", gap: "10px" }, children: skills.map((skill) => /* @__PURE__ */ jsx2(SkillCard, { skill }, skill.id)) })
1288
+ ] }) }) : null
1289
+ ] });
1290
+ }
1291
+ function ClawNetSidebarLink({ context }) {
1292
+ const syncParams = useMemo(
1293
+ () => context.companyId ? { companyId: context.companyId } : {},
1294
+ [context.companyId]
1295
+ );
1296
+ const { data: syncStatus } = usePluginData2("sync-status", syncParams);
1297
+ const href = pluginPagePath(context.companyPrefix);
1298
+ const isActive = typeof window !== "undefined" && window.location.pathname === href;
1299
+ const agentCount = syncStatus?.agentCount ?? 0;
1300
+ return /* @__PURE__ */ jsxs2(
1301
+ "a",
1302
+ {
1303
+ href,
1304
+ "aria-current": isActive ? "page" : void 0,
1305
+ style: {
1306
+ display: "flex",
1307
+ alignItems: "center",
1308
+ gap: "10px",
1309
+ padding: "8px 12px",
1310
+ fontSize: "13px",
1311
+ fontWeight: isActive ? 600 : 400,
1312
+ textDecoration: "none",
1313
+ color: isActive ? "var(--foreground)" : "color-mix(in srgb, var(--foreground) 80%, transparent)",
1314
+ background: isActive ? "color-mix(in srgb, var(--accent, var(--muted)) 60%, transparent)" : "transparent",
1315
+ borderRadius: "6px",
1316
+ transition: "background 0.15s, color 0.15s",
1317
+ cursor: "pointer"
1318
+ },
1319
+ children: [
1320
+ /* @__PURE__ */ jsxs2(
1321
+ "svg",
1322
+ {
1323
+ width: "16",
1324
+ height: "16",
1325
+ viewBox: "0 0 24 24",
1326
+ fill: "none",
1327
+ stroke: "currentColor",
1328
+ strokeWidth: "1.9",
1329
+ strokeLinecap: "round",
1330
+ strokeLinejoin: "round",
1331
+ "aria-hidden": "true",
1332
+ style: { flexShrink: 0 },
1333
+ children: [
1334
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "5", r: "2.5" }),
1335
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "19", r: "2.5" }),
1336
+ /* @__PURE__ */ jsx2("circle", { cx: "19", cy: "19", r: "2.5" }),
1337
+ /* @__PURE__ */ jsx2("path", { d: "M12 7.5v4" }),
1338
+ /* @__PURE__ */ jsx2("path", { d: "M7.5 17.5l3-6" }),
1339
+ /* @__PURE__ */ jsx2("path", { d: "M16.5 17.5l-3-6" })
1340
+ ]
1341
+ }
1342
+ ),
1343
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1 }, children: "ClawNet" }),
1344
+ agentCount > 0 ? /* @__PURE__ */ jsx2(
1345
+ "span",
1346
+ {
1347
+ style: {
1348
+ display: "inline-flex",
1349
+ alignItems: "center",
1350
+ justifyContent: "center",
1351
+ minWidth: "20px",
1352
+ height: "18px",
1353
+ borderRadius: "999px",
1354
+ background: "color-mix(in srgb, var(--foreground) 12%, transparent)",
1355
+ fontSize: "10px",
1356
+ fontWeight: 600,
1357
+ padding: "0 5px",
1358
+ flexShrink: 0
1359
+ },
1360
+ children: agentCount
1361
+ }
1362
+ ) : null
1363
+ ]
1364
+ }
1365
+ );
1366
+ }
1367
+ export {
1368
+ ClawNetFleetWidget,
1369
+ ClawNetMarketplacePage,
1370
+ ClawNetSettingsPage,
1371
+ ClawNetSidebarLink
1372
+ };
1373
+ //# sourceMappingURL=index.js.map