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
@@ -3,13 +3,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import WebSocket from "ws";
6
- import { readFileSync } from "node:fs";
6
+ import { readFileSync, existsSync } from "node:fs";
7
7
  import path, { join, resolve, relative, extname } from "node:path";
8
8
  import os, { homedir } from "node:os";
9
9
  import { json } from "@tanstack/router-core/ssr/client";
10
+ import { execSync } from "node:child_process";
10
11
  import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
11
12
  import { posix } from "path";
12
- const appCss = "/assets/styles-BaTVzdPa.css";
13
+ const appCss = "/assets/styles-CHP4l6vZ.css";
13
14
  const swRegisterScript = `
14
15
  (() => {
15
16
  // Skip PWA service worker inside Capacitor native shell — they conflict
@@ -46,15 +47,21 @@ const themeScript = `
46
47
  if (stored) {
47
48
  const parsed = JSON.parse(stored)
48
49
  const storedTheme = parsed?.state?.settings?.theme
49
- if (storedTheme === 'light' || storedTheme === 'dark' || storedTheme === 'system' || storedTheme === 'chameleon') {
50
+ if (storedTheme === 'light' || storedTheme === 'dark' || storedTheme === 'system' || storedTheme === 'chameleon' || storedTheme === 'frost-light' || storedTheme === 'frost-dark') {
50
51
  theme = storedTheme
51
52
  }
52
53
  }
53
54
  const root = document.documentElement
54
55
  const media = window.matchMedia('(prefers-color-scheme: dark)')
55
56
  const apply = () => {
56
- root.classList.remove('light', 'dark', 'system', 'chameleon')
57
+ root.classList.remove('light', 'dark', 'system', 'chameleon', 'frost', 'frost-light', 'frost-dark')
57
58
  root.classList.add(theme)
59
+ if (theme === 'frost-light' || theme === 'frost-dark') {
60
+ root.classList.add('frost')
61
+ }
62
+ if (theme === 'frost-dark') {
63
+ root.classList.add('dark')
64
+ }
58
65
  if (theme === 'system' && media.matches) {
59
66
  root.classList.add('dark')
60
67
  }
@@ -66,6 +73,13 @@ const themeScript = `
66
73
  } catch {}
67
74
  })()
68
75
  `;
76
+ const tauriDetectScript = `
77
+ (() => {
78
+ if (window.__TAURI_INTERNALS__ || window.__TAURI__) {
79
+ document.documentElement.classList.add('tauri')
80
+ }
81
+ })()
82
+ `;
69
83
  const textSizeScript = `
70
84
  (() => {
71
85
  try {
@@ -76,13 +90,119 @@ const textSizeScript = `
76
90
  } catch {}
77
91
  })()
