opencami 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +60 -0
  2. package/dist/client/assets/CSPContext-EgWK8bIJ.js +1 -0
  3. package/dist/client/assets/DirectionContext-DXtY05YF.js +1 -0
  4. package/dist/client/assets/_sessionKey-B89e7G3y.js +100 -0
  5. package/dist/client/assets/agents-screen-1BiEZ9od.js +1 -0
  6. package/dist/client/assets/agents-x54ocA9z.js +2 -0
  7. package/dist/client/assets/bots-screen-BNQciUeJ.js +1 -0
  8. package/dist/client/assets/bots-x86ZHG4b.js +2 -0
  9. package/dist/client/assets/button-nDcsaNPl.js +1 -0
  10. package/dist/client/assets/{connect-D3baVDFL.js → connect-w4lLOqiJ.js} +1 -1
  11. package/dist/client/assets/file-explorer-screen-CAsjd3w8.js +1 -0
  12. package/dist/client/assets/files-Bype5Mnb.js +2 -0
  13. package/dist/client/assets/{index-ByIsZcHh.js → index-36G0WCxU.js} +1 -1
  14. package/dist/client/assets/{index-CVV4XiZo.js → index-BXkRE220.js} +2 -2
  15. package/dist/client/assets/keyboard-shortcuts-dialog-BdCeXRjD.js +1 -0
  16. package/dist/client/assets/{main-CkIF0soY.js → main-B3N0eQFg.js} +129 -17
  17. package/dist/client/assets/{opencami-logo-CIxSO1oo.js → opencami-logo-DD0DPFRQ.js} +1 -1
  18. package/dist/client/assets/{react-BhVdgA5r.js → react-B16OrBeM.js} +1 -1
  19. package/dist/client/assets/search-dialog-BjTPceEl.js +1 -0
  20. package/dist/client/assets/session-export-dialog-DtHKG2zW.js +1 -0
  21. package/dist/client/assets/settings-dialog-hiqdk_UD.js +1 -0
  22. package/dist/client/assets/skills-DhwyFq3y.js +2 -0
  23. package/dist/client/assets/skills-panel-BLUjzfjJ.js +5 -0
  24. package/dist/client/assets/styles-CHP4l6vZ.css +1 -0
  25. package/dist/client/assets/switch-J6wLIVu2.js +1 -0
  26. package/dist/client/assets/tabs-DvPgTz5I.js +1 -0
  27. package/dist/client/assets/tooltip-C14vdXHK.js +1 -0
  28. package/dist/client/assets/use-file-explorer-state-BnaJEqRP.js +12 -0
  29. package/dist/client/assets/useButton-Bnnac1eR.js +1 -0
  30. package/dist/client/assets/useCompositeItem-BgiEMKAt.js +1 -0
  31. package/dist/client/assets/{useControlled-Y306krcC.js → useControlled-BhUuiHAm.js} +1 -1
  32. package/dist/client/assets/{useMutation-0WgW4xQJ.js → useMutation-CFmVaBag.js} +1 -1
  33. package/dist/client/assets/visuallyHidden-DCCICp6T.js +9 -0
  34. package/dist/server/assets/{_sessionKey-BhFH4uWY.js → _sessionKey-tRze5NLR.js} +178 -46
  35. package/dist/server/assets/_tanstack-start-manifest_v-CyfoMvUa.js +4 -0
  36. package/dist/server/assets/{agents-Dz_i76VW.js → agents-CmQ4vvXm.js} +1 -1
  37. package/dist/server/assets/{agents-screen-CqQPJndp.js → agents-screen-bmrIyFbk.js} +43 -35
  38. package/dist/server/assets/{bots-6ryCIgKh.js → bots-Byt6jv0a.js} +1 -1
  39. package/dist/server/assets/bots-screen-C2TGFv42.js +474 -0
  40. package/dist/server/assets/{button-DtQ3rV1m.js → button-CwY2OHFj.js} +2 -2
  41. package/dist/server/assets/{connect-B8jpGQGK.js → connect-d3AqjAqe.js} +2 -2
  42. package/dist/server/assets/{file-explorer-screen-DCfS_Ajx.js → file-explorer-screen-CVlFiAFu.js} +36 -36
  43. package/dist/server/assets/{files-D2GIrPF4.js → files-BIEcSPGp.js} +1 -1
  44. package/dist/server/assets/{index-BNSsDaLb.js → index-CNIATlJ9.js} +22 -3
  45. package/dist/server/assets/{index-B2JHn34C.js → index-CRfLKh30.js} +2 -1
  46. package/dist/server/assets/{keyboard-shortcuts-dialog-CqIm8aYF.js → keyboard-shortcuts-dialog-CsNP85q8.js} +2 -2
  47. package/dist/server/assets/{router-Dme7USeO.js → router-rn7pJO_D.js} +356 -64
  48. package/dist/server/assets/{search-dialog-DG0D9KRN.js → search-dialog-Bz4Cu0KW.js} +23 -6
  49. package/dist/server/assets/{session-export-dialog-DLPZVlQV.js → session-export-dialog-CwclV0Aj.js} +2 -2
  50. package/dist/server/assets/{settings-dialog-BaGT4e5l.js → settings-dialog-BBM7jCjE.js} +386 -95
  51. package/dist/server/assets/skills-Cy8xclXY.js +11 -0
  52. package/dist/server/assets/skills-panel-BnRNb7u9.js +762 -0
  53. package/dist/server/assets/{switch-DnX0MjGS.js → switch-BbkUeVDV.js} +1 -1
  54. package/dist/server/assets/tabs-DDFZob0m.js +67 -0
  55. package/dist/server/assets/{tooltip-gbV6rEVv.js → tooltip-DgsSPocE.js} +1 -1
  56. package/dist/server/assets/{use-file-explorer-state-DfAKF2gZ.js → use-file-explorer-state-Il1LlBAe.js} +1 -1
  57. package/dist/server/server.js +2 -2
  58. package/package.json +6 -2
  59. package/dist/client/assets/_sessionKey-DB95zj1L.js +0 -97
  60. package/dist/client/assets/agents-LFrWe-HX.js +0 -2
  61. package/dist/client/assets/agents-screen-CEBBk1yO.js +0 -1
  62. package/dist/client/assets/bots-CxDwf_WK.js +0 -2
  63. package/dist/client/assets/bots-screen-D6qma1wK.js +0 -1
  64. package/dist/client/assets/button-Il3CHIzX.js +0 -1
  65. package/dist/client/assets/file-explorer-screen-rtV6n-5_.js +0 -1
  66. package/dist/client/assets/files-DMemuq9D.js +0 -2
  67. package/dist/client/assets/keyboard-shortcuts-dialog-DXC0YHoy.js +0 -1
  68. package/dist/client/assets/search-dialog-I1jJplIh.js +0 -1
  69. package/dist/client/assets/session-export-dialog-CH5unryw.js +0 -1
  70. package/dist/client/assets/settings-dialog-B8v-GVJ8.js +0 -1
  71. package/dist/client/assets/styles-BaTVzdPa.css +0 -1
  72. package/dist/client/assets/switch-sQnv1YsK.js +0 -1
  73. package/dist/client/assets/tooltip-j_viC_EE.js +0 -1
  74. package/dist/client/assets/use-file-explorer-state-BUH-u7Jv.js +0 -12
  75. package/dist/client/assets/useButton-9VAzplAB.js +0 -9
  76. package/dist/server/assets/_tanstack-start-manifest_v-BaIrL1VQ.js +0 -4
  77. package/dist/server/assets/bots-screen-DS_ZF9Ec.js +0 -417
@@ -0,0 +1,762 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
3
+ import { HugeiconsIcon } from "@hugeicons/react";
4
+ import { ArrowLeft01Icon, Loading02Icon, ArrowUpRight01Icon, Download04Icon, StarIcon, Tick01Icon, Calendar01Icon, Search01Icon, Shield01Icon } from "@hugeicons/core-free-icons";
5
+ import { Link } from "@tanstack/react-router";
6
+ import { B as Button } from "./button-CwY2OHFj.js";
7
+ import { T as Tabs, a as TabsList, b as TabsTab } from "./tabs-DDFZob0m.js";
8
+ import "@base-ui/react/merge-props";
9
+ import "@base-ui/react/use-render";
10
+ import "class-variance-authority";
11
+ import "clsx";
12
+ import "tailwind-merge";
13
+ import "@base-ui/react/tabs";
14
+ async function fetchApi(url) {
15
+ const res = await fetch(url);
16
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
17
+ const data = await res.json();
18
+ if (!data.ok) throw new Error(data.error || "Unknown error");
19
+ return data.skills;
20
+ }
21
+ function useInstalledSkills() {
22
+ const [skills, setSkills] = useState([]);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState(null);
25
+ const refresh = useCallback(async () => {
26
+ setLoading(true);
27
+ setError(null);
28
+ try {
29
+ const data = await fetchApi("/api/skills?action=installed");
30
+ setSkills(data);
31
+ } catch (err) {
32
+ setError(err instanceof Error ? err.message : String(err));
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ }, []);
37
+ useEffect(() => {
38
+ void refresh();
39
+ }, [refresh]);
40
+ return { skills, loading, error, refresh };
41
+ }
42
+ function useExploreSkills(sort, limit) {
43
+ const [skills, setSkills] = useState([]);
44
+ const [loading, setLoading] = useState(true);
45
+ const [error, setError] = useState(null);
46
+ useEffect(() => {
47
+ let cancelled = false;
48
+ setLoading(true);
49
+ setError(null);
50
+ fetchApi(`/api/skills?action=explore&sort=${sort}&limit=${limit}`).then((data) => {
51
+ if (!cancelled) setSkills(data);
52
+ }).catch((err) => {
53
+ if (!cancelled) setError(err instanceof Error ? err.message : String(err));
54
+ }).finally(() => {
55
+ if (!cancelled) setLoading(false);
56
+ });
57
+ return () => {
58
+ cancelled = true;
59
+ };
60
+ }, [sort, limit]);
61
+ return { skills, loading, error };
62
+ }
63
+ function useSearchSkills(query) {
64
+ const [skills, setSkills] = useState([]);
65
+ const [loading, setLoading] = useState(false);
66
+ const [error, setError] = useState(null);
67
+ const timerRef = useRef(null);
68
+ useEffect(() => {
69
+ if (timerRef.current) clearTimeout(timerRef.current);
70
+ if (!query.trim()) {
71
+ setSkills([]);
72
+ setLoading(false);
73
+ return;
74
+ }
75
+ setLoading(true);
76
+ timerRef.current = setTimeout(() => {
77
+ fetchApi(`/api/skills?action=search&q=${encodeURIComponent(query)}&limit=10`).then(setSkills).catch((err) => setError(err instanceof Error ? err.message : String(err))).finally(() => setLoading(false));
78
+ }, 400);
79
+ return () => {
80
+ if (timerRef.current) clearTimeout(timerRef.current);
81
+ };
82
+ }, [query]);
83
+ return { skills, loading, error };
84
+ }
85
+ function useMySkills() {
86
+ const [skills, setSkills] = useState([]);
87
+ const [loading, setLoading] = useState(true);
88
+ const [error, setError] = useState(null);
89
+ const refresh = useCallback(async () => {
90
+ setLoading(true);
91
+ setError(null);
92
+ try {
93
+ const data = await fetchApi("/api/skills?action=my-skills");
94
+ setSkills(data);
95
+ } catch (err) {
96
+ setError(err instanceof Error ? err.message : String(err));
97
+ } finally {
98
+ setLoading(false);
99
+ }
100
+ }, []);
101
+ useEffect(() => {
102
+ void refresh();
103
+ }, [refresh]);
104
+ return { skills, loading, error, refresh };
105
+ }
106
+ function useRecommendedSkills() {
107
+ const [skills, setSkills] = useState([]);
108
+ const [loading, setLoading] = useState(true);
109
+ const [error, setError] = useState(null);
110
+ const refresh = useCallback(async () => {
111
+ setLoading(true);
112
+ setError(null);
113
+ try {
114
+ const data = await fetchApi("/api/skills?action=recommended");
115
+ setSkills(data);
116
+ } catch (err) {
117
+ setError(err instanceof Error ? err.message : String(err));
118
+ } finally {
119
+ setLoading(false);
120
+ }
121
+ }, []);
122
+ useEffect(() => {
123
+ void refresh();
124
+ }, [refresh]);
125
+ return { skills, loading, error, refresh };
126
+ }
127
+ function useInstallSkill() {
128
+ const [installing, setInstalling] = useState(null);
129
+ const [error, setError] = useState(null);
130
+ const install = useCallback(async (slug) => {
131
+ setInstalling(slug);
132
+ setError(null);
133
+ try {
134
+ const res = await fetch("/api/skills", {
135
+ method: "POST",
136
+ headers: { "Content-Type": "application/json" },
137
+ body: JSON.stringify({ action: "install", slug })
138
+ });
139
+ const data = await res.json();
140
+ if (!data.ok) throw new Error(data.error || "Install failed");
141
+ } catch (err) {
142
+ setError(err instanceof Error ? err.message : String(err));
143
+ throw err;
144
+ } finally {
145
+ setInstalling(null);
146
+ }
147
+ }, []);
148
+ return { install, installing, error };
149
+ }
150
+ function useUpdateSkill() {
151
+ const [updating, setUpdating] = useState(null);
152
+ const [error, setError] = useState(null);
153
+ const update = useCallback(async (slug) => {
154
+ setUpdating(slug);
155
+ setError(null);
156
+ try {
157
+ const res = await fetch("/api/skills", {
158
+ method: "POST",
159
+ headers: { "Content-Type": "application/json" },
160
+ body: JSON.stringify({ action: "update", slug })
161
+ });
162
+ const data = await res.json();
163
+ if (!data.ok) throw new Error(data.error || "Update failed");
164
+ } catch (err) {
165
+ setError(err instanceof Error ? err.message : String(err));
166
+ throw err;
167
+ } finally {
168
+ setUpdating(null);
169
+ }
170
+ }, []);
171
+ return { update, updating, error };
172
+ }
173
+ const TRUSTED_PUBLISHERS = /* @__PURE__ */ new Set([
174
+ "openclaw",
175
+ "clawhub",
176
+ "anthropic",
177
+ "robbyczgw-cla"
178
+ ]);
179
+ const VERIFIED_DOWNLOAD_THRESHOLD = 100;
180
+ function getBadgeInfo(skill, isInstalled) {
181
+ if (isInstalled) {
182
+ return { type: "installed", label: "Installed" };
183
+ }
184
+ const publisher = skill.publisher || skill.author || "";
185
+ const downloads = skill.stats?.downloads || 0;
186
+ if (TRUSTED_PUBLISHERS.has(publisher.toLowerCase()) || downloads >= VERIFIED_DOWNLOAD_THRESHOLD) {
187
+ return { type: "verified", label: "Verified" };
188
+ }
189
+ return { type: "community", label: "Community" };
190
+ }
191
+ function TrustBadge({ type, label }) {
192
+ const styles = {
193
+ verified: "bg-emerald-50 text-emerald-600 border-emerald-100",
194
+ community: "bg-primary-50 text-primary-500 border-primary-100",
195
+ installed: "bg-sky-50 text-sky-600 border-sky-100"
196
+ };
197
+ return /* @__PURE__ */ jsxs(
198
+ "span",
199
+ {
200
+ className: `inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full border ${styles[type]}`,
201
+ title: `${label} Skill`,
202
+ children: [
203
+ /* @__PURE__ */ jsx(
204
+ HugeiconsIcon,
205
+ {
206
+ icon: type === "installed" ? Tick01Icon : Shield01Icon,
207
+ size: 10,
208
+ strokeWidth: 2
209
+ }
210
+ ),
211
+ label
212
+ ]
213
+ }
214
+ );
215
+ }
216
+ function SkillCard({
217
+ skill,
218
+ installed,
219
+ installing,
220
+ onInstall,
221
+ onUpdate,
222
+ updating,
223
+ showUpdate,
224
+ onClick
225
+ }) {
226
+ const slug = skill.slug || "";
227
+ const name = skill.displayName || slug;
228
+ const summary = skill.summary;
229
+ const version = skill.version || skill.latestVersion?.version || "";
230
+ const downloads = skill.stats?.downloads;
231
+ const badge = getBadgeInfo(skill, installed);
232
+ return /* @__PURE__ */ jsxs(
233
+ "div",
234
+ {
235
+ className: `
236
+ group rounded-lg border border-primary-100 bg-surface p-4
237
+ transition-all duration-150 ease-out
238
+ ${onClick ? "cursor-pointer hover:border-primary-200 hover:shadow-sm" : ""}
239
+ `,
240
+ onClick,
241
+ children: [
242
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
243
+ /* @__PURE__ */ jsx("h4", { className: "text-[13px] font-semibold text-primary-900 leading-tight truncate", children: name }),
244
+ version && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-mono text-primary-400", children: version })
245
+ ] }),
246
+ summary && /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-500 leading-relaxed line-clamp-2 mb-3", children: summary }),
247
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-primary-50", children: [
248
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
249
+ /* @__PURE__ */ jsx(TrustBadge, { ...badge }),
250
+ downloads !== void 0 && downloads > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
251
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Download04Icon, size: 11, strokeWidth: 1.5 }),
252
+ downloads.toLocaleString()
253
+ ] })
254
+ ] }),
255
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", onClick: (e) => e.stopPropagation(), children: [
256
+ showUpdate && onUpdate && /* @__PURE__ */ jsx(
257
+ "button",
258
+ {
259
+ onClick: onUpdate,
260
+ disabled: !!updating,
261
+ className: "text-[11px] font-medium text-primary-500 hover:text-primary-700 transition-colors duration-150 disabled:opacity-50",
262
+ children: updating ? /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 12, className: "animate-spin" }) : "Update"
263
+ }
264
+ ),
265
+ !installed && /* @__PURE__ */ jsx(
266
+ "button",
267
+ {
268
+ onClick: onInstall,
269
+ disabled: installing,
270
+ className: "text-[11px] font-medium text-primary-600 hover:text-primary-800 transition-colors duration-150 disabled:opacity-50",
271
+ children: installing ? /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 12, className: "animate-spin" }) : "Install"
272
+ }
273
+ )
274
+ ] })
275
+ ] })
276
+ ]
277
+ }
278
+ );
279
+ }
280
+ function formatDate(timestamp) {
281
+ if (!timestamp) return "";
282
+ return new Date(timestamp).toLocaleDateString(void 0, {
283
+ year: "numeric",
284
+ month: "short",
285
+ day: "numeric"
286
+ });
287
+ }
288
+ function formatNumber(n) {
289
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
290
+ return n.toString();
291
+ }
292
+ function SkillDetailView({
293
+ skill,
294
+ installed,
295
+ installing,
296
+ onInstall,
297
+ onBack
298
+ }) {
299
+ const slug = skill.slug || "";
300
+ const name = skill.displayName || slug;
301
+ const version = skill.version || skill.latestVersion?.version || "";
302
+ const badge = getBadgeInfo(skill, installed);
303
+ const downloads = skill.stats?.downloads || 0;
304
+ const tags = skill.tags ? Array.isArray(skill.tags) ? skill.tags : Object.keys(skill.tags).filter((k) => k !== "latest") : [];
305
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
306
+ /* @__PURE__ */ jsxs(
307
+ "button",
308
+ {
309
+ onClick: onBack,
310
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary-500 hover:text-primary-700 transition-colors duration-150",
311
+ children: [
312
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowLeft01Icon, size: 14, strokeWidth: 2 }),
313
+ "Back to browse"
314
+ ]
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
318
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
319
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
320
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-primary-900 leading-tight", children: name }),
321
+ version && /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono text-primary-400", children: [
322
+ "v",
323
+ version
324
+ ] })
325
+ ] }),
326
+ /* @__PURE__ */ jsx(TrustBadge, { ...badge })
327
+ ] }),
328
+ skill.summary && /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600 leading-relaxed", children: skill.summary })
329
+ ] }),
330
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 py-4 border-y border-primary-100", children: [
331
+ /* @__PURE__ */ jsxs("div", { children: [
332
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: formatNumber(downloads) }),
333
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-primary-400 uppercase tracking-wide", children: "Downloads" })
334
+ ] }),
335
+ skill.stats?.versions !== void 0 && /* @__PURE__ */ jsxs("div", { children: [
336
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: skill.stats.versions }),
337
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-primary-400 uppercase tracking-wide", children: "Versions" })
338
+ ] }),
339
+ skill.stats?.stars !== void 0 && skill.stats.stars > 0 && /* @__PURE__ */ jsxs("div", { children: [
340
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: skill.stats.stars }),
341
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] text-primary-400 uppercase tracking-wide", children: "Stars" })
342
+ ] })
343
+ ] }),
344
+ skill.latestVersion?.changelog && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
345
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
346
+ /* @__PURE__ */ jsx("h4", { className: "text-xs font-semibold text-primary-700 uppercase tracking-wide", children: "Latest Release" }),
347
+ skill.latestVersion.createdAt && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
348
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Calendar01Icon, size: 11, strokeWidth: 1.5 }),
349
+ formatDate(skill.latestVersion.createdAt)
350
+ ] })
351
+ ] }),
352
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600 leading-relaxed", children: skill.latestVersion.changelog })
353
+ ] }),
354
+ tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: tags.map((tag) => /* @__PURE__ */ jsx(
355
+ "span",
356
+ {
357
+ className: "text-[10px] font-medium text-primary-500 bg-primary-50 px-2 py-1 rounded",
358
+ children: tag
359
+ },
360
+ tag
361
+ )) }),
362
+ (skill.createdAt || skill.updatedAt) && /* @__PURE__ */ jsxs("div", { className: "text-[11px] text-primary-400 space-y-0.5", children: [
363
+ skill.createdAt && /* @__PURE__ */ jsxs("div", { children: [
364
+ "Published ",
365
+ formatDate(skill.createdAt)
366
+ ] }),
367
+ skill.updatedAt && /* @__PURE__ */ jsxs("div", { children: [
368
+ "Last updated ",
369
+ formatDate(skill.updatedAt)
370
+ ] })
371
+ ] }),
372
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 pt-2", children: [
373
+ installed ? /* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center justify-center gap-2 py-2.5 rounded-lg bg-emerald-50 text-emerald-600 text-sm font-medium border border-emerald-100", children: [
374
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 15, strokeWidth: 2 }),
375
+ "Installed"
376
+ ] }) : /* @__PURE__ */ jsx(
377
+ Button,
378
+ {
379
+ onClick: onInstall,
380
+ disabled: installing,
381
+ className: "flex-1",
382
+ size: "sm",
383
+ children: installing ? /* @__PURE__ */ jsxs(Fragment, { children: [
384
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 14, className: "animate-spin mr-1.5" }),
385
+ "Installing…"
386
+ ] }) : "Install Skill"
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxs(
390
+ "a",
391
+ {
392
+ href: `https://clawhub.com/skills/${slug}`,
393
+ target: "_blank",
394
+ rel: "noopener noreferrer",
395
+ className: "inline-flex items-center gap-1 px-4 py-2 text-sm font-medium text-primary-600 hover:text-primary-800 transition-colors duration-150",
396
+ children: [
397
+ "View on ClawHub",
398
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowUpRight01Icon, size: 14, strokeWidth: 1.5 })
399
+ ]
400
+ }
401
+ )
402
+ ] })
403
+ ] });
404
+ }
405
+ function InstalledTab() {
406
+ const { skills, loading, error, refresh } = useInstalledSkills();
407
+ const { update, updating } = useUpdateSkill();
408
+ const handleUpdate = async (name) => {
409
+ try {
410
+ await update(name);
411
+ void refresh();
412
+ } catch {
413
+ }
414
+ };
415
+ if (loading) {
416
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 18, className: "animate-spin text-primary-300" }) });
417
+ }
418
+ if (error) {
419
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-red-500 py-8 text-center", children: error });
420
+ }
421
+ if (skills.length === 0) {
422
+ return /* @__PURE__ */ jsxs("div", { className: "py-12 text-center", children: [
423
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-400", children: "No skills installed" }),
424
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-300 mt-1", children: "Browse the catalog to get started" })
425
+ ] });
426
+ }
427
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: skills.map((skill) => /* @__PURE__ */ jsx(
428
+ SkillCard,
429
+ {
430
+ skill: { slug: skill.name, displayName: skill.name, version: skill.version },
431
+ installed: true,
432
+ installing: false,
433
+ onInstall: () => {
434
+ },
435
+ onUpdate: () => handleUpdate(skill.name),
436
+ updating: updating === skill.name,
437
+ showUpdate: true
438
+ },
439
+ skill.name
440
+ )) });
441
+ }
442
+ function MySkillsTab() {
443
+ const { skills, loading, error } = useMySkills();
444
+ const totalDownloads = useMemo(
445
+ () => skills.reduce((sum, s) => sum + (s.stats?.downloads || 0), 0),
446
+ [skills]
447
+ );
448
+ const totalStars = useMemo(
449
+ () => skills.reduce((sum, s) => sum + (s.stats?.stars || 0), 0),
450
+ [skills]
451
+ );
452
+ if (loading) {
453
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 18, className: "animate-spin text-primary-300" }) });
454
+ }
455
+ if (error) {
456
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-red-500 py-8 text-center", children: error });
457
+ }
458
+ if (skills.length === 0) {
459
+ return /* @__PURE__ */ jsxs("div", { className: "py-12 text-center", children: [
460
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-400", children: "No skills configured" }),
461
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-300 mt-1", children: "Add slugs to .clawhub-my-skills.json" })
462
+ ] });
463
+ }
464
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
465
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 py-3 px-4 rounded-lg bg-primary-50/60 border border-primary-100", children: [
466
+ /* @__PURE__ */ jsxs("div", { children: [
467
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: skills.length }),
468
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] text-primary-400 uppercase tracking-wide", children: "Skills" })
469
+ ] }),
470
+ /* @__PURE__ */ jsxs("div", { children: [
471
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: totalDownloads.toLocaleString() }),
472
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] text-primary-400 uppercase tracking-wide", children: "Downloads" })
473
+ ] }),
474
+ totalStars > 0 && /* @__PURE__ */ jsxs("div", { children: [
475
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-primary-900", children: totalStars }),
476
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] text-primary-400 uppercase tracking-wide", children: "Stars" })
477
+ ] })
478
+ ] }),
479
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: skills.map((skill) => {
480
+ const slug = skill.slug || "";
481
+ const name = skill.displayName || slug;
482
+ const version = skill.version || skill.latestVersion?.version || "";
483
+ const downloads = skill.stats?.downloads || 0;
484
+ const stars = skill.stats?.stars || 0;
485
+ const installs = skill.stats?.installsAllTime || skill.stats?.installsCurrent || 0;
486
+ return /* @__PURE__ */ jsxs(
487
+ "a",
488
+ {
489
+ href: `https://clawhub.com/skills/${slug}`,
490
+ target: "_blank",
491
+ rel: "noopener noreferrer",
492
+ className: "group block rounded-lg border border-primary-100 bg-surface p-4 transition-all duration-150 ease-out hover:border-primary-200 hover:shadow-sm",
493
+ children: [
494
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-1.5", children: [
495
+ /* @__PURE__ */ jsx("h4", { className: "text-[13px] font-semibold text-primary-900 leading-tight truncate group-hover:text-primary-700 transition-colors duration-150", children: name }),
496
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
497
+ version && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-primary-400", children: version }),
498
+ /* @__PURE__ */ jsx(
499
+ HugeiconsIcon,
500
+ {
501
+ icon: ArrowUpRight01Icon,
502
+ size: 12,
503
+ strokeWidth: 1.5,
504
+ className: "text-primary-300 opacity-0 group-hover:opacity-100 transition-opacity duration-150"
505
+ }
506
+ )
507
+ ] })
508
+ ] }),
509
+ skill.summary && /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-500 leading-relaxed line-clamp-2 mb-3", children: skill.summary }),
510
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 pt-2 border-t border-primary-50", children: [
511
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
512
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Download04Icon, size: 11, strokeWidth: 1.5 }),
513
+ downloads.toLocaleString()
514
+ ] }),
515
+ stars > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
516
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StarIcon, size: 11, strokeWidth: 1.5 }),
517
+ stars
518
+ ] }),
519
+ installs > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
520
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 11, strokeWidth: 1.5 }),
521
+ installs.toLocaleString()
522
+ ] }),
523
+ skill.updatedAt && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1 ml-auto", children: [
524
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Calendar01Icon, size: 11, strokeWidth: 1.5 }),
525
+ formatDate(skill.updatedAt)
526
+ ] })
527
+ ] })
528
+ ]
529
+ },
530
+ slug
531
+ );
532
+ }) })
533
+ ] });
534
+ }
535
+ function RecommendedTab() {
536
+ const { skills, loading, error } = useRecommendedSkills();
537
+ const { skills: installedSkills } = useInstalledSkills();
538
+ const { install, installing } = useInstallSkill();
539
+ const refreshInstalled = useInstalledSkills().refresh;
540
+ const installedSet = useMemo(
541
+ () => new Set(installedSkills.map((s) => s.name)),
542
+ [installedSkills]
543
+ );
544
+ const handleInstall = async (slug) => {
545
+ try {
546
+ await install(slug);
547
+ void refreshInstalled();
548
+ } catch {
549
+ }
550
+ };
551
+ if (loading) {
552
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 18, className: "animate-spin text-primary-300" }) });
553
+ }
554
+ if (error) {
555
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-red-500 py-8 text-center", children: error });
556
+ }
557
+ if (skills.length === 0) {
558
+ return /* @__PURE__ */ jsx("div", { className: "py-12 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-400", children: "No recommended skills available" }) });
559
+ }
560
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
561
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-primary-500", children: [
562
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StarIcon, size: 14, strokeWidth: 1.5, className: "text-amber-400" }),
563
+ /* @__PURE__ */ jsx("span", { children: "Curated skills we recommend" })
564
+ ] }),
565
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: skills.map((skill) => {
566
+ const slug = skill.slug || "";
567
+ const name = skill.displayName || slug;
568
+ const isInstalled = installedSet.has(slug) || installedSet.has(name);
569
+ return /* @__PURE__ */ jsxs(
570
+ "div",
571
+ {
572
+ className: "group relative rounded-lg border border-primary-100 bg-surface p-4 transition-all duration-150 ease-out hover:border-primary-200 hover:shadow-sm",
573
+ children: [
574
+ /* @__PURE__ */ jsx("div", { className: "absolute -top-1.5 -right-1.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[9px] font-medium rounded-full bg-amber-50 text-amber-600 border border-amber-100", children: [
575
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StarIcon, size: 8, strokeWidth: 2 }),
576
+ "Pick"
577
+ ] }) }),
578
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
579
+ /* @__PURE__ */ jsx("h4", { className: "text-[13px] font-semibold text-primary-900 leading-tight truncate", children: name }),
580
+ skill.version && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-mono text-primary-400", children: skill.version })
581
+ ] }),
582
+ skill.summary && /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-500 leading-relaxed line-clamp-2 mb-3", children: skill.summary }),
583
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-primary-50", children: [
584
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
585
+ skill.stats?.downloads !== void 0 && skill.stats.downloads > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
586
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Download04Icon, size: 11, strokeWidth: 1.5 }),
587
+ skill.stats.downloads.toLocaleString()
588
+ ] }),
589
+ skill.stats?.stars !== void 0 && skill.stats.stars > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-primary-400 flex items-center gap-1", children: [
590
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StarIcon, size: 11, strokeWidth: 1.5 }),
591
+ skill.stats.stars
592
+ ] })
593
+ ] }),
594
+ /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: isInstalled ? /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-medium text-emerald-500 flex items-center gap-1", children: [
595
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 11, strokeWidth: 2 }),
596
+ "Installed"
597
+ ] }) : /* @__PURE__ */ jsx(
598
+ "button",
599
+ {
600
+ onClick: () => handleInstall(slug),
601
+ disabled: installing === slug,
602
+ className: "text-[11px] font-medium text-primary-600 hover:text-primary-800 transition-colors duration-150 disabled:opacity-50",
603
+ children: installing === slug ? /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 12, className: "animate-spin" }) : "Install"
604
+ }
605
+ ) })
606
+ ] })
607
+ ]
608
+ },
609
+ slug
610
+ );
611
+ }) })
612
+ ] });
613
+ }
614
+ function sortSkills(skills, sortBy) {
615
+ return [...skills].sort((a, b) => {
616
+ if (sortBy === "downloads") {
617
+ return (b.stats?.downloads || 0) - (a.stats?.downloads || 0);
618
+ }
619
+ if (sortBy === "stars") {
620
+ return (b.stats?.stars || 0) - (a.stats?.stars || 0);
621
+ }
622
+ if (sortBy === "updatedAt") {
623
+ return (b.updatedAt || 0) - (a.updatedAt || 0);
624
+ }
625
+ return 0;
626
+ });
627
+ }
628
+ function BrowseTab() {
629
+ const [sort, setSort] = useState("downloads");
630
+ const [searchQuery, setSearchQuery] = useState("");
631
+ const [selectedSkill, setSelectedSkill] = useState(null);
632
+ const apiSort = sort === "updatedAt" ? "newest" : sort === "stars" ? "trending" : "downloads";
633
+ const { skills: exploreSkills, loading: exploreLoading, error: exploreError } = useExploreSkills(apiSort, 50);
634
+ const { skills: searchResults, loading: searchLoading } = useSearchSkills(searchQuery);
635
+ const { skills: installedSkills } = useInstalledSkills();
636
+ const { install, installing } = useInstallSkill();
637
+ const installedSet = useMemo(
638
+ () => new Set(installedSkills.map((s) => s.name)),
639
+ [installedSkills]
640
+ );
641
+ const refreshInstalled = useInstalledSkills().refresh;
642
+ const handleInstall = async (slug) => {
643
+ try {
644
+ await install(slug);
645
+ void refreshInstalled();
646
+ } catch {
647
+ }
648
+ };
649
+ const isSearching = searchQuery.trim().length > 0;
650
+ const skills = useMemo(() => {
651
+ if (isSearching) {
652
+ return sortSkills(searchResults, sort);
653
+ }
654
+ return exploreSkills;
655
+ }, [isSearching, searchResults, exploreSkills, sort]);
656
+ const loading = isSearching ? searchLoading : exploreLoading;
657
+ if (selectedSkill) {
658
+ const slug = selectedSkill.slug || "";
659
+ const name = selectedSkill.displayName || slug;
660
+ const isInstalled = installedSet.has(slug) || installedSet.has(name);
661
+ return /* @__PURE__ */ jsx(
662
+ SkillDetailView,
663
+ {
664
+ skill: selectedSkill,
665
+ installed: isInstalled,
666
+ installing: installing === slug,
667
+ onInstall: () => handleInstall(slug),
668
+ onBack: () => setSelectedSkill(null)
669
+ }
670
+ );
671
+ }
672
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
673
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
674
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
675
+ /* @__PURE__ */ jsx(
676
+ HugeiconsIcon,
677
+ {
678
+ icon: Search01Icon,
679
+ size: 15,
680
+ strokeWidth: 1.5,
681
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-primary-300"
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsx(
685
+ "input",
686
+ {
687
+ type: "text",
688
+ value: searchQuery,
689
+ onChange: (e) => setSearchQuery(e.target.value),
690
+ placeholder: "Search skills…",
691
+ className: "w-full pl-9 pr-3 py-2 text-sm rounded-lg border border-primary-100 bg-surface placeholder:text-primary-300 focus:outline-none focus:border-primary-300 transition-colors duration-150"
692
+ }
693
+ )
694
+ ] }),
695
+ /* @__PURE__ */ jsxs(
696
+ "select",
697
+ {
698
+ value: sort,
699
+ onChange: (e) => setSort(e.target.value),
700
+ className: "shrink-0 text-xs text-primary-600 rounded-md border border-primary-100 bg-surface px-2 py-2 focus:outline-none focus:border-primary-300 transition-colors duration-150",
701
+ children: [
702
+ /* @__PURE__ */ jsx("option", { value: "downloads", children: "Most Downloads" }),
703
+ /* @__PURE__ */ jsx("option", { value: "stars", children: "Most Stars" }),
704
+ /* @__PURE__ */ jsx("option", { value: "updatedAt", children: "Recently Updated" })
705
+ ]
706
+ }
707
+ )
708
+ ] }),
709
+ loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading02Icon, size: 18, className: "animate-spin text-primary-300" }) }) : exploreError && !isSearching ? /* @__PURE__ */ jsx("div", { className: "text-sm text-red-500 py-8 text-center", children: exploreError }) : skills.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-12 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-400", children: isSearching ? "No skills found" : "No skills available" }) }) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: skills.map((skill) => {
710
+ const slug = skill.slug || skill.name || "";
711
+ const name = skill.displayName || slug;
712
+ return /* @__PURE__ */ jsx(
713
+ SkillCard,
714
+ {
715
+ skill,
716
+ installed: installedSet.has(slug) || installedSet.has(name),
717
+ installing: installing === slug,
718
+ onInstall: () => handleInstall(slug),
719
+ onClick: () => setSelectedSkill(skill)
720
+ },
721
+ slug
722
+ );
723
+ }) })
724
+ ] });
725
+ }
726
+ function SkillsPanel() {
727
+ const [tab, setTab] = useState("installed");
728
+ const { skills: mySkills, loading: mySkillsLoading } = useMySkills();
729
+ const showMySkills = !mySkillsLoading && mySkills.length > 0;
730
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
731
+ /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4 pb-3 border-b border-primary-100", children: [
732
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
733
+ /* @__PURE__ */ jsx(
734
+ Link,
735
+ {
736
+ to: "/chat/$sessionKey",
737
+ params: { sessionKey: "main" },
738
+ className: "p-1.5 -ml-1.5 rounded-md text-primary-500 hover:text-primary-700 hover:bg-primary-50 transition-colors duration-150",
739
+ "aria-label": "Back to Chat",
740
+ children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowLeft01Icon, size: 18, strokeWidth: 2 })
741
+ }
742
+ ),
743
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-primary-900", children: "Skills" })
744
+ ] }),
745
+ /* @__PURE__ */ jsx(Tabs, { value: tab, onValueChange: setTab, children: /* @__PURE__ */ jsxs(TabsList, { variant: "default", className: "gap-2 *:data-[slot=tab-indicator]:duration-0", children: [
746
+ /* @__PURE__ */ jsx(TabsTab, { value: "installed", children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: "Installed" }) }),
747
+ showMySkills && /* @__PURE__ */ jsx(TabsTab, { value: "my-skills", children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: "My Skills" }) }),
748
+ /* @__PURE__ */ jsx(TabsTab, { value: "recommended", children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: "Recommended" }) }),
749
+ /* @__PURE__ */ jsx(TabsTab, { value: "browse", children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: "Browse" }) })
750
+ ] }) })
751
+ ] }),
752
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-4 pb-4 pt-4", children: [
753
+ tab === "installed" && /* @__PURE__ */ jsx(InstalledTab, {}),
754
+ tab === "my-skills" && /* @__PURE__ */ jsx(MySkillsTab, {}),
755
+ tab === "recommended" && /* @__PURE__ */ jsx(RecommendedTab, {}),
756
+ tab === "browse" && /* @__PURE__ */ jsx(BrowseTab, {})
757
+ ] })
758
+ ] });
759
+ }
760
+ export {
761
+ SkillsPanel
762
+ };