78
92
  `;
93
+ const fontFamilyScript = `
94
+ (() => {
95
+ try {
96
+ const stored = localStorage.getItem('opencami-font-family')
97
+ const map = {
98
+ 'system': '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
99
+ 'inter': '"Inter", sans-serif',
100
+ 'ibm-plex-sans': '"IBM Plex Sans", sans-serif',
101
+ 'jetbrains-mono': '"JetBrains Mono", monospace',
102
+ 'merriweather': '"Merriweather", serif',
103
+ 'roboto': '"Roboto", sans-serif',
104
+ }
105
+ const value = map[stored] || map.system
106
+ document.documentElement.style.setProperty('--opencami-font-family', value)
107
+ } catch {}
108
+ })()
109
+ `;
110
+ const densityScript = `
111
+ (() => {
112
+ try {
113
+ const stored = localStorage.getItem('opencami-density')
114
+ const value = stored === 'compact' || stored === 'spacious' || stored === 'comfortable'
115
+ ? stored
116
+ : 'comfortable'
117
+ const root = document.documentElement
118
+ root.style.setProperty('--opencami-density', value)
119
+
120
+ if (value === 'compact') {
121
+ root.style.setProperty('--opencami-msg-padding-y', '0.25rem')
122
+ root.style.setProperty('--opencami-msg-gap', '0.25rem')
123
+ root.style.setProperty('--opencami-user-bubble-py', '0.4rem')
124
+ } else if (value === 'spacious') {
125
+ root.style.setProperty('--opencami-msg-padding-y', '1.25rem')
126
+ root.style.setProperty('--opencami-msg-gap', '1rem')
127
+ root.style.setProperty('--opencami-user-bubble-py', '1rem')
128
+ } else {
129
+ root.style.setProperty('--opencami-msg-padding-y', '0.75rem')
130
+ root.style.setProperty('--opencami-msg-gap', '0.5rem')
131
+ root.style.setProperty('--opencami-user-bubble-py', '0.625rem')
132
+ }
133
+ } catch {}
134
+ })()
135
+ `;
136
+ const accentColorScript = `
137
+ (() => {
138
+ try {
139
+ const stored = localStorage.getItem('opencami-accent-color')
140
+ const map = {
141
+ green: { accent: '#22c55e', hover: '#16a34a', light: 'rgba(34, 197, 94, 0.10)' },
142
+ blue: { accent: '#3b82f6', hover: '#2563eb', light: 'rgba(59, 130, 246, 0.10)' },
143
+ purple: { accent: '#8b5cf6', hover: '#7c3aed', light: 'rgba(139, 92, 246, 0.10)' },
144
+ orange: { accent: '#f97316', hover: '#ea580c', light: 'rgba(249, 115, 22, 0.10)' },
145
+ pink: { accent: '#ec4899', hover: '#db2777', light: 'rgba(236, 72, 153, 0.10)' },
146
+ red: { accent: '#ef4444', hover: '#dc2626', light: 'rgba(239, 68, 68, 0.10)' },
147
+ cyan: { accent: '#06b6d4', hover: '#0891b2', light: 'rgba(6, 182, 212, 0.10)' },
148
+ yellow: { accent: '#eab308', hover: '#ca8a04', light: 'rgba(234, 179, 8, 0.10)' },
149
+ }
150
+ const selected = map[stored] || map.green
151
+ const root = document.documentElement
152
+ root.style.setProperty('--opencami-accent', selected.accent)
153
+ root.style.setProperty('--opencami-accent-hover', selected.hover)
154
+ root.style.setProperty('--opencami-accent-light', selected.light)
155
+ } catch {}
156
+ })()
157
+ `;
158
+ const chatWidthScript = `
159
+ (() => {
160
+ try {
161
+ const stored = localStorage.getItem('opencami-chat-width')
162
+ const map = {
163
+ narrow: '640px',
164
+ medium: '800px',
165
+ wide: '1000px',
166
+ full: '100%',
167
+ }
168
+ const value = map[stored] || map.wide
169
+ document.documentElement.style.setProperty('--opencami-chat-width', value)
170
+ } catch {}
171
+ })()
172
+ `;
173
+ const sidebarWidthScript = `
174
+ (() => {
175
+ try {
176
+ const stored = localStorage.getItem('opencami-sidebar-width')
177
+ const map = {
178
+ compact: '200px',
179
+ normal: '260px',
180
+ wide: '320px',
181
+ xl: '400px',
182
+ }
183
+ const value = map[stored] || map.normal
184
+ document.documentElement.style.setProperty('--opencami-sidebar-width', value)
185
+ } catch {}
186
+ })()
187
+ `;
188
+ const bubbleStyleScript = `
189
+ (() => {
190
+ try {
191
+ const stored = localStorage.getItem('opencami-bubble-style')
192
+ const value = stored === 'bubbles' || stored === 'minimal' || stored === 'default'
193
+ ? stored
194
+ : 'default'
195
+ document.documentElement.setAttribute('data-opencami-bubble-style', value)
196
+ } catch {}
197
+ })()
198
+ `;
79
199
  function NotFoundRedirect() {
80
200
  if (typeof window !== "undefined") {
81
201
  window.location.href = "/chat/main";
82
202
  }
83
203
  return null;
84
204
  }
85
- const Route$u = createRootRoute({
205
+ const Route$w = createRootRoute({
86
206
  notFoundComponent: NotFoundRedirect,
87
207
  head: () => ({
88
208
  meta: [
@@ -172,7 +292,14 @@ function RootLayout() {
172
292
  function RootDocument({ children }) {
173
293
  return /* @__PURE__ */ jsxs("html", { lang: "en", suppressHydrationWarning: true, children: [
174
294
  /* @__PURE__ */ jsxs("head", { children: [
295
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: tauriDetectScript } }),
175
296
  /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: textSizeScript } }),
297
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: fontFamilyScript } }),
298
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: densityScript } }),
299
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: accentColorScript } }),
300
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: chatWidthScript } }),
301
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: sidebarWidthScript } }),
302
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: bubbleStyleScript } }),
176
303
  /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: themeScript } }),
177
304
  /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: swRegisterScript } }),
178
305
  /* @__PURE__ */ jsx(HeadContent, {})
@@ -183,8 +310,12 @@ function RootDocument({ children }) {
183
310
  ] })
184
311
  ] });
185
312
  }
313
+ const $$splitComponentImporter$7 = () => import("./skills-Cy8xclXY.js");
314
+ const Route$v = createFileRoute("/skills")({
315
+ component: lazyRouteComponent($$splitComponentImporter$7, "component")
316
+ });
186
317
  const $$splitComponentImporter$6 = () => import("./new-Dzk5YxE9.js");
187
- const Route$t = createFileRoute("/new")({
318
+ const Route$u = createFileRoute("/new")({
188
319
  beforeLoad: function redirectToNewChat() {
189
320
  throw redirect({
190
321
  to: "/chat/$sessionKey",
@@ -196,28 +327,28 @@ const Route$t = createFileRoute("/new")({
196
327
  },
197
328
  component: lazyRouteComponent($$splitComponentImporter$6, "component")
198
329
  });
199
- const $$splitComponentImporter$5 = () => import("./files-D2GIrPF4.js");
200
- const Route$s = createFileRoute("/files")({
330
+ const $$splitComponentImporter$5 = () => import("./files-BIEcSPGp.js");
331
+ const Route$t = createFileRoute("/files")({
201
332
  component: lazyRouteComponent($$splitComponentImporter$5, "component")
202
333
  });
203
- const $$splitComponentImporter$4 = () => import("./connect-B8jpGQGK.js");
204
- const Route$r = createFileRoute("/connect")({
334
+ const $$splitComponentImporter$4 = () => import("./connect-d3AqjAqe.js");
335
+ const Route$s = createFileRoute("/connect")({
205
336
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
206
337
  });
207
- const $$splitComponentImporter$3 = () => import("./bots-6ryCIgKh.js");
208
- const Route$q = createFileRoute("/bots")({
338
+ const $$splitComponentImporter$3 = () => import("./bots-Byt6jv0a.js");
339
+ const Route$r = createFileRoute("/bots")({
209
340
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
210
341
  });
211
- const $$splitComponentImporter$2 = () => import("./agents-Dz_i76VW.js");
212
- const Route$p = createFileRoute("/agents")({
342
+ const $$splitComponentImporter$2 = () => import("./agents-CmQ4vvXm.js");
343
+ const Route$q = createFileRoute("/agents")({
213
344
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
214
345
  });
215
- const $$splitComponentImporter$1 = () => import("./index-B2JHn34C.js");
216
- const Route$o = createFileRoute("/")({
346
+ const $$splitComponentImporter$1 = () => import("./index-CRfLKh30.js");
347
+ const Route$p = createFileRoute("/")({
217
348
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
218
349
  });
219
- const $$splitComponentImporter = () => import("./_sessionKey-BhFH4uWY.js").then((n) => n.$);
220
- const Route$n = createFileRoute("/chat/$sessionKey")({
350
+ const $$splitComponentImporter = () => import("./_sessionKey-tRze5NLR.js").then((n) => n.$);
351
+ const Route$o = createFileRoute("/chat/$sessionKey")({
221
352
  component: lazyRouteComponent($$splitComponentImporter, "component")
222
353
  });
223
354
  function getGatewayConfig() {
@@ -584,7 +715,7 @@ async function ttsEdge(text, voice) {
584
715
  }
585
716
  });
586
717
  }
587
- const Route$m = createFileRoute("/api/tts")({
718
+ const Route$n = createFileRoute("/api/tts")({
588
719
  server: {
589
720
  handlers: {
590
721
  POST: async ({ request }) => {
@@ -739,7 +870,7 @@ async function sttOpenAI(audioBlob, apiKey, language) {
739
870
  const data = await res.json();
740
871
  return { text: data.text || "" };
741
872
  }
742
- const Route$l = createFileRoute("/api/stt")({
873
+ const Route$m = createFileRoute("/api/stt")({
743
874
  server: {
744
875
  handlers: {
745
876
  POST: async ({ request }) => {
@@ -841,7 +972,7 @@ const Route$l = createFileRoute("/api/stt")({
841
972
  }
842
973
  }
843
974
  });
844
- const Route$k = createFileRoute("/api/stream")({
975
+ const Route$l = createFileRoute("/api/stream")({
845
976
  server: {
846
977
  handlers: {
847
978
  GET: async ({ request }) => {
@@ -946,6 +1077,155 @@ data: ${JSON.stringify(data)}
946
1077
  }
947
1078
  }
948
1079
  });
1080
+ const MY_SKILLS_CONFIG = resolve(process.cwd(), ".clawhub-my-skills.json");
1081
+ const RECOMMENDED_SKILLS = [
1082
+ "web-search-plus",
1083
+ "elevenlabs-voices",
1084
+ "sports-ticker",
1085
+ "personas",
1086
+ "agent-chronicle",
1087
+ "summarize",
1088
+ "weather",
1089
+ "muninn",
1090
+ "clawd-docs-v2",
1091
+ "therapy-mode"
1092
+ ];
1093
+ function runCmd(cmd) {
1094
+ try {
1095
+ return execSync(cmd, { encoding: "utf-8", timeout: 3e4 }).trim();
1096
+ } catch (err) {
1097
+ const msg = err instanceof Error ? err.message : String(err);
1098
+ throw new Error(`Command failed: ${msg}`);
1099
+ }
1100
+ }
1101
+ function loadMySkillsSlugs() {
1102
+ try {
1103
+ if (!existsSync(MY_SKILLS_CONFIG)) return [];
1104
+ const data = JSON.parse(readFileSync(MY_SKILLS_CONFIG, "utf-8"));
1105
+ return Array.isArray(data.slugs) ? data.slugs : [];
1106
+ } catch {
1107
+ return [];
1108
+ }
1109
+ }
1110
+ function parseInstalledSkills(output) {
1111
+ if (!output) return [];
1112
+ return output.split("\n").filter((line) => line.trim()).map((line) => {
1113
+ const match = line.match(/^(.+?)\s+v?([\d.]+.*)$/);
1114
+ if (match) return { name: match[1].trim(), version: match[2].trim() };
1115
+ return { name: line.trim(), version: "" };
1116
+ });
1117
+ }
1118
+ function parseSearchResults(output) {
1119
+ if (!output) return [];
1120
+ return output.split("\n").filter((line) => line.trim()).map((line) => {
1121
+ const match = line.match(/^(.+?)\s+v([\d.]+)\s+(.+?)(?:\s+\(([\d.]+)\))?$/);
1122
+ if (match) {
1123
+ return {
1124
+ slug: match[1].trim(),
1125
+ displayName: match[1].trim(),
1126
+ version: match[2].trim(),
1127
+ summary: match[3].trim(),
1128
+ score: match[4] ? parseFloat(match[4]) : void 0
1129
+ };
1130
+ }
1131
+ return { slug: line.trim(), displayName: line.trim(), version: "", summary: "" };
1132
+ });
1133
+ }
1134
+ const Route$k = createFileRoute("/api/skills")({
1135
+ server: {
1136
+ handlers: {
1137
+ GET: async ({ request }) => {
1138
+ try {
1139
+ const url = new URL(request.url);
1140
+ const action = url.searchParams.get("action") || "installed";
1141
+ if (action === "installed") {
1142
+ const output = runCmd("clawhub list");
1143
+ return json({ ok: true, skills: parseInstalledSkills(output) });
1144
+ }
1145
+ if (action === "explore") {
1146
+ const sort = url.searchParams.get("sort") || "trending";
1147
+ const limit = url.searchParams.get("limit") || "25";
1148
+ const safeSort = sort.replace(/[^a-z-]/gi, "");
1149
+ const safeLimit = String(parseInt(limit, 10) || 25);
1150
+ const raw = runCmd(`clawhub explore --json --limit ${safeLimit} --sort ${safeSort}`);
1151
+ const output = raw.substring(raw.indexOf("{"));
1152
+ try {
1153
+ const data = JSON.parse(output);
1154
+ return json({ ok: true, skills: Array.isArray(data) ? data : data.items || data.skills || data.results || [] });
1155
+ } catch {
1156
+ return json({ ok: true, skills: [] });
1157
+ }
1158
+ }
1159
+ if (action === "search") {
1160
+ const q = url.searchParams.get("q") || "";
1161
+ const limit = url.searchParams.get("limit") || "10";
1162
+ if (!q.trim()) return json({ ok: true, skills: [] });
1163
+ const safeLimit = String(parseInt(limit, 10) || 10);
1164
+ const safeQ = q.replace(/"/g, '\\"');
1165
+ const raw = runCmd(`clawhub search "${safeQ}" --limit ${safeLimit}`);
1166
+ const output = raw.replace(/^- Searching\n?/, "");
1167
+ return json({ ok: true, skills: parseSearchResults(output) });
1168
+ }
1169
+ if (action === "my-skills" || action === "recommended") {
1170
+ const targetSlugs = action === "my-skills" ? loadMySkillsSlugs() : RECOMMENDED_SKILLS;
1171
+ if (targetSlugs.length === 0) {
1172
+ return json({ ok: true, skills: [] });
1173
+ }
1174
+ const slugSet = new Set(targetSlugs);
1175
+ const found = /* @__PURE__ */ new Map();
1176
+ for (const sort of ["downloads", "installs", "newest", "trending"]) {
1177
+ if (found.size >= slugSet.size) break;
1178
+ try {
1179
+ const raw = runCmd(`clawhub explore --json --limit 200 --sort ${sort}`);
1180
+ const output = raw.substring(raw.indexOf("{"));
1181
+ const data = JSON.parse(output);
1182
+ const items = Array.isArray(data) ? data : data.items || [];
1183
+ for (const item of items) {
1184
+ if (item.slug && slugSet.has(item.slug) && !found.has(item.slug)) {
1185
+ found.set(item.slug, item);
1186
+ }
1187
+ }
1188
+ } catch {
1189
+ }
1190
+ }
1191
+ const skills = Array.from(found.values());
1192
+ skills.sort((a, b) => (b.stats?.downloads || 0) - (a.stats?.downloads || 0));
1193
+ return json({ ok: true, skills });
1194
+ }
1195
+ return json({ ok: false, error: "Unknown action" }, { status: 400 });
1196
+ } catch (err) {
1197
+ return json(
1198
+ { ok: false, error: err instanceof Error ? err.message : String(err) },
1199
+ { status: 500 }
1200
+ );
1201
+ }
1202
+ },
1203
+ POST: async ({ request }) => {
1204
+ try {
1205
+ const body = await request.json().catch(() => ({}));
1206
+ const action = typeof body.action === "string" ? body.action : "";
1207
+ const slug = typeof body.slug === "string" ? body.slug.trim() : "";
1208
+ if (!slug) return json({ ok: false, error: "slug is required" }, { status: 400 });
1209
+ const safeSlug = slug.replace(/[^a-zA-Z0-9/_-]/g, "");
1210
+ if (action === "install") {
1211
+ const output = runCmd(`clawhub install ${safeSlug} --no-input`);
1212
+ return json({ ok: true, output });
1213
+ }
1214
+ if (action === "update") {
1215
+ const output = runCmd(`clawhub update ${safeSlug} --no-input`);
1216
+ return json({ ok: true, output });
1217
+ }
1218
+ return json({ ok: false, error: "Unknown action" }, { status: 400 });
1219
+ } catch (err) {
1220
+ return json(
1221
+ { ok: false, error: err instanceof Error ? err.message : String(err) },
1222
+ { status: 500 }
1223
+ );
1224
+ }
1225
+ }
1226
+ }
1227
+ }
1228
+ });
949
1229
  function deriveFriendlyIdFromKey(key) {
950
1230
  if (typeof key !== "string" || key.trim().length === 0) return "main";
951
1231
  const parts = key.split(":");
@@ -2993,155 +3273,165 @@ const Route = createFileRoute("/api/files/delete")({
2993
3273
  }
2994
3274
  }
2995
3275
  });
2996
- const NewRoute = Route$t.update({
3276
+ const SkillsRoute = Route$v.update({
3277
+ id: "/skills",
3278
+ path: "/skills",
3279
+ getParentRoute: () => Route$w
3280
+ });
3281
+ const NewRoute = Route$u.update({
2997
3282
  id: "/new",
2998
3283
  path: "/new",
2999
- getParentRoute: () => Route$u
3284
+ getParentRoute: () => Route$w
3000
3285
  });
3001
- const FilesRoute = Route$s.update({
3286
+ const FilesRoute = Route$t.update({
3002
3287
  id: "/files",
3003
3288
  path: "/files",
3004
- getParentRoute: () => Route$u
3289
+ getParentRoute: () => Route$w
3005
3290
  });
3006
- const ConnectRoute = Route$r.update({
3291
+ const ConnectRoute = Route$s.update({
3007
3292
  id: "/connect",
3008
3293
  path: "/connect",
3009
- getParentRoute: () => Route$u
3294
+ getParentRoute: () => Route$w
3010
3295
  });
3011
- const BotsRoute = Route$q.update({
3296
+ const BotsRoute = Route$r.update({
3012
3297
  id: "/bots",
3013
3298
  path: "/bots",
3014
- getParentRoute: () => Route$u
3299
+ getParentRoute: () => Route$w
3015
3300
  });
3016
- const AgentsRoute = Route$p.update({
3301
+ const AgentsRoute = Route$q.update({
3017
3302
  id: "/agents",
3018
3303
  path: "/agents",
3019
- getParentRoute: () => Route$u
3304
+ getParentRoute: () => Route$w
3020
3305
  });
3021
- const IndexRoute = Route$o.update({
3306
+ const IndexRoute = Route$p.update({
3022
3307
  id: "/",
3023
3308
  path: "/",
3024
- getParentRoute: () => Route$u
3309
+ getParentRoute: () => Route$w
3025
3310
  });
3026
- const ChatSessionKeyRoute = Route$n.update({
3311
+ const ChatSessionKeyRoute = Route$o.update({
3027
3312
  id: "/chat/$sessionKey",
3028
3313
  path: "/chat/$sessionKey",
3029
- getParentRoute: () => Route$u
3314
+ getParentRoute: () => Route$w
3030
3315
  });
3031
- const ApiTtsRoute = Route$m.update({
3316
+ const ApiTtsRoute = Route$n.update({
3032
3317
  id: "/api/tts",
3033
3318
  path: "/api/tts",
3034
- getParentRoute: () => Route$u
3319
+ getParentRoute: () => Route$w
3035
3320
  });
3036
- const ApiSttRoute = Route$l.update({
3321
+ const ApiSttRoute = Route$m.update({
3037
3322
  id: "/api/stt",
3038
3323
  path: "/api/stt",
3039
- getParentRoute: () => Route$u
3324
+ getParentRoute: () => Route$w
3040
3325
  });
3041
- const ApiStreamRoute = Route$k.update({
3326
+ const ApiStreamRoute = Route$l.update({
3042
3327
  id: "/api/stream",
3043
3328
  path: "/api/stream",
3044
- getParentRoute: () => Route$u
3329
+ getParentRoute: () => Route$w
3330
+ });
3331
+ const ApiSkillsRoute = Route$k.update({
3332
+ id: "/api/skills",
3333
+ path: "/api/skills",
3334
+ getParentRoute: () => Route$w
3045
3335
  });
3046
3336
  const ApiSessionsRoute = Route$j.update({
3047
3337
  id: "/api/sessions",
3048
3338
  path: "/api/sessions",
3049
- getParentRoute: () => Route$u
3339
+ getParentRoute: () => Route$w
3050
3340
  });
3051
3341
  const ApiSendRoute = Route$i.update({
3052
3342
  id: "/api/send",
3053
3343
  path: "/api/send",
3054
- getParentRoute: () => Route$u
3344
+ getParentRoute: () => Route$w
3055
3345
  });
3056
3346
  const ApiPingRoute = Route$h.update({
3057
3347
  id: "/api/ping",
3058
3348
  path: "/api/ping",
3059
- getParentRoute: () => Route$u
3349
+ getParentRoute: () => Route$w
3060
3350
  });
3061
3351
  const ApiPersonasRoute = Route$g.update({
3062
3352
  id: "/api/personas",
3063
3353
  path: "/api/personas",
3064
- getParentRoute: () => Route$u
3354
+ getParentRoute: () => Route$w
3065
3355
  });
3066
3356
  const ApiPathsRoute = Route$f.update({
3067
3357
  id: "/api/paths",
3068
3358
  path: "/api/paths",
3069
- getParentRoute: () => Route$u
3359
+ getParentRoute: () => Route$w
3070
3360
  });
3071
3361
  const ApiModelsRoute = Route$e.update({
3072
3362
  id: "/api/models",
3073
3363
  path: "/api/models",
3074
- getParentRoute: () => Route$u
3364
+ getParentRoute: () => Route$w
3075
3365
  });
3076
3366
  const ApiLlmFeaturesRoute = Route$d.update({
3077
3367
  id: "/api/llm-features",
3078
3368
  path: "/api/llm-features",
3079
- getParentRoute: () => Route$u
3369
+ getParentRoute: () => Route$w
3080
3370
  });
3081
3371
  const ApiHistoryRoute = Route$c.update({
3082
3372
  id: "/api/history",
3083
3373
  path: "/api/history",
3084
- getParentRoute: () => Route$u
3374
+ getParentRoute: () => Route$w
3085
3375
  });
3086
3376
  const ApiFollowUpsRoute = Route$b.update({
3087
3377
  id: "/api/follow-ups",
3088
3378
  path: "/api/follow-ups",
3089
- getParentRoute: () => Route$u
3379
+ getParentRoute: () => Route$w
3090
3380
  });
3091
3381
  const ApiCronRoute = Route$a.update({
3092
3382
  id: "/api/cron",
3093
3383
  path: "/api/cron",
3094
- getParentRoute: () => Route$u
3384
+ getParentRoute: () => Route$w
3095
3385
  });
3096
3386
  const ApiAgentsRoute = Route$9.update({
3097
3387
  id: "/api/agents",
3098
3388
  path: "/api/agents",
3099
- getParentRoute: () => Route$u
3389
+ getParentRoute: () => Route$w
3100
3390
  });
3101
3391
  const ApiFilesUploadRoute = Route$8.update({
3102
3392
  id: "/api/files/upload",
3103
3393
  path: "/api/files/upload",
3104
- getParentRoute: () => Route$u
3394
+ getParentRoute: () => Route$w
3105
3395
  });
3106
3396
  const ApiFilesSaveRoute = Route$7.update({
3107
3397
  id: "/api/files/save",
3108
3398
  path: "/api/files/save",
3109
- getParentRoute: () => Route$u
3399
+ getParentRoute: () => Route$w
3110
3400
  });
3111
3401
  const ApiFilesRenameRoute = Route$6.update({
3112
3402
  id: "/api/files/rename",
3113
3403
  path: "/api/files/rename",
3114
- getParentRoute: () => Route$u
3404
+ getParentRoute: () => Route$w
3115
3405
  });
3116
3406
  const ApiFilesReadRoute = Route$5.update({
3117
3407
  id: "/api/files/read",
3118
3408
  path: "/api/files/read",
3119
- getParentRoute: () => Route$u
3409
+ getParentRoute: () => Route$w
3120
3410
  });
3121
3411
  const ApiFilesMkdirRoute = Route$4.update({
3122
3412
  id: "/api/files/mkdir",
3123
3413
  path: "/api/files/mkdir",
3124
- getParentRoute: () => Route$u
3414
+ getParentRoute: () => Route$w
3125
3415
  });
3126
3416
  const ApiFilesListRoute = Route$3.update({
3127
3417
  id: "/api/files/list",
3128
3418
  path: "/api/files/list",
3129
- getParentRoute: () => Route$u
3419
+ getParentRoute: () => Route$w
3130
3420
  });
3131
3421
  const ApiFilesInfoRoute = Route$2.update({
3132
3422
  id: "/api/files/info",
3133
3423
  path: "/api/files/info",
3134
- getParentRoute: () => Route$u
3424
+ getParentRoute: () => Route$w
3135
3425
  });
3136
3426
  const ApiFilesDownloadRoute = Route$1.update({
3137
3427
  id: "/api/files/download",
3138
3428
  path: "/api/files/download",
3139
- getParentRoute: () => Route$u
3429
+ getParentRoute: () => Route$w
3140
3430
  });
3141
3431
  const ApiFilesDeleteRoute = Route.update({
3142
3432
  id: "/api/files/delete",
3143
3433
  path: "/api/files/delete",
3144
- getParentRoute: () => Route$u
3434
+ getParentRoute: () => Route$w
3145
3435
  });
3146
3436
  const rootRouteChildren = {
3147
3437
  IndexRoute,
@@ -3150,6 +3440,7 @@ const rootRouteChildren = {
3150
3440
  ConnectRoute,
3151
3441
  FilesRoute,
3152
3442
  NewRoute,
3443
+ SkillsRoute,
3153
3444
  ApiAgentsRoute,
3154
3445
  ApiCronRoute,
3155
3446
  ApiFollowUpsRoute,
@@ -3161,6 +3452,7 @@ const rootRouteChildren = {
3161
3452
  ApiPingRoute,
3162
3453
  ApiSendRoute,
3163
3454
  ApiSessionsRoute,
3455
+ ApiSkillsRoute,
3164
3456
  ApiStreamRoute,
3165
3457
  ApiSttRoute,
3166
3458
  ApiTtsRoute,
@@ -3175,7 +3467,7 @@ const rootRouteChildren = {
3175
3467
  ApiFilesSaveRoute,
3176
3468
  ApiFilesUploadRoute
3177
3469
  };
3178
- const routeTree = Route$u._addFileChildren(rootRouteChildren)._addFileTypes();
3470
+ const routeTree = Route$w._addFileChildren(rootRouteChildren)._addFileTypes();
3179
3471
  const getRouter = () => {
3180
3472
  const router2 = createRouter({
3181
3473
  routeTree,
@@ -3190,7 +3482,7 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
3190
3482
  getRouter
3191
3483
  }, Symbol.toStringTag, { value: "Module" }));
3192
3484
  export {
3193
- Route$o as R,
3194
- Route$n as a,
3485
+ Route$p as R,
3486
+ Route$o as a,
3195
3487
  router as r
3196
3488
  };