@samanhappy/mcphub 1.1.14 → 1.2.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 (215) hide show
  1. package/README.fr.md +2 -1
  2. package/README.md +2 -1
  3. package/README.zh.md +2 -1
  4. package/dist/betterAuth.js +3 -3
  5. package/dist/betterAuth.js.map +1 -1
  6. package/dist/cli/commands/keys.js +22 -3
  7. package/dist/cli/commands/keys.js.map +1 -1
  8. package/dist/clients/mcpSdkClient.js +2667 -1448
  9. package/dist/clients/mcpSdkClient.js.map +4 -4
  10. package/dist/clients/openapi.js +3 -2
  11. package/dist/clients/openapi.js.map +1 -1
  12. package/dist/controllers/bearerKeyController.js +49 -3
  13. package/dist/controllers/bearerKeyController.js.map +1 -1
  14. package/dist/controllers/credentialBindingController.js +1 -1
  15. package/dist/controllers/credentialBindingController.js.map +1 -1
  16. package/dist/controllers/groupController.js +12 -5
  17. package/dist/controllers/groupController.js.map +1 -1
  18. package/dist/controllers/healthController.js +23 -1
  19. package/dist/controllers/healthController.js.map +1 -1
  20. package/dist/controllers/metricsController.js +9 -1
  21. package/dist/controllers/metricsController.js.map +1 -1
  22. package/dist/controllers/oauthDynamicRegistrationController.js +5 -5
  23. package/dist/controllers/oauthDynamicRegistrationController.js.map +1 -1
  24. package/dist/controllers/oauthRelayController.js +65 -0
  25. package/dist/controllers/oauthRelayController.js.map +1 -0
  26. package/dist/controllers/personalOAuthController.js +10 -1
  27. package/dist/controllers/personalOAuthController.js.map +1 -1
  28. package/dist/controllers/serverController.js +23 -14
  29. package/dist/controllers/serverController.js.map +1 -1
  30. package/dist/controllers/userController.js +11 -1
  31. package/dist/controllers/userController.js.map +1 -1
  32. package/dist/dao/BearerKeyDao.js +10 -5
  33. package/dist/dao/BearerKeyDao.js.map +1 -1
  34. package/dist/dao/BearerKeyDaoDbImpl.js +16 -5
  35. package/dist/dao/BearerKeyDaoDbImpl.js.map +1 -1
  36. package/dist/dao/GroupDaoDbImpl.js +11 -0
  37. package/dist/dao/GroupDaoDbImpl.js.map +1 -1
  38. package/dist/dao/ServerDao.js +5 -1
  39. package/dist/dao/ServerDao.js.map +1 -1
  40. package/dist/dao/ServerDaoDbImpl.js +3 -0
  41. package/dist/dao/ServerDaoDbImpl.js.map +1 -1
  42. package/dist/dao/UserDaoDbImpl.js +4 -0
  43. package/dist/dao/UserDaoDbImpl.js.map +1 -1
  44. package/dist/db/connection.js +6 -2
  45. package/dist/db/connection.js.map +1 -1
  46. package/dist/db/entities/BearerKey.js +8 -0
  47. package/dist/db/entities/BearerKey.js.map +1 -1
  48. package/dist/db/entities/Group.js +4 -0
  49. package/dist/db/entities/Group.js.map +1 -1
  50. package/dist/db/entities/OAuthState.js +43 -0
  51. package/dist/db/entities/OAuthState.js.map +1 -0
  52. package/dist/db/entities/Server.js +4 -0
  53. package/dist/db/entities/Server.js.map +1 -1
  54. package/dist/db/entities/User.js +4 -0
  55. package/dist/db/entities/User.js.map +1 -1
  56. package/dist/db/entities/index.js +3 -1
  57. package/dist/db/entities/index.js.map +1 -1
  58. package/dist/db/replicaSchema.js +40 -0
  59. package/dist/db/replicaSchema.js.map +1 -0
  60. package/dist/db/repositories/ServerRepository.js +1 -0
  61. package/dist/db/repositories/ServerRepository.js.map +1 -1
  62. package/dist/db/setup.js +25 -0
  63. package/dist/db/setup.js.map +1 -0
  64. package/dist/db/transactionContext.js +21 -0
  65. package/dist/db/transactionContext.js.map +1 -0
  66. package/dist/index.js +2 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/middlewares/auth.js +7 -1
  69. package/dist/middlewares/auth.js.map +1 -1
  70. package/dist/middlewares/replicaConfig.js +134 -0
  71. package/dist/middlewares/replicaConfig.js.map +1 -0
  72. package/dist/middlewares/userContext.js +1 -1
  73. package/dist/middlewares/userContext.js.map +1 -1
  74. package/dist/models/OAuth.js +57 -7
  75. package/dist/models/OAuth.js.map +1 -1
  76. package/dist/routes/index.js +16 -1
  77. package/dist/routes/index.js.map +1 -1
  78. package/dist/server.js +13 -4
  79. package/dist/server.js.map +1 -1
  80. package/dist/services/authorizationService.js.map +1 -1
  81. package/dist/services/capabilitySnapshotService.js +2 -1
  82. package/dist/services/capabilitySnapshotService.js.map +1 -1
  83. package/dist/services/credentialBindingService.js +13 -1
  84. package/dist/services/credentialBindingService.js.map +1 -1
  85. package/dist/services/dataService.js +4 -2
  86. package/dist/services/dataService.js.map +1 -1
  87. package/dist/services/groupService.js +15 -7
  88. package/dist/services/groupService.js.map +1 -1
  89. package/dist/services/mcpEventBus.js +6 -0
  90. package/dist/services/mcpEventBus.js.map +1 -0
  91. package/dist/services/mcpOAuthProvider.js +91 -5
  92. package/dist/services/mcpOAuthProvider.js.map +1 -1
  93. package/dist/services/mcpService.js +136 -9
  94. package/dist/services/mcpService.js.map +1 -1
  95. package/dist/services/metricsService.js +4 -0
  96. package/dist/services/metricsService.js.map +1 -1
  97. package/dist/services/oauthClientRegistration.js +21 -1
  98. package/dist/services/oauthClientRegistration.js.map +1 -1
  99. package/dist/services/oauthServerService.js +5 -6
  100. package/dist/services/oauthServerService.js.map +1 -1
  101. package/dist/services/oauthSettingsStore.js +21 -0
  102. package/dist/services/oauthSettingsStore.js.map +1 -1
  103. package/dist/services/personalOAuthService.js +42 -2
  104. package/dist/services/personalOAuthService.js.map +1 -1
  105. package/dist/services/replicaLock.js +30 -0
  106. package/dist/services/replicaLock.js.map +1 -0
  107. package/dist/services/replicaService.js +111 -0
  108. package/dist/services/replicaService.js.map +1 -0
  109. package/dist/services/requestContextService.js +15 -0
  110. package/dist/services/requestContextService.js.map +1 -1
  111. package/dist/services/serverConfigEnvironment.js +2 -1
  112. package/dist/services/serverConfigEnvironment.js.map +1 -1
  113. package/dist/services/sseService.js +61 -8
  114. package/dist/services/sseService.js.map +1 -1
  115. package/dist/services/templateService.js +11 -5
  116. package/dist/services/templateService.js.map +1 -1
  117. package/dist/services/userService.js +5 -1
  118. package/dist/services/userService.js.map +1 -1
  119. package/dist/utils/groupAccess.js +6 -4
  120. package/dist/utils/groupAccess.js.map +1 -1
  121. package/dist/utils/groupKeyScope.js +11 -5
  122. package/dist/utils/groupKeyScope.js.map +1 -1
  123. package/dist/utils/logger.js +7 -0
  124. package/dist/utils/logger.js.map +1 -1
  125. package/dist/utils/migration.js +8 -0
  126. package/dist/utils/migration.js.map +1 -1
  127. package/dist/utils/oauthBearer.js +1 -0
  128. package/dist/utils/oauthBearer.js.map +1 -1
  129. package/dist/utils/oauthServerMetadata.js +21 -0
  130. package/dist/utils/oauthServerMetadata.js.map +1 -1
  131. package/dist/utils/replicaLogContext.js +3 -0
  132. package/dist/utils/replicaLogContext.js.map +1 -0
  133. package/dist/utils/replicaMode.js +17 -0
  134. package/dist/utils/replicaMode.js.map +1 -0
  135. package/dist/utils/serverConfigPersistence.js +20 -1
  136. package/dist/utils/serverConfigPersistence.js.map +1 -1
  137. package/dist/utils/serverConfigValidation.js +1 -0
  138. package/dist/utils/serverConfigValidation.js.map +1 -1
  139. package/dist/utils/settingsSync.js +4 -0
  140. package/dist/utils/settingsSync.js.map +1 -1
  141. package/frontend/dist/assets/{ActivityPage-C8Zj4FeE.js → ActivityPage-CSXEpl6y.js} +2 -2
  142. package/frontend/dist/assets/{ActivityPage-C8Zj4FeE.js.map → ActivityPage-CSXEpl6y.js.map} +1 -1
  143. package/frontend/dist/assets/{ConfirmDialog-BYdfb115.js → ConfirmDialog-BJpzQmMw.js} +2 -2
  144. package/frontend/dist/assets/{ConfirmDialog-BYdfb115.js.map → ConfirmDialog-BJpzQmMw.js.map} +1 -1
  145. package/frontend/dist/assets/CredentialsPage-CPxOHQUZ.js +2 -0
  146. package/frontend/dist/assets/CredentialsPage-CPxOHQUZ.js.map +1 -0
  147. package/frontend/dist/assets/{Dashboard-CCiZcGi4.js → Dashboard-Da-YT_l_.js} +2 -2
  148. package/frontend/dist/assets/{Dashboard-CCiZcGi4.js.map → Dashboard-Da-YT_l_.js.map} +1 -1
  149. package/frontend/dist/assets/{DeleteDialog-BaCsEkJA.js → DeleteDialog-BoMYLx3J.js} +2 -2
  150. package/frontend/dist/assets/{DeleteDialog-BaCsEkJA.js.map → DeleteDialog-BoMYLx3J.js.map} +1 -1
  151. package/frontend/dist/assets/{EndpointCopy-CmKx4RVL.js → EndpointCopy-CZOjiFAH.js} +2 -2
  152. package/frontend/dist/assets/{EndpointCopy-CmKx4RVL.js.map → EndpointCopy-CZOjiFAH.js.map} +1 -1
  153. package/frontend/dist/assets/GroupsPage-CSA5G9_H.js +33 -0
  154. package/frontend/dist/assets/GroupsPage-CSA5G9_H.js.map +1 -0
  155. package/frontend/dist/assets/{LoginPage-Hn1uu0vB.js → LoginPage-7RswUPtG.js} +2 -2
  156. package/frontend/dist/assets/{LoginPage-Hn1uu0vB.js.map → LoginPage-7RswUPtG.js.map} +1 -1
  157. package/frontend/dist/assets/{LogsPage-JMCtTjCv.js → LogsPage-Dbef_68F.js} +2 -2
  158. package/frontend/dist/assets/{LogsPage-JMCtTjCv.js.map → LogsPage-Dbef_68F.js.map} +1 -1
  159. package/frontend/dist/assets/MarketPage-rSBdHZxo.js +2 -0
  160. package/frontend/dist/assets/{MarketPage-Bvi1KESo.js.map → MarketPage-rSBdHZxo.js.map} +1 -1
  161. package/frontend/dist/assets/{OAuthConsentPage-DofQEZAU.js → OAuthConsentPage-BH9O7bk3.js} +2 -2
  162. package/frontend/dist/assets/{OAuthConsentPage-DofQEZAU.js.map → OAuthConsentPage-BH9O7bk3.js.map} +1 -1
  163. package/frontend/dist/assets/{Pagination-D99WHhBx.js → Pagination-S-7lzy59.js} +2 -2
  164. package/frontend/dist/assets/{Pagination-D99WHhBx.js.map → Pagination-S-7lzy59.js.map} +1 -1
  165. package/frontend/dist/assets/{PromptsPage-B-8vczAR.js → PromptsPage-B558MhVU.js} +2 -2
  166. package/frontend/dist/assets/{PromptsPage-B-8vczAR.js.map → PromptsPage-B558MhVU.js.map} +1 -1
  167. package/frontend/dist/assets/{ResourcesPage-DLAIatoG.js → ResourcesPage-DQpy8B9L.js} +2 -2
  168. package/frontend/dist/assets/{ResourcesPage-DLAIatoG.js.map → ResourcesPage-DQpy8B9L.js.map} +1 -1
  169. package/frontend/dist/assets/{ServersPage-wVLsTL_f.js → ServersPage-DBcC8pG2.js} +5 -5
  170. package/frontend/dist/assets/ServersPage-DBcC8pG2.js.map +1 -0
  171. package/frontend/dist/assets/SettingsPage-BcpeHFR3.js +14 -0
  172. package/frontend/dist/assets/SettingsPage-BcpeHFR3.js.map +1 -0
  173. package/frontend/dist/assets/{StatusDot-00-sTQKv.js → StatusDot-CLxRrJ5l.js} +2 -2
  174. package/frontend/dist/assets/{StatusDot-00-sTQKv.js.map → StatusDot-CLxRrJ5l.js.map} +1 -1
  175. package/frontend/dist/assets/{ToggleGroup-CtWBng3r.js → ToggleGroup-DuWJZU41.js} +2 -2
  176. package/frontend/dist/assets/{ToggleGroup-CtWBng3r.js.map → ToggleGroup-DuWJZU41.js.map} +1 -1
  177. package/frontend/dist/assets/UserGroupSelection-CN0weGXJ.js +2 -0
  178. package/frontend/dist/assets/UserGroupSelection-CN0weGXJ.js.map +1 -0
  179. package/frontend/dist/assets/UsersPage-B158Bofs.js +2 -0
  180. package/frontend/dist/assets/UsersPage-B158Bofs.js.map +1 -0
  181. package/frontend/dist/assets/{clipboard-2RRFM-q8.js → clipboard-BLr_gbf8.js} +2 -2
  182. package/frontend/dist/assets/{clipboard-2RRFM-q8.js.map → clipboard-BLr_gbf8.js.map} +1 -1
  183. package/frontend/dist/assets/{costRevision-Ntl7VtWo.js → costRevision-Dzec2k_b.js} +2 -2
  184. package/frontend/dist/assets/{costRevision-Ntl7VtWo.js.map → costRevision-Dzec2k_b.js.map} +1 -1
  185. package/frontend/dist/assets/framework-vendor-D0S_ys6r.js +61 -0
  186. package/frontend/dist/assets/framework-vendor-D0S_ys6r.js.map +1 -0
  187. package/frontend/dist/assets/{i18n-vendor-DVmjrzgW.js → i18n-vendor-COTriSc6.js} +2 -2
  188. package/frontend/dist/assets/{i18n-vendor-DVmjrzgW.js.map → i18n-vendor-COTriSc6.js.map} +1 -1
  189. package/frontend/dist/assets/{icons-vendor-CyAS6fj0.js → icons-vendor-DZjjGrOu.js} +2 -2
  190. package/frontend/dist/assets/{icons-vendor-CyAS6fj0.js.map → icons-vendor-DZjjGrOu.js.map} +1 -1
  191. package/frontend/dist/assets/index-CGRjM_NC.js +3 -0
  192. package/frontend/dist/assets/{index-CkSmxNPW.js.map → index-CGRjM_NC.js.map} +1 -1
  193. package/frontend/dist/assets/{resourceService-BB4UM6IQ.js → resourceService-BNH7uNar.js} +2 -2
  194. package/frontend/dist/assets/{resourceService-BB4UM6IQ.js.map → resourceService-BNH7uNar.js.map} +1 -1
  195. package/frontend/dist/assets/{useCostData-C3XnQyUj.js → useCostData-B2d2eLCD.js} +2 -2
  196. package/frontend/dist/assets/{useCostData-C3XnQyUj.js.map → useCostData-B2d2eLCD.js.map} +1 -1
  197. package/frontend/dist/assets/variableDetection-DRdE941j.js +80 -0
  198. package/frontend/dist/assets/variableDetection-DRdE941j.js.map +1 -0
  199. package/frontend/dist/index.html +4 -4
  200. package/package.json +4 -2
  201. package/frontend/dist/assets/CredentialsPage-BnzqPYjg.js +0 -2
  202. package/frontend/dist/assets/CredentialsPage-BnzqPYjg.js.map +0 -1
  203. package/frontend/dist/assets/GroupsPage-DHgQeVHy.js +0 -33
  204. package/frontend/dist/assets/GroupsPage-DHgQeVHy.js.map +0 -1
  205. package/frontend/dist/assets/MarketPage-Bvi1KESo.js +0 -2
  206. package/frontend/dist/assets/ServersPage-wVLsTL_f.js.map +0 -1
  207. package/frontend/dist/assets/SettingsPage-DkfXDE5_.js +0 -14
  208. package/frontend/dist/assets/SettingsPage-DkfXDE5_.js.map +0 -1
  209. package/frontend/dist/assets/UsersPage-BtdFWcVD.js +0 -2
  210. package/frontend/dist/assets/UsersPage-BtdFWcVD.js.map +0 -1
  211. package/frontend/dist/assets/framework-vendor-CQFm9vKd.js +0 -61
  212. package/frontend/dist/assets/framework-vendor-CQFm9vKd.js.map +0 -1
  213. package/frontend/dist/assets/index-CkSmxNPW.js +0 -3
  214. package/frontend/dist/assets/variableDetection-BUXwrSop.js +0 -18
  215. package/frontend/dist/assets/variableDetection-BUXwrSop.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{j as e}from"./framework-vendor-CQFm9vKd.js";import{o as l}from"./index-CkSmxNPW.js";import{u}from"./i18n-vendor-DVmjrzgW.js";const h={connected:"ok",connecting:"warn",oauth_required:"warn",disconnected:"err"},p={connected:"status.online",connecting:"status.connecting",oauth_required:"status.oauthRequired",disconnected:"status.offline"},c=({kind:n,label:i,className:a,onClick:s,title:t})=>e.jsxs("span",{className:l("hub-status",n,s?"cursor-pointer hover:opacity-80":"",a),onClick:s,title:t,children:[e.jsx("span",{className:"hub-dot"}),i!=null&&e.jsx("span",{className:"hub-status-label",children:i})]}),S=({status:n,enabled:i,startOnDemand:a,onAuthClick:s,className:t})=>{const{t:r}=u();if(i===!1)return e.jsx(c,{kind:"muted",label:r("server.disable")||"Disabled",className:t});if(a&&n==="disconnected")return e.jsx(c,{kind:"muted",label:e.jsxs(e.Fragment,{children:[e.jsx("span",{"aria-hidden":!0,children:"💤"}),e.jsx("span",{children:r("status.sleeping","Sleeping")})]}),title:r("status.sleepingDescription","On-demand server — will start automatically when a tool is called"),className:t});const d=h[n]??"muted",o=n==="oauth_required";return e.jsx(c,{kind:d,label:e.jsxs(e.Fragment,{children:[o&&e.jsx("span",{"aria-hidden":!0,children:"🔐"}),e.jsx("span",{children:r(p[n]||n)})]}),onClick:o&&s?s:void 0,title:o?r("status.clickToAuthorize"):void 0,className:t})};export{S,c as a};
2
- //# sourceMappingURL=StatusDot-00-sTQKv.js.map
1
+ import{j as e}from"./framework-vendor-D0S_ys6r.js";import{o as l}from"./index-CGRjM_NC.js";import{u}from"./i18n-vendor-COTriSc6.js";const h={connected:"ok",connecting:"warn",oauth_required:"warn",disconnected:"err"},p={connected:"status.online",connecting:"status.connecting",oauth_required:"status.oauthRequired",disconnected:"status.offline"},c=({kind:n,label:i,className:a,onClick:s,title:t})=>e.jsxs("span",{className:l("hub-status",n,s?"cursor-pointer hover:opacity-80":"",a),onClick:s,title:t,children:[e.jsx("span",{className:"hub-dot"}),i!=null&&e.jsx("span",{className:"hub-status-label",children:i})]}),S=({status:n,enabled:i,startOnDemand:a,onAuthClick:s,className:t})=>{const{t:r}=u();if(i===!1)return e.jsx(c,{kind:"muted",label:r("server.disable")||"Disabled",className:t});if(a&&n==="disconnected")return e.jsx(c,{kind:"muted",label:e.jsxs(e.Fragment,{children:[e.jsx("span",{"aria-hidden":!0,children:"💤"}),e.jsx("span",{children:r("status.sleeping","Sleeping")})]}),title:r("status.sleepingDescription","On-demand server — will start automatically when a tool is called"),className:t});const d=h[n]??"muted",o=n==="oauth_required";return e.jsx(c,{kind:d,label:e.jsxs(e.Fragment,{children:[o&&e.jsx("span",{"aria-hidden":!0,children:"🔐"}),e.jsx("span",{children:r(p[n]||n)})]}),onClick:o&&s?s:void 0,title:o?r("status.clickToAuthorize"):void 0,className:t})};export{S,c as a};
2
+ //# sourceMappingURL=StatusDot-CLxRrJ5l.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatusDot-00-sTQKv.js","sources":["../../src/components/ui/StatusDot.tsx"],"sourcesContent":["import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport type { ServerStatus } from '@/types';\nimport { cn } from '@/utils/cn';\n\nexport type DotKind = 'ok' | 'warn' | 'err' | 'muted';\n\nconst STATUS_TO_KIND: Record<ServerStatus, DotKind> = {\n connected: 'ok',\n connecting: 'warn',\n oauth_required: 'warn',\n disconnected: 'err',\n};\n\nconst STATUS_TO_KEY: Record<ServerStatus, string> = {\n connected: 'status.online',\n connecting: 'status.connecting',\n oauth_required: 'status.oauthRequired',\n disconnected: 'status.offline',\n};\n\ninterface StatusDotProps {\n kind: DotKind;\n label?: React.ReactNode;\n className?: string;\n onClick?: (e: React.MouseEvent) => void;\n title?: string;\n}\n\nexport const StatusDot: React.FC<StatusDotProps> = ({ kind, label, className, onClick, title }) => (\n <span\n className={cn('hub-status', kind, onClick ? 'cursor-pointer hover:opacity-80' : '', className)}\n onClick={onClick}\n title={title}\n >\n <span className=\"hub-dot\" />\n {label != null && <span className=\"hub-status-label\">{label}</span>}\n </span>\n);\n\ninterface ServerStatusDotProps {\n status: ServerStatus;\n enabled?: boolean;\n startOnDemand?: boolean;\n onAuthClick?: (e: React.MouseEvent) => void;\n className?: string;\n}\n\nexport const ServerStatusDot: React.FC<ServerStatusDotProps> = ({\n status,\n enabled,\n startOnDemand,\n onAuthClick,\n className,\n}) => {\n const { t } = useTranslation();\n if (enabled === false) {\n return <StatusDot kind=\"muted\" label={t('server.disable') || 'Disabled'} className={className} />;\n }\n // On-demand servers that are disconnected are \"sleeping\", not offline\n if (startOnDemand && status === 'disconnected') {\n return (\n <StatusDot\n kind=\"muted\"\n label={\n <>\n <span aria-hidden>💤</span>\n <span>{t('status.sleeping', 'Sleeping')}</span>\n </>\n }\n title={t('status.sleepingDescription', 'On-demand server — will start automatically when a tool is called')}\n className={className}\n />\n );\n }\n const kind = STATUS_TO_KIND[status] ?? 'muted';\n const isOAuth = status === 'oauth_required';\n return (\n <StatusDot\n kind={kind}\n label={\n <>\n {isOAuth && <span aria-hidden>🔐</span>}\n <span>{t(STATUS_TO_KEY[status] || status)}</span>\n </>\n }\n onClick={isOAuth && onAuthClick ? onAuthClick : undefined}\n title={isOAuth ? t('status.clickToAuthorize') : undefined}\n className={className}\n />\n );\n};\n"],"names":["STATUS_TO_KIND","STATUS_TO_KEY","StatusDot","kind","label","className","onClick","title","jsxs","cn","jsx","ServerStatusDot","status","enabled","startOnDemand","onAuthClick","t","useTranslation","Fragment","isOAuth"],"mappings":"oIAOA,MAAMA,EAAgD,CACpD,UAAW,KACX,WAAY,OACZ,eAAgB,OAChB,aAAc,KAChB,EAEMC,EAA8C,CAClD,UAAW,gBACX,WAAY,oBACZ,eAAgB,uBAChB,aAAc,gBAChB,EAUaC,EAAsC,CAAC,CAAE,KAAAC,EAAM,MAAAC,EAAO,UAAAC,EAAW,QAAAC,EAAS,MAAAC,KACrFC,EAAAA,KAAC,OAAA,CACC,UAAWC,EAAG,aAAcN,EAAMG,EAAU,kCAAoC,GAAID,CAAS,EAC7F,QAAAC,EACA,MAAAC,EAEA,SAAA,CAAAG,EAAAA,IAAC,OAAA,CAAK,UAAU,SAAA,CAAU,EACzBN,GAAS,MAAQM,EAAAA,IAAC,OAAA,CAAK,UAAU,mBAAoB,SAAAN,CAAA,CAAM,CAAA,CAAA,CAC9D,EAWWO,EAAkD,CAAC,CAC9D,OAAAC,EACA,QAAAC,EACA,cAAAC,EACA,YAAAC,EACA,UAAAV,CACF,IAAM,CACJ,KAAM,CAAE,EAAAW,CAAA,EAAMC,EAAA,EACd,GAAIJ,IAAY,GACd,OAAOH,MAACR,GAAU,KAAK,QAAQ,MAAOc,EAAE,gBAAgB,GAAK,WAAY,UAAAX,CAAA,CAAsB,EAGjG,GAAIS,GAAiBF,IAAW,eAC9B,OACEF,EAAAA,IAACR,EAAA,CACC,KAAK,QACL,MACEM,EAAAA,KAAAU,WAAA,CACE,SAAA,CAAAR,EAAAA,IAAC,OAAA,CAAK,cAAW,GAAC,SAAA,KAAE,EACpBA,EAAAA,IAAC,OAAA,CAAM,SAAAM,EAAE,kBAAmB,UAAU,CAAA,CAAE,CAAA,EAC1C,EAEF,MAAOA,EAAE,6BAA8B,mEAAmE,EAC1G,UAAAX,CAAA,CAAA,EAIN,MAAMF,EAAOH,EAAeY,CAAM,GAAK,QACjCO,EAAUP,IAAW,iBAC3B,OACEF,EAAAA,IAACR,EAAA,CACC,KAAAC,EACA,MACEK,EAAAA,KAAAU,WAAA,CACG,SAAA,CAAAC,GAAWT,EAAAA,IAAC,OAAA,CAAK,cAAW,GAAC,SAAA,KAAE,QAC/B,OAAA,CAAM,SAAAM,EAAEf,EAAcW,CAAM,GAAKA,CAAM,CAAA,CAAE,CAAA,EAC5C,EAEF,QAASO,GAAWJ,EAAcA,EAAc,OAChD,MAAOI,EAAUH,EAAE,yBAAyB,EAAI,OAChD,UAAAX,CAAA,CAAA,CAGN"}
1
+ {"version":3,"file":"StatusDot-CLxRrJ5l.js","sources":["../../src/components/ui/StatusDot.tsx"],"sourcesContent":["import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport type { ServerStatus } from '@/types';\nimport { cn } from '@/utils/cn';\n\nexport type DotKind = 'ok' | 'warn' | 'err' | 'muted';\n\nconst STATUS_TO_KIND: Record<ServerStatus, DotKind> = {\n connected: 'ok',\n connecting: 'warn',\n oauth_required: 'warn',\n disconnected: 'err',\n};\n\nconst STATUS_TO_KEY: Record<ServerStatus, string> = {\n connected: 'status.online',\n connecting: 'status.connecting',\n oauth_required: 'status.oauthRequired',\n disconnected: 'status.offline',\n};\n\ninterface StatusDotProps {\n kind: DotKind;\n label?: React.ReactNode;\n className?: string;\n onClick?: (e: React.MouseEvent) => void;\n title?: string;\n}\n\nexport const StatusDot: React.FC<StatusDotProps> = ({ kind, label, className, onClick, title }) => (\n <span\n className={cn('hub-status', kind, onClick ? 'cursor-pointer hover:opacity-80' : '', className)}\n onClick={onClick}\n title={title}\n >\n <span className=\"hub-dot\" />\n {label != null && <span className=\"hub-status-label\">{label}</span>}\n </span>\n);\n\ninterface ServerStatusDotProps {\n status: ServerStatus;\n enabled?: boolean;\n startOnDemand?: boolean;\n onAuthClick?: (e: React.MouseEvent) => void;\n className?: string;\n}\n\nexport const ServerStatusDot: React.FC<ServerStatusDotProps> = ({\n status,\n enabled,\n startOnDemand,\n onAuthClick,\n className,\n}) => {\n const { t } = useTranslation();\n if (enabled === false) {\n return <StatusDot kind=\"muted\" label={t('server.disable') || 'Disabled'} className={className} />;\n }\n // On-demand servers that are disconnected are \"sleeping\", not offline\n if (startOnDemand && status === 'disconnected') {\n return (\n <StatusDot\n kind=\"muted\"\n label={\n <>\n <span aria-hidden>💤</span>\n <span>{t('status.sleeping', 'Sleeping')}</span>\n </>\n }\n title={t('status.sleepingDescription', 'On-demand server — will start automatically when a tool is called')}\n className={className}\n />\n );\n }\n const kind = STATUS_TO_KIND[status] ?? 'muted';\n const isOAuth = status === 'oauth_required';\n return (\n <StatusDot\n kind={kind}\n label={\n <>\n {isOAuth && <span aria-hidden>🔐</span>}\n <span>{t(STATUS_TO_KEY[status] || status)}</span>\n </>\n }\n onClick={isOAuth && onAuthClick ? onAuthClick : undefined}\n title={isOAuth ? t('status.clickToAuthorize') : undefined}\n className={className}\n />\n );\n};\n"],"names":["STATUS_TO_KIND","STATUS_TO_KEY","StatusDot","kind","label","className","onClick","title","jsxs","cn","jsx","ServerStatusDot","status","enabled","startOnDemand","onAuthClick","t","useTranslation","Fragment","isOAuth"],"mappings":"oIAOA,MAAMA,EAAgD,CACpD,UAAW,KACX,WAAY,OACZ,eAAgB,OAChB,aAAc,KAChB,EAEMC,EAA8C,CAClD,UAAW,gBACX,WAAY,oBACZ,eAAgB,uBAChB,aAAc,gBAChB,EAUaC,EAAsC,CAAC,CAAE,KAAAC,EAAM,MAAAC,EAAO,UAAAC,EAAW,QAAAC,EAAS,MAAAC,KACrFC,EAAAA,KAAC,OAAA,CACC,UAAWC,EAAG,aAAcN,EAAMG,EAAU,kCAAoC,GAAID,CAAS,EAC7F,QAAAC,EACA,MAAAC,EAEA,SAAA,CAAAG,EAAAA,IAAC,OAAA,CAAK,UAAU,SAAA,CAAU,EACzBN,GAAS,MAAQM,EAAAA,IAAC,OAAA,CAAK,UAAU,mBAAoB,SAAAN,CAAA,CAAM,CAAA,CAAA,CAC9D,EAWWO,EAAkD,CAAC,CAC9D,OAAAC,EACA,QAAAC,EACA,cAAAC,EACA,YAAAC,EACA,UAAAV,CACF,IAAM,CACJ,KAAM,CAAE,EAAAW,CAAA,EAAMC,EAAA,EACd,GAAIJ,IAAY,GACd,OAAOH,MAACR,GAAU,KAAK,QAAQ,MAAOc,EAAE,gBAAgB,GAAK,WAAY,UAAAX,CAAA,CAAsB,EAGjG,GAAIS,GAAiBF,IAAW,eAC9B,OACEF,EAAAA,IAACR,EAAA,CACC,KAAK,QACL,MACEM,EAAAA,KAAAU,WAAA,CACE,SAAA,CAAAR,EAAAA,IAAC,OAAA,CAAK,cAAW,GAAC,SAAA,KAAE,EACpBA,EAAAA,IAAC,OAAA,CAAM,SAAAM,EAAE,kBAAmB,UAAU,CAAA,CAAE,CAAA,EAC1C,EAEF,MAAOA,EAAE,6BAA8B,mEAAmE,EAC1G,UAAAX,CAAA,CAAA,EAIN,MAAMF,EAAOH,EAAeY,CAAM,GAAK,QACjCO,EAAUP,IAAW,iBAC3B,OACEF,EAAAA,IAACR,EAAA,CACC,KAAAC,EACA,MACEK,EAAAA,KAAAU,WAAA,CACG,SAAA,CAAAC,GAAWT,EAAAA,IAAC,OAAA,CAAK,cAAW,GAAC,SAAA,KAAE,QAC/B,OAAA,CAAM,SAAAM,EAAEf,EAAcW,CAAM,GAAKA,CAAM,CAAA,CAAE,CAAA,EAC5C,EAEF,QAASO,GAAWJ,EAAcA,EAAc,OAChD,MAAOI,EAAUH,EAAE,yBAAyB,EAAI,OAChD,UAAAX,CAAA,CAAA,CAGN"}
@@ -1,2 +1,2 @@
1
- import{j as u}from"./framework-vendor-CQFm9vKd.js";import{o as f}from"./index-CkSmxNPW.js";const n=({checked:t,onCheckedChange:p,disabled:o=!1,size:s="regular",className:m,onClick:r,...c})=>u.jsx("button",{...c,type:"button",role:"switch","aria-checked":t,disabled:o,className:f("hub-switch",t&&"on",s==="card"&&"card",s==="compact"&&"compact",m),onClick:a=>{a.stopPropagation(),r==null||r(a),!a.defaultPrevented&&!o&&p(!t)}});export{n as S};
2
- //# sourceMappingURL=ToggleGroup-CtWBng3r.js.map
1
+ import{j as u}from"./framework-vendor-D0S_ys6r.js";import{o as f}from"./index-CGRjM_NC.js";const n=({checked:t,onCheckedChange:p,disabled:o=!1,size:s="regular",className:m,onClick:r,...c})=>u.jsx("button",{...c,type:"button",role:"switch","aria-checked":t,disabled:o,className:f("hub-switch",t&&"on",s==="card"&&"card",s==="compact"&&"compact",m),onClick:a=>{a.stopPropagation(),r==null||r(a),!a.defaultPrevented&&!o&&p(!t)}});export{n as S};
2
+ //# sourceMappingURL=ToggleGroup-DuWJZU41.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToggleGroup-CtWBng3r.js","sources":["../../src/components/ui/ToggleGroup.tsx"],"sourcesContent":["import React, { ReactNode, useId } from 'react';\nimport { cn } from '@/utils/cn';\n\ninterface ToggleGroupItemProps {\n value: string;\n isSelected: boolean;\n onClick: () => void;\n children: ReactNode;\n}\n\nexport const ToggleGroupItem: React.FC<ToggleGroupItemProps> = ({\n isSelected,\n onClick,\n children,\n}) => {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={isSelected}\n className={cn(\n 'flex w-full cursor-pointer items-center justify-between rounded p-2 transition-colors',\n isSelected\n ? 'border-l-4 border-blue-500 bg-blue-50 text-blue-700 hover:bg-blue-100'\n : 'text-gray-700 hover:bg-gray-50',\n )}\n onClick={onClick}\n >\n <span className=\"flex items-center\">{children}</span>\n {isSelected && (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" className=\"h-5 w-5 text-blue-500\">\n <path\n fillRule=\"evenodd\"\n d=\"M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z\"\n clipRule=\"evenodd\"\n />\n </svg>\n )}\n </button>\n );\n};\n\ninterface ToggleGroupProps {\n label: string;\n helpText?: string;\n noOptionsText?: string;\n values: string[];\n options: { value: string; label: string }[];\n onChange: (values: string[]) => void;\n className?: string;\n}\n\nexport const ToggleGroup: React.FC<ToggleGroupProps> = ({\n label,\n helpText,\n noOptionsText = 'No options available',\n values,\n options,\n onChange,\n className,\n}) => {\n const labelId = useId();\n\n const handleToggle = (value: string) => {\n const isSelected = values.includes(value);\n if (isSelected) {\n onChange(values.filter((v) => v !== value));\n } else {\n onChange([...values, value]);\n }\n };\n\n return (\n <div className={className}>\n <div id={labelId} className=\"mb-2 block text-sm font-bold text-gray-700\">{label}</div>\n <div\n role=\"group\"\n aria-labelledby={labelId}\n className=\"max-h-60 overflow-y-auto rounded border border-gray-200 shadow dark:border-gray-700\"\n >\n {options.length === 0 ? (\n <p className=\"p-3 text-sm text-gray-500\">{noOptionsText}</p>\n ) : (\n <div className=\"space-y-1 p-1\">\n {options.map((option) => (\n <ToggleGroupItem\n key={option.value}\n value={option.value}\n isSelected={values.includes(option.value)}\n onClick={() => handleToggle(option.value)}\n >\n {option.label}\n </ToggleGroupItem>\n ))}\n </div>\n )}\n </div>\n {helpText && <p className=\"mt-1 text-xs text-gray-500\">{helpText}</p>}\n </div>\n );\n};\n\ninterface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onChange' | 'role' | 'type'> {\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n size?: 'regular' | 'card' | 'compact';\n}\n\nexport const Switch: React.FC<SwitchProps> = ({\n checked,\n onCheckedChange,\n disabled = false,\n size = 'regular',\n className,\n onClick,\n ...buttonProps\n}) => {\n return (\n <button\n {...buttonProps}\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n className={cn(\n 'hub-switch',\n checked && 'on',\n size === 'card' && 'card',\n size === 'compact' && 'compact',\n className,\n )}\n onClick={(event) => {\n event.stopPropagation();\n onClick?.(event);\n if (!event.defaultPrevented && !disabled) {\n onCheckedChange(!checked);\n }\n }}\n />\n );\n};"],"names":["Switch","checked","onCheckedChange","disabled","size","className","onClick","buttonProps","jsx","cn","event"],"mappings":"2FA4GO,MAAMA,EAAgC,CAAC,CAC5C,QAAAC,EACA,gBAAAC,EACA,SAAAC,EAAW,GACX,KAAAC,EAAO,UACP,UAAAC,EACA,QAAAC,EACA,GAAGC,CACL,IAEIC,EAAAA,IAAC,SAAA,CACE,GAAGD,EACJ,KAAK,SACL,KAAK,SACL,eAAcN,EACd,SAAAE,EACA,UAAWM,EACT,aACAR,GAAW,KACXG,IAAS,QAAU,OACnBA,IAAS,WAAa,UACtBC,CAAA,EAEF,QAAUK,GAAU,CAClBA,EAAM,gBAAA,EACNJ,GAAA,MAAAA,EAAUI,GACN,CAACA,EAAM,kBAAoB,CAACP,GAC9BD,EAAgB,CAACD,CAAO,CAE5B,CAAA,CAAA"}
1
+ {"version":3,"file":"ToggleGroup-DuWJZU41.js","sources":["../../src/components/ui/ToggleGroup.tsx"],"sourcesContent":["import React, { ReactNode, useId } from 'react';\nimport { cn } from '@/utils/cn';\n\ninterface ToggleGroupItemProps {\n value: string;\n isSelected: boolean;\n onClick: () => void;\n children: ReactNode;\n}\n\nexport const ToggleGroupItem: React.FC<ToggleGroupItemProps> = ({\n isSelected,\n onClick,\n children,\n}) => {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={isSelected}\n className={cn(\n 'flex w-full cursor-pointer items-center justify-between rounded p-2 transition-colors',\n isSelected\n ? 'border-l-4 border-blue-500 bg-blue-50 text-blue-700 hover:bg-blue-100'\n : 'text-gray-700 hover:bg-gray-50',\n )}\n onClick={onClick}\n >\n <span className=\"flex items-center\">{children}</span>\n {isSelected && (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" className=\"h-5 w-5 text-blue-500\">\n <path\n fillRule=\"evenodd\"\n d=\"M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z\"\n clipRule=\"evenodd\"\n />\n </svg>\n )}\n </button>\n );\n};\n\ninterface ToggleGroupProps {\n label: string;\n helpText?: string;\n noOptionsText?: string;\n values: string[];\n options: { value: string; label: string }[];\n onChange: (values: string[]) => void;\n className?: string;\n}\n\nexport const ToggleGroup: React.FC<ToggleGroupProps> = ({\n label,\n helpText,\n noOptionsText = 'No options available',\n values,\n options,\n onChange,\n className,\n}) => {\n const labelId = useId();\n\n const handleToggle = (value: string) => {\n const isSelected = values.includes(value);\n if (isSelected) {\n onChange(values.filter((v) => v !== value));\n } else {\n onChange([...values, value]);\n }\n };\n\n return (\n <div className={className}>\n <div id={labelId} className=\"mb-2 block text-sm font-bold text-gray-700\">{label}</div>\n <div\n role=\"group\"\n aria-labelledby={labelId}\n className=\"max-h-60 overflow-y-auto rounded border border-gray-200 shadow dark:border-gray-700\"\n >\n {options.length === 0 ? (\n <p className=\"p-3 text-sm text-gray-500\">{noOptionsText}</p>\n ) : (\n <div className=\"space-y-1 p-1\">\n {options.map((option) => (\n <ToggleGroupItem\n key={option.value}\n value={option.value}\n isSelected={values.includes(option.value)}\n onClick={() => handleToggle(option.value)}\n >\n {option.label}\n </ToggleGroupItem>\n ))}\n </div>\n )}\n </div>\n {helpText && <p className=\"mt-1 text-xs text-gray-500\">{helpText}</p>}\n </div>\n );\n};\n\ninterface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onChange' | 'role' | 'type'> {\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n size?: 'regular' | 'card' | 'compact';\n}\n\nexport const Switch: React.FC<SwitchProps> = ({\n checked,\n onCheckedChange,\n disabled = false,\n size = 'regular',\n className,\n onClick,\n ...buttonProps\n}) => {\n return (\n <button\n {...buttonProps}\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n className={cn(\n 'hub-switch',\n checked && 'on',\n size === 'card' && 'card',\n size === 'compact' && 'compact',\n className,\n )}\n onClick={(event) => {\n event.stopPropagation();\n onClick?.(event);\n if (!event.defaultPrevented && !disabled) {\n onCheckedChange(!checked);\n }\n }}\n />\n );\n};"],"names":["Switch","checked","onCheckedChange","disabled","size","className","onClick","buttonProps","jsx","cn","event"],"mappings":"2FA4GO,MAAMA,EAAgC,CAAC,CAC5C,QAAAC,EACA,gBAAAC,EACA,SAAAC,EAAW,GACX,KAAAC,EAAO,UACP,UAAAC,EACA,QAAAC,EACA,GAAGC,CACL,IAEIC,EAAAA,IAAC,SAAA,CACE,GAAGD,EACJ,KAAK,SACL,KAAK,SACL,eAAcN,EACd,SAAAE,EACA,UAAWM,EACT,aACAR,GAAW,KACXG,IAAS,QAAU,OACnBA,IAAS,WAAa,UACtBC,CAAA,EAEF,QAAUK,GAAU,CAClBA,EAAM,gBAAA,EACNJ,GAAA,MAAAA,EAAUI,GACN,CAACA,EAAM,kBAAoB,CAACP,GAC9BD,EAAgB,CAACD,CAAO,CAE5B,CAAA,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{j as e}from"./framework-vendor-D0S_ys6r.js";import{u as x}from"./i18n-vendor-COTriSc6.js";function p({options:n,selected:t,disabled:a,onChange:m}){const{t:r}=x(),c=[...new Set([...n,...t])].sort();return e.jsxs("fieldset",{disabled:a,className:"mb-4",children:[e.jsx("legend",{className:"text-sm font-medium",children:r("users.userGroups")}),e.jsx("p",{className:"text-xs text-gray-500 mt-1 mb-2",children:r("users.shareUserGroupsHint")}),c.map(s=>e.jsxs("label",{className:"flex items-center gap-2 py-1 text-sm",children:[e.jsx("input",{type:"checkbox",checked:t.includes(s),onChange:i=>m(i.target.checked?[...t,s]:t.filter(o=>o!==s))}),s]},s))]})}export{p as U};
2
+ //# sourceMappingURL=UserGroupSelection-CN0weGXJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserGroupSelection-CN0weGXJ.js","sources":["../../src/components/UserGroupSelection.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nexport default function UserGroupSelection({\n options,\n selected,\n disabled,\n onChange,\n}: {\n options: string[];\n selected: string[];\n disabled?: boolean;\n onChange: (groups: string[]) => void;\n}) {\n const { t } = useTranslation();\n const groups = [...new Set([...options, ...selected])].sort();\n return (\n <fieldset disabled={disabled} className=\"mb-4\">\n <legend className=\"text-sm font-medium\">{t('users.userGroups')}</legend>\n <p className=\"text-xs text-gray-500 mt-1 mb-2\">{t('users.shareUserGroupsHint')}</p>\n {groups.map((group) => (\n <label key={group} className=\"flex items-center gap-2 py-1 text-sm\">\n <input\n type=\"checkbox\"\n checked={selected.includes(group)}\n onChange={(event) =>\n onChange(\n event.target.checked\n ? [...selected, group]\n : selected.filter((name) => name !== group),\n )\n }\n />\n {group}\n </label>\n ))}\n </fieldset>\n );\n}\n"],"names":["UserGroupSelection","options","selected","disabled","onChange","t","useTranslation","groups","jsxs","jsx","group","event","name"],"mappings":"iGAEA,SAAwBA,EAAmB,CACzC,QAAAC,EACA,SAAAC,EACA,SAAAC,EACA,SAAAC,CACF,EAKG,CACD,KAAM,CAAE,EAAAC,CAAA,EAAMC,EAAA,EACRC,EAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAGN,EAAS,GAAGC,CAAQ,CAAC,CAAC,EAAE,KAAA,EACvD,OACEM,EAAAA,KAAC,WAAA,CAAS,SAAAL,EAAoB,UAAU,OACtC,SAAA,CAAAM,MAAC,SAAA,CAAO,UAAU,sBAAuB,SAAAJ,EAAE,kBAAkB,EAAE,QAC9D,IAAA,CAAE,UAAU,kCAAmC,SAAAA,EAAE,2BAA2B,EAAE,EAC9EE,EAAO,IAAKG,GACXF,EAAAA,KAAC,QAAA,CAAkB,UAAU,uCAC3B,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACC,KAAK,WACL,QAASP,EAAS,SAASQ,CAAK,EAChC,SAAWC,GACTP,EACEO,EAAM,OAAO,QACT,CAAC,GAAGT,EAAUQ,CAAK,EACnBR,EAAS,OAAQU,GAASA,IAASF,CAAK,CAAA,CAC9C,CAAA,EAGHA,CAAA,CAAA,EAZSA,CAaZ,CACD,CAAA,EACH,CAEJ"}
@@ -0,0 +1,2 @@
1
+ import{r as h,j as e,L as S}from"./framework-vendor-D0S_ys6r.js";import{h as E,i as A,j as U,l as F,a as z}from"./index-CGRjM_NC.js";import{u as N}from"./i18n-vendor-COTriSc6.js";import{v as k,m as C}from"./passwordValidation-DTLWgUy9.js";import{D}from"./DeleteDialog-BoMYLx3J.js";import{a as R}from"./StatusDot-CLxRrJ5l.js";import{R as G,P as L,l as I,X as T,U as $,K as q,w as H,T as B}from"./icons-vendor-DZjjGrOu.js";const v=()=>{const{t}=N(),[w,a]=h.useState([]),[n,j]=h.useState(!0),[p,o]=h.useState(null),[f,u]=h.useState(0),m=h.useCallback(async()=>{try{j(!0);const r=await E("/users");if(!r.success){o(r.message||t("users.fetchError"));return}r&&r.success&&Array.isArray(r.data)?a(r.data):(console.error("Invalid user data format:",r),a([])),o(null)}catch(r){console.error("Error fetching users:",r),o(r instanceof Error?r.message:"Failed to fetch users"),a([])}finally{j(!1)}},[]),c=h.useCallback(()=>{u(r=>r+1)},[]),i=async r=>{try{const s=await F("/users",r);return c(),s}catch(s){return o(s instanceof Error?s.message:"Failed to create user"),null}},g=async(r,s)=>{try{const x=await U(`/users/${r}`,s);return c(),x||null}catch(x){return o(x instanceof Error?x.message:"Failed to update user"),null}},b=async r=>{try{const s=await A(`/users/${r}`);return s!=null&&s.success?(c(),s):(o((s==null?void 0:s.message)||t("users.deleteError")),s)}catch(s){return o(s instanceof Error?s.message:"Failed to delete user"),!1}};return h.useEffect(()=>{m()},[m,f]),{users:w,loading:n,error:p,setError:o,triggerRefresh:c,createUser:i,updateUser:g,deleteUser:b}},K=({onAdd:t,onCancel:w})=>{const{t:a}=N(),{createUser:n}=v(),[j,p]=h.useState(null),[o,f]=h.useState([]),[u,m]=h.useState(!1),[c,i]=h.useState({username:"",password:"",isAdmin:!1,email:""}),g=async r=>{var x;if(r.preventDefault(),p(null),f([]),!c.username.trim()){p(a("users.usernameRequired"));return}if(!c.password.trim()){p(a("users.passwordRequired"));return}const s=k(c.password);if(!s.isValid){p(a("auth.passwordStrengthError")),f(s.errors.map(l=>a(`auth.${l}`)));return}m(!0);try{const l=await n(c);l!=null&&l.success?t():(x=l==null?void 0:l.errors)!=null&&x.length?(p(l.message||a("auth.passwordStrengthError")),f(C(l.errors).map(d=>a(d)))):p((l==null?void 0:l.message)||a("users.createError"))}catch(l){p(l instanceof Error?l.message:a("users.createError"))}finally{m(!1)}},b=r=>{const{name:s,value:x,type:l,checked:d}=r.target;i(y=>({...y,[s]:l==="checkbox"?d:x}))};return e.jsx("div",{className:"fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4",children:e.jsx("div",{className:"bg-white dark:bg-gray-800 p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100 dark:border-gray-700",children:e.jsxs("form",{onSubmit:g,children:[e.jsx("h2",{className:"text-xl font-bold text-gray-900 mb-6",children:a("users.addNew")}),j&&e.jsxs("div",{className:"bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md",children:[e.jsx("p",{className:"text-sm font-medium",children:j}),o.length>0&&e.jsx("ul",{className:"list-disc list-inside mt-2 space-y-1 text-sm",children:o.map(r=>e.jsx("li",{children:r},r))})]}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsxs("label",{htmlFor:"username",className:"block text-sm font-medium text-gray-700 mb-1",children:[a("users.username")," ",e.jsx("span",{className:"text-red-500",children:"*"})]}),e.jsx("input",{type:"text",id:"username",name:"username",value:c.username,onChange:b,placeholder:a("users.usernamePlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",required:!0,disabled:u})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"email",className:"block text-sm font-medium text-gray-700 mb-1",children:a("users.email")}),e.jsx("input",{type:"email",id:"email",name:"email",value:c.email||"",onChange:b,placeholder:a("users.emailPlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",disabled:u})]}),e.jsxs("div",{children:[e.jsxs("label",{htmlFor:"password",className:"block text-sm font-medium text-gray-700 mb-1",children:[a("users.password")," ",e.jsx("span",{className:"text-red-500",children:"*"})]}),e.jsx("input",{type:"password",id:"password",name:"password",value:c.password,onChange:b,placeholder:a("users.passwordPlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",required:!0,disabled:u,minLength:8}),e.jsx("p",{className:"mt-1 text-xs text-gray-500",children:a("auth.passwordStrengthHint")})]}),e.jsxs("div",{className:"flex items-center pt-2",children:[e.jsx("input",{type:"checkbox",id:"isAdmin",name:"isAdmin",checked:c.isAdmin,onChange:b,className:"h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200",disabled:u}),e.jsx("label",{htmlFor:"isAdmin",className:"ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none",children:a("users.adminRole")})]})]}),e.jsxs("div",{className:"flex justify-end space-x-2 mt-6",children:[e.jsx("button",{type:"button",onClick:w,className:"hub-btn",disabled:u,children:a("common.cancel")}),e.jsxs("button",{type:"submit",className:"hub-btn primary",disabled:u,children:[u&&e.jsxs("svg",{className:"animate-spin -ml-1 mr-2 h-4 w-4 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),a(u?"common.creating":"users.create")]})]})]})})})},V=({user:t,onEdit:w,onCancel:a})=>{const{t:n}=N(),{updateUser:j}=v(),[p,o]=h.useState(null),[f,u]=h.useState([]),[m,c]=h.useState(!1),[i,g]=h.useState({isAdmin:t.isAdmin,email:t.email||"",userGroups:(t.userGroups||[]).join(", "),newPassword:"",confirmPassword:""}),b=async s=>{var x;if(s.preventDefault(),o(null),u([]),i.newPassword&&i.newPassword!==i.confirmPassword){o(n("users.passwordMismatch"));return}if(i.newPassword){const l=k(i.newPassword);if(!l.isValid){o(n("auth.passwordStrengthError")),u(l.errors.map(d=>n(`auth.${d}`)));return}}c(!0);try{const l={isAdmin:i.isAdmin,email:i.email,userGroups:[...new Set(i.userGroups.split(",").map(y=>y.trim()).filter(Boolean))]};i.newPassword&&(l.newPassword=i.newPassword);const d=await j(t.username,l);d!=null&&d.success?w():(x=d==null?void 0:d.errors)!=null&&x.length?(o(d.message||n("auth.passwordStrengthError")),u(C(d.errors).map(y=>n(y)))):o((d==null?void 0:d.message)||n("users.updateError"))}catch(l){o(l instanceof Error?l.message:n("users.updateError"))}finally{c(!1)}},r=s=>{const{name:x,value:l,type:d,checked:y}=s.target;g(P=>({...P,[x]:d==="checkbox"?y:l}))};return e.jsx("div",{className:"fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4",children:e.jsx("div",{className:"bg-white dark:bg-gray-800 p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100 dark:border-gray-700",children:e.jsxs("form",{onSubmit:b,children:[e.jsxs("h2",{className:"text-xl font-bold text-gray-900 mb-6",children:[n("users.edit")," - ",e.jsx("span",{className:"text-blue-600",children:t.username})]}),p&&e.jsxs("div",{className:"bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md",children:[e.jsx("p",{className:"text-sm font-medium",children:p}),f.length>0&&e.jsx("ul",{className:"list-disc list-inside mt-2 space-y-1 text-sm",children:f.map(s=>e.jsx("li",{children:s},s))})]}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{className:"flex items-center pt-2",children:[e.jsx("input",{type:"checkbox",id:"isAdmin",name:"isAdmin",checked:i.isAdmin,onChange:r,className:"h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200",disabled:m}),e.jsx("label",{htmlFor:"isAdmin",className:"ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none",children:n("users.adminRole")})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"email",className:"block text-sm font-medium text-gray-700 mb-1",children:n("users.email")}),e.jsx("input",{type:"email",id:"email",name:"email",value:i.email,onChange:r,placeholder:n("users.emailPlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",disabled:m})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"userGroups",className:"block text-sm font-medium text-gray-700 mb-1",children:n("users.userGroups")}),e.jsx("input",{id:"userGroups",name:"userGroups",value:i.userGroups,onChange:r,disabled:m,className:"hub-input"}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n("users.userGroupsHint")})]}),e.jsxs("div",{className:"border-t border-gray-100 dark:border-gray-700 pt-4 mt-2",children:[e.jsx("p",{className:"text-xs text-gray-500 uppercase font-semibold tracking-wider mb-3",children:n("users.changePassword")}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"newPassword",className:"block text-sm font-medium text-gray-700 mb-1",children:n("users.newPassword")}),e.jsx("input",{type:"password",id:"newPassword",name:"newPassword",value:i.newPassword,onChange:r,placeholder:n("users.newPasswordPlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",disabled:m,minLength:8}),e.jsx("p",{className:"mt-1 text-xs text-gray-500",children:n("auth.passwordStrengthHint")})]}),i.newPassword&&e.jsxs("div",{className:"animate-fadeIn",children:[e.jsx("label",{htmlFor:"confirmPassword",className:"block text-sm font-medium text-gray-700 mb-1",children:n("users.confirmPassword")}),e.jsx("input",{type:"password",id:"confirmPassword",name:"confirmPassword",value:i.confirmPassword,onChange:r,placeholder:n("users.confirmPasswordPlaceholder"),className:"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200",disabled:m,minLength:8})]})]})]})]}),e.jsxs("div",{className:"flex justify-end space-x-2 mt-6",children:[e.jsx("button",{type:"button",onClick:a,className:"hub-btn",disabled:m,children:n("common.cancel")}),e.jsxs("button",{type:"submit",className:"hub-btn primary",disabled:m,children:[m&&e.jsxs("svg",{className:"animate-spin -ml-1 mr-2 h-4 w-4 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[e.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),e.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),n(m?"common.updating":"users.update")]})]})]})})})},Z=()=>{const{t}=N(),{auth:w}=z(),a=w.user,{users:n,loading:j,error:p,setError:o,deleteUser:f,triggerRefresh:u}=v(),[m,c]=h.useState(null),[i,g]=h.useState(!1),[b,r]=h.useState(null);return a!=null&&a.isAdmin?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-end justify-between gap-4 mb-6",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"hub-h1",children:t("pages.users.title")}),e.jsxs("p",{className:"hub-sub",children:[e.jsx("span",{className:"hub-num",children:n.length})," ",t("nav.users").toLowerCase()]})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsxs("button",{className:"hub-btn",onClick:()=>u(),"aria-label":t("common.refresh"),children:[e.jsx(G,{size:13})," ",t("common.refresh")]}),e.jsxs("button",{className:"hub-btn primary",onClick:()=>g(!0),children:[e.jsx(L,{size:13})," ",t("users.add")]})]})]}),p&&e.jsxs("div",{className:"hub-card flex items-center justify-between gap-3 mb-4",style:{padding:"10px 14px",borderColor:"oklch(0.85 0.1 25)",background:"oklch(0.97 0.03 25)",color:"oklch(0.4 0.18 25)"},children:[e.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[e.jsx(I,{size:14,className:"flex-shrink-0"}),e.jsx("span",{className:"truncate text-[13px]",children:p})]}),e.jsx("button",{className:"hub-icon-btn sm",onClick:()=>o(null),children:e.jsx(T,{size:13})})]}),j?e.jsx("div",{className:"hub-card p-10 text-center",style:{color:"var(--hub-ink-3)"},children:t("app.loading")}):n.length===0?e.jsx("div",{className:"hub-card p-10 text-center",style:{color:"var(--hub-ink-3)"},children:e.jsxs("div",{className:"flex flex-col items-center gap-3",children:[e.jsx("div",{className:"grid place-items-center",style:{width:40,height:40,borderRadius:10,border:"1px solid var(--hub-line)",background:"var(--hub-bg-2)"},children:e.jsx($,{size:18})}),e.jsx("div",{className:"font-medium",style:{color:"var(--hub-ink-2)",fontSize:13},children:t("users.noUsers")}),e.jsx("button",{onClick:()=>g(!0),className:"hub-btn ghost sm",style:{color:"var(--hub-accent)"},children:t("users.addFirst")})]})}):e.jsxs("div",{className:"hub-card overflow-hidden",children:[e.jsxs("div",{className:"hub-row head hub-mono",style:{gridTemplateColumns:"1.6fr 1.2fr 100px 100px"},children:[e.jsx("div",{children:t("users.username")}),e.jsx("div",{children:t("users.email")}),e.jsx("div",{children:t("users.role")}),e.jsx("div",{className:"text-right",children:t("users.actions")})]}),n.map(s=>{const x=(a==null?void 0:a.username)===s.username;return e.jsxs("div",{className:"hub-row hover",style:{gridTemplateColumns:"1.6fr 1.2fr 100px 100px"},children:[e.jsxs("div",{className:"flex items-center gap-3 min-w-0",children:[e.jsx("div",{className:"grid place-items-center flex-shrink-0 hub-mono",style:{width:28,height:28,borderRadius:7,background:"var(--hub-bg-2)",border:"1px solid var(--hub-line)",color:"var(--hub-ink-2)",fontWeight:600,fontSize:12},children:s.username.charAt(0).toUpperCase()}),e.jsx("span",{className:"hub-mono truncate",style:{fontSize:13,color:"var(--hub-ink)"},children:s.username}),x&&e.jsx("span",{className:"hub-tag accent",style:{fontSize:10},children:t("users.currentUser")})]}),e.jsx("div",{className:"flex items-center min-w-0",children:e.jsx("span",{className:"truncate",style:{fontSize:13,color:s.email?"var(--hub-ink)":"var(--hub-ink-3)"},children:s.email||"—"})}),e.jsx("div",{children:e.jsx(R,{kind:s.isAdmin?"ok":"muted",label:s.isAdmin?t("users.admin"):t("users.user")})}),e.jsxs("div",{className:"flex justify-end gap-1",children:[e.jsx(S,{to:`/credentials?user=${encodeURIComponent(s.username)}`,className:"hub-icon-btn sm",title:t("credentials.title"),"aria-label":`${t("credentials.title")}: ${s.username}`,children:e.jsx(q,{size:13})}),e.jsx("button",{onClick:()=>c(s),className:"hub-icon-btn sm",title:t("users.edit"),children:e.jsx(H,{size:13})}),!x&&e.jsx("button",{onClick:()=>r(s.username),className:"hub-icon-btn sm",title:t("users.delete"),style:{color:"var(--hub-err)"},children:e.jsx(B,{size:13})})]})]},s.username)})]}),i&&e.jsx(K,{onAdd:()=>{g(!1),u()},onCancel:()=>g(!1)}),m&&e.jsx(V,{user:m,onEdit:()=>{c(null),u()},onCancel:()=>c(null)}),e.jsx(D,{isOpen:!!b,onClose:()=>r(null),onConfirm:async()=>{if(b){const s=await f(b);s!=null&&s.success||o((s==null?void 0:s.message)||t("users.deleteError")),r(null)}},serverName:b||"",isGroup:!1,isUser:!0})]}):e.jsx("div",{className:"hub-card p-6 text-center",style:{color:"var(--hub-err)"},children:t("users.adminRequired")})};export{Z as default};
2
+ //# sourceMappingURL=UsersPage-B158Bofs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsersPage-B158Bofs.js","sources":["../../src/hooks/useUserData.ts","../../src/components/AddUserForm.tsx","../../src/components/EditUserForm.tsx","../../src/pages/UsersPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { User, ApiResponse, UserFormData, UserUpdateData } from '@/types';\nimport { apiDelete, apiGet, apiPost, apiPut } from '../utils/fetchInterceptor';\n\nexport const useUserData = () => {\n const { t } = useTranslation();\n const [users, setUsers] = useState<User[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [refreshKey, setRefreshKey] = useState(0);\n\n const fetchUsers = useCallback(async () => {\n try {\n setLoading(true);\n const data: ApiResponse<User[]> = await apiGet('/users');\n if (!data.success) {\n setError(data.message || t('users.fetchError'));\n return;\n }\n\n if (data && data.success && Array.isArray(data.data)) {\n setUsers(data.data);\n } else {\n console.error('Invalid user data format:', data);\n setUsers([]);\n }\n\n setError(null);\n } catch (err) {\n console.error('Error fetching users:', err);\n setError(err instanceof Error ? err.message : 'Failed to fetch users');\n setUsers([]);\n } finally {\n setLoading(false);\n }\n }, []);\n\n // Trigger a refresh of the users data\n const triggerRefresh = useCallback(() => {\n setRefreshKey((prev) => prev + 1);\n }, []);\n\n // Create a new user\n const createUser = async (userData: UserFormData) => {\n try {\n const result: ApiResponse<User> = await apiPost('/users', userData);\n triggerRefresh();\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create user');\n return null;\n }\n };\n\n // Update an existing user\n const updateUser = async (username: string, data: UserUpdateData) => {\n try {\n const result: ApiResponse<User> = await apiPut(`/users/${username}`, data);\n triggerRefresh();\n return result || null;\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update user');\n return null;\n }\n };\n\n // Delete a user\n const deleteUser = async (username: string) => {\n try {\n const result = await apiDelete(`/users/${username}`);\n if (!result?.success) {\n setError(result?.message || t('users.deleteError'));\n return result;\n }\n\n triggerRefresh();\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to delete user');\n return false;\n }\n };\n\n // Fetch users when the component mounts or refreshKey changes\n useEffect(() => {\n fetchUsers();\n }, [fetchUsers, refreshKey]);\n\n return {\n users,\n loading,\n error,\n setError,\n triggerRefresh,\n createUser,\n updateUser,\n deleteUser,\n };\n};\n","import { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useUserData } from '@/hooks/useUserData';\nimport { UserFormData } from '@/types';\nimport {\n validatePasswordStrength,\n mapBackendPasswordErrors,\n} from '../utils/passwordValidation';\n\ninterface AddUserFormProps {\n onAdd: () => void;\n onCancel: () => void;\n}\n\nconst AddUserForm = ({ onAdd, onCancel }: AddUserFormProps) => {\n const { t } = useTranslation();\n const { createUser } = useUserData();\n const [error, setError] = useState<string | null>(null);\n const [passwordErrors, setPasswordErrors] = useState<string[]>([]);\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const [formData, setFormData] = useState<UserFormData>({\n username: '',\n password: '',\n isAdmin: false,\n email: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setPasswordErrors([]);\n\n if (!formData.username.trim()) {\n setError(t('users.usernameRequired'));\n return;\n }\n\n if (!formData.password.trim()) {\n setError(t('users.passwordRequired'));\n return;\n }\n\n const validation = validatePasswordStrength(formData.password);\n if (!validation.isValid) {\n setError(t('auth.passwordStrengthError'));\n setPasswordErrors(validation.errors.map((key) => t(`auth.${key}`)));\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n const result = await createUser(formData);\n if (result?.success) {\n onAdd();\n } else if (result?.errors?.length) {\n setError(result.message || t('auth.passwordStrengthError'));\n setPasswordErrors(mapBackendPasswordErrors(result.errors).map((key) => t(key)));\n } else {\n setError(result?.message || t('users.createError'));\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : t('users.createError'));\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const { name, value, type, checked } = e.target;\n setFormData((prev) => ({\n ...prev,\n [name]: type === 'checkbox' ? checked : value,\n }));\n };\n\n return (\n <div className=\"fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4\">\n <div className=\"bg-white dark:bg-gray-800 p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100 dark:border-gray-700\">\n <form onSubmit={handleSubmit}>\n <h2 className=\"text-xl font-bold text-gray-900 mb-6\">{t('users.addNew')}</h2>\n\n {error && (\n <div className=\"bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md\">\n <p className=\"text-sm font-medium\">{error}</p>\n {passwordErrors.length > 0 && (\n <ul className=\"list-disc list-inside mt-2 space-y-1 text-sm\">\n {passwordErrors.map((message) => (\n <li key={message}>{message}</li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n <div className=\"space-y-5\">\n <div>\n <label htmlFor=\"username\" className=\"block text-sm font-medium text-gray-700 mb-1\">\n {t('users.username')} <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"text\"\n id=\"username\"\n name=\"username\"\n value={formData.username}\n onChange={handleInputChange}\n placeholder={t('users.usernamePlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n required\n disabled={isSubmitting}\n />\n </div>\n\n <div>\n <label htmlFor=\"email\" className=\"block text-sm font-medium text-gray-700 mb-1\">\n {t('users.email')}\n </label>\n <input\n type=\"email\"\n id=\"email\"\n name=\"email\"\n value={formData.email || ''}\n onChange={handleInputChange}\n placeholder={t('users.emailPlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n disabled={isSubmitting}\n />\n </div>\n\n <div>\n <label htmlFor=\"password\" className=\"block text-sm font-medium text-gray-700 mb-1\">\n {t('users.password')} <span className=\"text-red-500\">*</span>\n </label>\n <input\n type=\"password\"\n id=\"password\"\n name=\"password\"\n value={formData.password}\n onChange={handleInputChange}\n placeholder={t('users.passwordPlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n required\n disabled={isSubmitting}\n minLength={8}\n />\n <p className=\"mt-1 text-xs text-gray-500\">\n {t('auth.passwordStrengthHint')}\n </p>\n </div>\n\n <div className=\"flex items-center pt-2\">\n <input\n type=\"checkbox\"\n id=\"isAdmin\"\n name=\"isAdmin\"\n checked={formData.isAdmin}\n onChange={handleInputChange}\n className=\"h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200\"\n disabled={isSubmitting}\n />\n <label\n htmlFor=\"isAdmin\"\n className=\"ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none\"\n >\n {t('users.adminRole')}\n </label>\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-2 mt-6\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"hub-btn\"\n disabled={isSubmitting}\n >\n {t('common.cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"hub-btn primary\"\n disabled={isSubmitting}\n >\n {isSubmitting && (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4 text-white\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n )}\n {isSubmitting ? t('common.creating') : t('users.create')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\nexport default AddUserForm;\n","import { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useUserData } from '@/hooks/useUserData';\nimport { User, UserUpdateData } from '@/types';\nimport {\n validatePasswordStrength,\n mapBackendPasswordErrors,\n} from '../utils/passwordValidation';\n\ninterface EditUserFormProps {\n user: User;\n onEdit: () => void;\n onCancel: () => void;\n}\n\nconst EditUserForm = ({ user, onEdit, onCancel }: EditUserFormProps) => {\n const { t } = useTranslation();\n const { updateUser } = useUserData();\n const [error, setError] = useState<string | null>(null);\n const [passwordErrors, setPasswordErrors] = useState<string[]>([]);\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const [formData, setFormData] = useState({\n isAdmin: user.isAdmin,\n email: user.email || '',\n userGroups: (user.userGroups || []).join(', '),\n newPassword: '',\n confirmPassword: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n setPasswordErrors([]);\n\n // Validate passwords match if changing password\n if (formData.newPassword && formData.newPassword !== formData.confirmPassword) {\n setError(t('users.passwordMismatch'));\n return;\n }\n\n if (formData.newPassword) {\n const validation = validatePasswordStrength(formData.newPassword);\n if (!validation.isValid) {\n setError(t('auth.passwordStrengthError'));\n setPasswordErrors(validation.errors.map((key) => t(`auth.${key}`)));\n return;\n }\n }\n\n setIsSubmitting(true);\n\n try {\n const updateData: UserUpdateData = {\n isAdmin: formData.isAdmin,\n email: formData.email,\n userGroups: [\n ...new Set(\n formData.userGroups\n .split(',')\n .map((name) => name.trim())\n .filter(Boolean),\n ),\n ],\n };\n\n if (formData.newPassword) {\n updateData.newPassword = formData.newPassword;\n }\n\n const result = await updateUser(user.username, updateData);\n if (result?.success) {\n onEdit();\n } else if (result?.errors?.length) {\n setError(result.message || t('auth.passwordStrengthError'));\n setPasswordErrors(mapBackendPasswordErrors(result.errors).map((key) => t(key)));\n } else {\n setError(result?.message || t('users.updateError'));\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : t('users.updateError'));\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const { name, value, type, checked } = e.target;\n setFormData((prev) => ({\n ...prev,\n [name]: type === 'checkbox' ? checked : value,\n }));\n };\n\n return (\n <div className=\"fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4\">\n <div className=\"bg-white dark:bg-gray-800 p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100 dark:border-gray-700\">\n <form onSubmit={handleSubmit}>\n <h2 className=\"text-xl font-bold text-gray-900 mb-6\">\n {t('users.edit')} - <span className=\"text-blue-600\">{user.username}</span>\n </h2>\n\n {error && (\n <div className=\"bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md\">\n <p className=\"text-sm font-medium\">{error}</p>\n {passwordErrors.length > 0 && (\n <ul className=\"list-disc list-inside mt-2 space-y-1 text-sm\">\n {passwordErrors.map((message) => (\n <li key={message}>{message}</li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n <div className=\"space-y-5\">\n <div className=\"flex items-center pt-2\">\n <input\n type=\"checkbox\"\n id=\"isAdmin\"\n name=\"isAdmin\"\n checked={formData.isAdmin}\n onChange={handleInputChange}\n className=\"h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200\"\n disabled={isSubmitting}\n />\n <label\n htmlFor=\"isAdmin\"\n className=\"ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none\"\n >\n {t('users.adminRole')}\n </label>\n </div>\n\n <div>\n <label htmlFor=\"email\" className=\"block text-sm font-medium text-gray-700 mb-1\">\n {t('users.email')}\n </label>\n <input\n type=\"email\"\n id=\"email\"\n name=\"email\"\n value={formData.email}\n onChange={handleInputChange}\n placeholder={t('users.emailPlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n disabled={isSubmitting}\n />\n </div>\n\n <div>\n <label htmlFor=\"userGroups\" className=\"block text-sm font-medium text-gray-700 mb-1\">\n {t('users.userGroups')}\n </label>\n <input\n id=\"userGroups\"\n name=\"userGroups\"\n value={formData.userGroups}\n onChange={handleInputChange}\n disabled={isSubmitting}\n className=\"hub-input\"\n />\n <p className=\"text-xs text-gray-500 mt-1\">{t('users.userGroupsHint')}</p>\n </div>\n\n <div className=\"border-t border-gray-100 dark:border-gray-700 pt-4 mt-2\">\n <p className=\"text-xs text-gray-500 uppercase font-semibold tracking-wider mb-3\">\n {t('users.changePassword')}\n </p>\n\n <div className=\"space-y-4\">\n <div>\n <label\n htmlFor=\"newPassword\"\n className=\"block text-sm font-medium text-gray-700 mb-1\"\n >\n {t('users.newPassword')}\n </label>\n <input\n type=\"password\"\n id=\"newPassword\"\n name=\"newPassword\"\n value={formData.newPassword}\n onChange={handleInputChange}\n placeholder={t('users.newPasswordPlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n disabled={isSubmitting}\n minLength={8}\n />\n <p className=\"mt-1 text-xs text-gray-500\">\n {t('auth.passwordStrengthHint')}\n </p>\n </div>\n\n {formData.newPassword && (\n <div className=\"animate-fadeIn\">\n <label\n htmlFor=\"confirmPassword\"\n className=\"block text-sm font-medium text-gray-700 mb-1\"\n >\n {t('users.confirmPassword')}\n </label>\n <input\n type=\"password\"\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n value={formData.confirmPassword}\n onChange={handleInputChange}\n placeholder={t('users.confirmPasswordPlaceholder')}\n className=\"w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200\"\n disabled={isSubmitting}\n minLength={8}\n />\n </div>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-2 mt-6\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"hub-btn\"\n disabled={isSubmitting}\n >\n {t('common.cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"hub-btn primary\"\n disabled={isSubmitting}\n >\n {isSubmitting && (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4 text-white\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n )}\n {isSubmitting ? t('common.updating') : t('users.update')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\nexport default EditUserForm;\n","import { Link } from 'react-router-dom';\nimport React, { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { User } from '@/types';\nimport { useUserData } from '@/hooks/useUserData';\nimport { useAuth } from '@/contexts/AuthContext';\nimport AddUserForm from '@/components/AddUserForm';\nimport EditUserForm from '@/components/EditUserForm';\nimport {\n KeyRound,\n Edit3,\n Trash2,\n User as UserIcon,\n Plus,\n AlertCircle,\n X,\n RefreshCw,\n} from 'lucide-react';\nimport DeleteDialog from '@/components/ui/DeleteDialog';\nimport { StatusDot } from '@/components/ui/StatusDot';\n\nconst UsersPage: React.FC = () => {\n const { t } = useTranslation();\n const { auth } = useAuth();\n const currentUser = auth.user;\n const {\n users,\n loading: usersLoading,\n error: userError,\n setError: setUserError,\n deleteUser,\n triggerRefresh,\n } = useUserData();\n\n const [editingUser, setEditingUser] = useState<User | null>(null);\n const [showAddForm, setShowAddForm] = useState(false);\n const [userToDelete, setUserToDelete] = useState<string | null>(null);\n\n if (!currentUser?.isAdmin) {\n return (\n <div className=\"hub-card p-6 text-center\" style={{ color: 'var(--hub-err)' }}>\n {t('users.adminRequired')}\n </div>\n );\n }\n\n return (\n <div>\n <div className=\"flex items-end justify-between gap-4 mb-6\">\n <div>\n <h1 className=\"hub-h1\">{t('pages.users.title')}</h1>\n <p className=\"hub-sub\">\n <span className=\"hub-num\">{users.length}</span> {t('nav.users').toLowerCase()}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <button\n className=\"hub-btn\"\n onClick={() => triggerRefresh()}\n aria-label={t('common.refresh')}\n >\n <RefreshCw size={13} /> {t('common.refresh')}\n </button>\n <button className=\"hub-btn primary\" onClick={() => setShowAddForm(true)}>\n <Plus size={13} /> {t('users.add')}\n </button>\n </div>\n </div>\n\n {userError && (\n <div\n className=\"hub-card flex items-center justify-between gap-3 mb-4\"\n style={{\n padding: '10px 14px',\n borderColor: 'oklch(0.85 0.1 25)',\n background: 'oklch(0.97 0.03 25)',\n color: 'oklch(0.4 0.18 25)',\n }}\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <AlertCircle size={14} className=\"flex-shrink-0\" />\n <span className=\"truncate text-[13px]\">{userError}</span>\n </div>\n <button className=\"hub-icon-btn sm\" onClick={() => setUserError(null)}>\n <X size={13} />\n </button>\n </div>\n )}\n\n {usersLoading ? (\n <div className=\"hub-card p-10 text-center\" style={{ color: 'var(--hub-ink-3)' }}>\n {t('app.loading')}\n </div>\n ) : users.length === 0 ? (\n <div className=\"hub-card p-10 text-center\" style={{ color: 'var(--hub-ink-3)' }}>\n <div className=\"flex flex-col items-center gap-3\">\n <div\n className=\"grid place-items-center\"\n style={{\n width: 40,\n height: 40,\n borderRadius: 10,\n border: '1px solid var(--hub-line)',\n background: 'var(--hub-bg-2)',\n }}\n >\n <UserIcon size={18} />\n </div>\n <div className=\"font-medium\" style={{ color: 'var(--hub-ink-2)', fontSize: 13 }}>\n {t('users.noUsers')}\n </div>\n <button\n onClick={() => setShowAddForm(true)}\n className=\"hub-btn ghost sm\"\n style={{ color: 'var(--hub-accent)' }}\n >\n {t('users.addFirst')}\n </button>\n </div>\n </div>\n ) : (\n <div className=\"hub-card overflow-hidden\">\n <div\n className=\"hub-row head hub-mono\"\n style={{ gridTemplateColumns: '1.6fr 1.2fr 100px 100px' }}\n >\n <div>{t('users.username')}</div>\n <div>{t('users.email')}</div>\n <div>{t('users.role')}</div>\n <div className=\"text-right\">{t('users.actions')}</div>\n </div>\n {users.map((user) => {\n const isCurrentUser = currentUser?.username === user.username;\n return (\n <div\n key={user.username}\n className=\"hub-row hover\"\n style={{ gridTemplateColumns: '1.6fr 1.2fr 100px 100px' }}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className=\"grid place-items-center flex-shrink-0 hub-mono\"\n style={{\n width: 28,\n height: 28,\n borderRadius: 7,\n background: 'var(--hub-bg-2)',\n border: '1px solid var(--hub-line)',\n color: 'var(--hub-ink-2)',\n fontWeight: 600,\n fontSize: 12,\n }}\n >\n {user.username.charAt(0).toUpperCase()}\n </div>\n <span\n className=\"hub-mono truncate\"\n style={{ fontSize: 13, color: 'var(--hub-ink)' }}\n >\n {user.username}\n </span>\n {isCurrentUser && (\n <span className=\"hub-tag accent\" style={{ fontSize: 10 }}>\n {t('users.currentUser')}\n </span>\n )}\n </div>\n <div className=\"flex items-center min-w-0\">\n <span\n className=\"truncate\"\n style={{\n fontSize: 13,\n color: user.email ? 'var(--hub-ink)' : 'var(--hub-ink-3)',\n }}\n >\n {user.email || '—'}\n </span>\n </div>\n <div>\n <StatusDot\n kind={user.isAdmin ? 'ok' : 'muted'}\n label={user.isAdmin ? t('users.admin') : t('users.user')}\n />\n </div>\n <div className=\"flex justify-end gap-1\">\n <Link\n to={`/credentials?user=${encodeURIComponent(user.username)}`}\n className=\"hub-icon-btn sm\"\n title={t('credentials.title')}\n aria-label={`${t('credentials.title')}: ${user.username}`}\n >\n <KeyRound size={13} />\n </Link>\n <button\n onClick={() => setEditingUser(user)}\n className=\"hub-icon-btn sm\"\n title={t('users.edit')}\n >\n <Edit3 size={13} />\n </button>\n {!isCurrentUser && (\n <button\n onClick={() => setUserToDelete(user.username)}\n className=\"hub-icon-btn sm\"\n title={t('users.delete')}\n style={{ color: 'var(--hub-err)' }}\n >\n <Trash2 size={13} />\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {showAddForm && (\n <AddUserForm\n onAdd={() => {\n setShowAddForm(false);\n triggerRefresh();\n }}\n onCancel={() => setShowAddForm(false)}\n />\n )}\n\n {editingUser && (\n <EditUserForm\n user={editingUser}\n onEdit={() => {\n setEditingUser(null);\n triggerRefresh();\n }}\n onCancel={() => setEditingUser(null)}\n />\n )}\n\n <DeleteDialog\n isOpen={!!userToDelete}\n onClose={() => setUserToDelete(null)}\n onConfirm={async () => {\n if (userToDelete) {\n const result = await deleteUser(userToDelete);\n if (!result?.success) {\n setUserError(result?.message || t('users.deleteError'));\n }\n setUserToDelete(null);\n }\n }}\n serverName={userToDelete || ''}\n isGroup={false}\n isUser={true}\n />\n </div>\n );\n};\n\nexport default UsersPage;\n"],"names":["useUserData","useTranslation","users","setUsers","useState","loading","setLoading","error","setError","refreshKey","setRefreshKey","fetchUsers","useCallback","data","apiGet","err","triggerRefresh","prev","createUser","userData","result","apiPost","updateUser","username","apiPut","deleteUser","apiDelete","useEffect","AddUserForm","onAdd","onCancel","t","passwordErrors","setPasswordErrors","isSubmitting","setIsSubmitting","formData","setFormData","handleSubmit","e","validation","validatePasswordStrength","key","_a","mapBackendPasswordErrors","handleInputChange","name","value","type","checked","jsx","jsxs","message","EditUserForm","user","onEdit","updateData","UsersPage","auth","useAuth","currentUser","usersLoading","userError","setUserError","editingUser","setEditingUser","showAddForm","setShowAddForm","userToDelete","setUserToDelete","RefreshCw","Plus","AlertCircle","X","UserIcon","isCurrentUser","StatusDot","Link","KeyRound","Edit3","Trash2","DeleteDialog"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{w as c}from"./index-CkSmxNPW.js";const r=()=>c(),a=async e=>{try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.left="-9999px",document.body.appendChild(t),t.focus(),t.select();const o=document.execCommand("copy");return document.body.removeChild(t),o}catch{return!1}};export{a as c,r as u};
2
- //# sourceMappingURL=clipboard-2RRFM-q8.js.map
1
+ import{w as c}from"./index-CGRjM_NC.js";const r=()=>c(),a=async e=>{try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.left="-9999px",document.body.appendChild(t),t.focus(),t.select();const o=document.execCommand("copy");return document.body.removeChild(t),o}catch{return!1}};export{a as c,r as u};
2
+ //# sourceMappingURL=clipboard-BLr_gbf8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard-2RRFM-q8.js","sources":["../../src/hooks/useSettingsData.ts","../../src/utils/clipboard.ts"],"sourcesContent":["import { useSettings } from '@/contexts/SettingsContext';\n\n/**\n * Hook that provides access to settings data via SettingsContext.\n * This hook is a thin wrapper around useSettings to maintain backward compatibility.\n * The actual data fetching happens once in SettingsProvider, avoiding duplicate API calls.\n */\nexport const useSettingsData = () => {\n return useSettings();\n};\n","/**\n * Clipboard helper shared by the copy actions. Falls back to a hidden textarea\n * when the async Clipboard API is unavailable (non-secure contexts).\n */\nexport const copyText = async (value: string): Promise<boolean> => {\n try {\n if (navigator.clipboard && window.isSecureContext) {\n await navigator.clipboard.writeText(value);\n return true;\n }\n } catch {\n /* fall through to the textarea fallback */\n }\n\n try {\n const el = document.createElement('textarea');\n el.value = value;\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.focus();\n el.select();\n const ok = document.execCommand('copy');\n document.body.removeChild(el);\n return ok;\n } catch {\n return false;\n }\n};\n"],"names":["useSettingsData","useSettings","copyText","value","el","ok"],"mappings":"wCAOO,MAAMA,EAAkB,IACtBC,EAAA,ECJIC,EAAW,MAAOC,GAAoC,CACjE,GAAI,CACF,GAAI,UAAU,WAAa,OAAO,gBAChC,aAAM,UAAU,UAAU,UAAUA,CAAK,EAClC,EAEX,MAAQ,CAER,CAEA,GAAI,CACF,MAAMC,EAAK,SAAS,cAAc,UAAU,EAC5CA,EAAG,MAAQD,EACXC,EAAG,MAAM,SAAW,QACpBA,EAAG,MAAM,KAAO,UAChB,SAAS,KAAK,YAAYA,CAAE,EAC5BA,EAAG,MAAA,EACHA,EAAG,OAAA,EACH,MAAMC,EAAK,SAAS,YAAY,MAAM,EACtC,gBAAS,KAAK,YAAYD,CAAE,EACrBC,CACT,MAAQ,CACN,MAAO,EACT,CACF"}
1
+ {"version":3,"file":"clipboard-BLr_gbf8.js","sources":["../../src/hooks/useSettingsData.ts","../../src/utils/clipboard.ts"],"sourcesContent":["import { useSettings } from '@/contexts/SettingsContext';\n\n/**\n * Hook that provides access to settings data via SettingsContext.\n * This hook is a thin wrapper around useSettings to maintain backward compatibility.\n * The actual data fetching happens once in SettingsProvider, avoiding duplicate API calls.\n */\nexport const useSettingsData = () => {\n return useSettings();\n};\n","/**\n * Clipboard helper shared by the copy actions. Falls back to a hidden textarea\n * when the async Clipboard API is unavailable (non-secure contexts).\n */\nexport const copyText = async (value: string): Promise<boolean> => {\n try {\n if (navigator.clipboard && window.isSecureContext) {\n await navigator.clipboard.writeText(value);\n return true;\n }\n } catch {\n /* fall through to the textarea fallback */\n }\n\n try {\n const el = document.createElement('textarea');\n el.value = value;\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.focus();\n el.select();\n const ok = document.execCommand('copy');\n document.body.removeChild(el);\n return ok;\n } catch {\n return false;\n }\n};\n"],"names":["useSettingsData","useSettings","copyText","value","el","ok"],"mappings":"wCAOO,MAAMA,EAAkB,IACtBC,EAAA,ECJIC,EAAW,MAAOC,GAAoC,CACjE,GAAI,CACF,GAAI,UAAU,WAAa,OAAO,gBAChC,aAAM,UAAU,UAAU,UAAUA,CAAK,EAClC,EAEX,MAAQ,CAER,CAEA,GAAI,CACF,MAAMC,EAAK,SAAS,cAAc,UAAU,EAC5CA,EAAG,MAAQD,EACXC,EAAG,MAAM,SAAW,QACpBA,EAAG,MAAM,KAAO,UAChB,SAAS,KAAK,YAAYA,CAAE,EAC5BA,EAAG,MAAA,EACHA,EAAG,OAAA,EACH,MAAMC,EAAK,SAAS,YAAY,MAAM,EACtC,gBAAS,KAAK,YAAYD,CAAE,EACrBC,CACT,MAAQ,CACN,MAAO,EACT,CACF"}
@@ -1,4 +1,4 @@
1
- import{r as a,j as o}from"./framework-vendor-CQFm9vKd.js";import{c as P}from"./clipboard-2RRFM-q8.js";import{k as L}from"./index-CkSmxNPW.js";import{u as R}from"./i18n-vendor-DVmjrzgW.js";import{X as H,C as J,r as K}from"./icons-vendor-CyAS6fj0.js";const w=(e,n)=>e&&e.trim()?e:n,ce=(e,n)=>{const s=w(e.description,n);return e.hasDescriptionOverride?{currentDescription:s,defaultDescription:w(e.defaultDescription,n),hasDescriptionOverride:!0}:{currentDescription:s,hasDescriptionOverride:!1}},O="generic-mcp-servers",F=[{id:"generic-mcp-servers"},{id:"claude-code"},{id:"cursor"},{id:"vscode"},{id:"codex"},{id:"opencode"},{id:"windsurf"},{id:"cherry-studio"},{id:"codebuddy"},{id:"qoder"},{id:"trae"},{id:"zcode"},{id:"workbuddy"},{id:"generic-http"}],M="<your-access-token>",A="mcphub-token",u=e=>!!e.url,p=e=>{const n=Object.entries(e.headers??{}).filter(([,s])=>s!==void 0);return n.length>0?Object.fromEntries(n):void 0},C=e=>{const n=Object.entries(e.env??{}).filter(([,s])=>s!==void 0);return n.length>0?Object.fromEntries(n):void 0},f=e=>{var t;const n=((t=e.args)==null?void 0:t.filter(i=>i!==void 0))??[],s=C(e);return{command:e.command??"",...n.length>0?{args:n}:{},...s?{env:s}:{}}},_=(e,n=!1)=>{if(!u(e))return n?{type:"stdio",...f(e)}:f(e);const s=p(e);return{type:e.type==="sse"?"sse":"http",url:e.url??"",...s?{headers:s}:{}}},z=e=>{const n=p(e);return{...e.type==="sse"?{type:"sse"}:{},url:e.url??"",...n?{headers:n}:{}}},v=(e,n,s)=>JSON.stringify({[e]:{[n]:s}},null,2),D=e=>v("mcpServers",e.name,u(e)?z(e):f(e)),U=(e,n)=>{const s=p(e),t=`Bearer ${M}`,i=(s==null?void 0:s.Authorization)===t,c=i?{...s,Authorization:`Bearer \${input:${A}}`}:s,d=u(e)?{type:e.type==="sse"?"sse":"http",url:e.url,...c?{headers:c}:{}}:{type:"stdio",...f(e)};return JSON.stringify({servers:{[e.name]:d},...i?{inputs:[{id:A,type:"promptString",description:n.tokenPromptDescription??"MCPHub access token",password:!0}]}:{}},null,2)},W=/^[A-Za-z0-9_-]+$/,N=e=>W.test(e)?e:JSON.stringify(e),h=e=>JSON.stringify(e),Z=e=>`{ ${Object.entries(e).map(([n,s])=>`${N(n)} = ${h(s)}`).join(", ")} }`,q=e=>{var c;const n=N(e.name),s=[`[mcp_servers.${n}]`];if(u(e)){s.push(`url = ${h(e.url??"")}`);const d=p(e);return d&&s.push(`http_headers = ${Z(d)}`),s.join(`
1
+ import{r as a,j as o}from"./framework-vendor-D0S_ys6r.js";import{c as P}from"./clipboard-BLr_gbf8.js";import{k as L}from"./index-CGRjM_NC.js";import{u as R}from"./i18n-vendor-COTriSc6.js";import{X as H,C as J,r as K}from"./icons-vendor-DZjjGrOu.js";const w=(e,n)=>e&&e.trim()?e:n,ce=(e,n)=>{const s=w(e.description,n);return e.hasDescriptionOverride?{currentDescription:s,defaultDescription:w(e.defaultDescription,n),hasDescriptionOverride:!0}:{currentDescription:s,hasDescriptionOverride:!1}},O="generic-mcp-servers",F=[{id:"generic-mcp-servers"},{id:"claude-code"},{id:"cursor"},{id:"vscode"},{id:"codex"},{id:"opencode"},{id:"windsurf"},{id:"cherry-studio"},{id:"codebuddy"},{id:"qoder"},{id:"trae"},{id:"zcode"},{id:"workbuddy"},{id:"generic-http"}],M="<your-access-token>",A="mcphub-token",u=e=>!!e.url,p=e=>{const n=Object.entries(e.headers??{}).filter(([,s])=>s!==void 0);return n.length>0?Object.fromEntries(n):void 0},C=e=>{const n=Object.entries(e.env??{}).filter(([,s])=>s!==void 0);return n.length>0?Object.fromEntries(n):void 0},f=e=>{var t;const n=((t=e.args)==null?void 0:t.filter(i=>i!==void 0))??[],s=C(e);return{command:e.command??"",...n.length>0?{args:n}:{},...s?{env:s}:{}}},_=(e,n=!1)=>{if(!u(e))return n?{type:"stdio",...f(e)}:f(e);const s=p(e);return{type:e.type==="sse"?"sse":"http",url:e.url??"",...s?{headers:s}:{}}},z=e=>{const n=p(e);return{...e.type==="sse"?{type:"sse"}:{},url:e.url??"",...n?{headers:n}:{}}},v=(e,n,s)=>JSON.stringify({[e]:{[n]:s}},null,2),D=e=>v("mcpServers",e.name,u(e)?z(e):f(e)),U=(e,n)=>{const s=p(e),t=`Bearer ${M}`,i=(s==null?void 0:s.Authorization)===t,c=i?{...s,Authorization:`Bearer \${input:${A}}`}:s,d=u(e)?{type:e.type==="sse"?"sse":"http",url:e.url,...c?{headers:c}:{}}:{type:"stdio",...f(e)};return JSON.stringify({servers:{[e.name]:d},...i?{inputs:[{id:A,type:"promptString",description:n.tokenPromptDescription??"MCPHub access token",password:!0}]}:{}},null,2)},W=/^[A-Za-z0-9_-]+$/,N=e=>W.test(e)?e:JSON.stringify(e),h=e=>JSON.stringify(e),Z=e=>`{ ${Object.entries(e).map(([n,s])=>`${N(n)} = ${h(s)}`).join(", ")} }`,q=e=>{var c;const n=N(e.name),s=[`[mcp_servers.${n}]`];if(u(e)){s.push(`url = ${h(e.url??"")}`);const d=p(e);return d&&s.push(`http_headers = ${Z(d)}`),s.join(`
2
2
  `)}s.push(`command = ${h(e.command??"")}`);const t=((c=e.args)==null?void 0:c.filter(d=>d!==void 0))??[];t.length>0&&s.push(`args = [${t.map(h).join(", ")}]`);const i=C(e);if(i){s.push("",`[mcp_servers.${n}.env]`);for(const[d,b]of Object.entries(i))s.push(`${N(d)} = ${h(b)}`)}return s.join(`
3
3
  `)},Q=e=>{var s;if(!u(e)){const t=C(e),i=((s=e.args)==null?void 0:s.filter(c=>c!==void 0))??[];return JSON.stringify({mcp:{[e.name]:{type:"local",command:[e.command??"",...i],...t?{environment:t}:{}}}},null,2)}const n=p(e);return JSON.stringify({mcp:{[e.name]:{type:"remote",url:e.url,...n?{headers:n}:{}}}},null,2)},V=e=>{const n={name:e.name,description:e.description??""},s=p(e),t=u(e)?{...n,type:e.type==="sse"?"sse":"streamableHttp",baseUrl:e.url,...s?{headers:s}:{}}:{...n,type:"stdio",...f(e)};return v("mcpServers",e.name,t)},X=/^[A-Za-z0-9._:@/=-]+$/,Y=e=>`'${e.replace(/'/g,"'\\''")}'`,S=e=>X.test(e)?e:Y(e),G=e=>{if(!u(e)||e.name.startsWith("-")||(e.url??"").startsWith("-"))return;const n=["claude","mcp","add","--transport",e.type==="sse"?"sse":"http",S(e.name),S(e.url??"")];for(const[s,t]of Object.entries(p(e)??{}))n.push("--header",S(`${s}: ${t}`));return n.join(" ")},m=e=>typeof e=="string"&&e.length>0?e:void 0,I=e=>{switch(e){case"stdio":case"sse":case"streamable-http":case"openapi":return e;default:return}},y=e=>{if(!e||typeof e!="object"||Array.isArray(e))return;const n=Object.entries(e).filter(s=>typeof s[1]=="string");return n.length>0?Object.fromEntries(n):void 0},g=e=>{if(!Array.isArray(e))return;const n=e.filter(s=>typeof s=="string");return n.length>0?n:void 0},le=(e,n)=>{const s=n??{};return{name:e,...I(s.type)?{type:I(s.type)}:{},...m(s.url)?{url:m(s.url)}:{},...y(s.headers)?{headers:y(s.headers)}:{},...m(s.command)?{command:m(s.command)}:{},...g(s.args)?{args:g(s.args)}:{},...y(s.env)?{env:y(s.env)}:{},...m(s.description)?{description:m(s.description)}:{}}},ee=(e,n,s)=>{switch(e){case"generic-mcp-servers":return D(n);case"generic-http":return JSON.stringify(u(n)?{type:"http",...z(n)}:f(n),null,2);case"vscode":return U(n,s);case"codex":return q(n);case"opencode":return Q(n);case"cherry-studio":return V(n);case"claude-code":return v("mcpServers",n.name,_(n));case"codebuddy":return v("mcpServers",n.name,_(n,!0));default:return D(n)}},ne=(e,n,s={})=>{if(!n.url&&!n.command)return[];const t=[{kind:"config",text:ee(e,n,s)}];if(e==="claude-code"){const i=G(n);i&&t.push({kind:"command",text:i})}return t},de=({isOpen:e,onClose:n,target:s})=>{const{t}=R(),{showToast:i}=L(),[c,d]=a.useState(O),[b,x]=a.useState(null),E=a.useRef(null),T=a.useRef(null);a.useEffect(()=>{e||(x(null),d(O))},[e]),a.useEffect(()=>{var r;if(e)return T.current=document.activeElement instanceof HTMLElement?document.activeElement:null,(r=E.current)==null||r.focus(),()=>{var l;(l=T.current)==null||l.focus()}},[e]),a.useEffect(()=>{if(!e)return;const r=l=>{l.key==="Escape"&&n()};return document.addEventListener("keydown",r),()=>document.removeEventListener("keydown",r)},[e,n]);const j=a.useMemo(()=>s?ne(c,s,{tokenPromptDescription:t("clientConfig.tokenPrompt")}):[],[c,s,t]);if(!e||!s)return null;const B=async(r,l)=>{if(!await P(l)){i(t("common.copyFailed"),"error");return}x(r),i(t("common.copySuccess"),"success"),setTimeout(()=>x($=>$===r?null:$),1500)};return o.jsx("div",{className:"fixed inset-0 bg-black/50 z-[100] flex items-center justify-center p-4",onClick:r=>{r.target===r.currentTarget&&n()},children:o.jsxs("div",{ref:E,tabIndex:-1,className:"hub-card w-full max-w-2xl",role:"dialog","aria-modal":"true","aria-label":t("clientConfig.title"),children:[o.jsxs("div",{className:"flex items-start justify-between gap-3 px-4 py-3",style:{borderBottom:"1px solid var(--hub-line-2)"},children:[o.jsxs("div",{className:"min-w-0",children:[o.jsx("div",{className:"text-[14px] font-medium",style:{color:"var(--hub-ink)"},children:t("clientConfig.title")}),o.jsx("div",{className:"text-[12px] mt-0.5",style:{color:"var(--hub-ink-3)"},children:t("clientConfig.description",{name:s.name})})]}),o.jsx("button",{className:"hub-icon-btn sm",onClick:n,"aria-label":t("app.closeButton"),children:o.jsx(H,{size:13})})]}),j.length>0&&o.jsxs("div",{className:"px-4 pt-3",children:[o.jsx("div",{className:"flex flex-wrap gap-1",role:"tablist","aria-label":t("clientConfig.title"),children:F.map(r=>{const l=r.id===c;return o.jsx("button",{role:"tab","aria-selected":l,onClick:()=>d(r.id),className:"px-2.5 py-1 rounded-md text-[12px] transition-colors hover:bg-[var(--hub-surface-hover)]",style:{background:l?"var(--hub-surface)":"transparent",border:"1px solid "+(l?"var(--hub-line)":"transparent"),color:l?"var(--hub-ink)":"var(--hub-ink-2)"},children:t(`clientConfig.clients.${r.id}.label`)},r.id)})}),o.jsx("p",{className:"text-[12px] mt-3",style:{color:"var(--hub-ink-3)"},children:t(`clientConfig.clients.${c}.hint`)})]}),o.jsx("div",{className:"px-4 pb-4 pt-2 space-y-3 max-h-[60vh] overflow-auto",children:j.length===0?o.jsx("p",{className:"text-[12px]",style:{color:"var(--hub-ink-3)"},children:t("clientConfig.noTransport")}):j.map(r=>{const l=`${c}:${r.kind}`,k=b===l;return o.jsxs("div",{children:[o.jsxs("div",{className:"flex items-center justify-between mb-1",children:[o.jsx("span",{className:"text-[12px]",style:{color:"var(--hub-ink-2)"},children:t(r.kind==="command"?"clientConfig.commandBlock":"clientConfig.configBlock")}),o.jsxs("button",{className:"hub-btn sm",onClick:()=>void B(l,r.text),children:[k?o.jsx(J,{size:12,className:"text-[var(--hub-ok)]"}):o.jsx(K,{size:12}),t(k?"clientConfig.copied":"common.copy")]})]}),o.jsx("pre",{className:"hub-mono m-0 p-3 rounded-md overflow-auto",style:{background:"var(--hub-bg-2)",border:"1px solid var(--hub-line-2)",fontSize:12,lineHeight:1.5,color:"var(--hub-ink-2)",maxHeight:320},children:o.jsx("code",{children:r.text})})]},l)})}),o.jsx("div",{className:"flex justify-end px-4 py-3",style:{borderTop:"1px solid var(--hub-line-2)"},children:o.jsx("button",{className:"hub-btn",onClick:n,children:t("common.cancel")})})]})})},ae=e=>JSON.stringify(e.map(({name:n,status:s,enabled:t,tools:i,prompts:c,resources:d})=>({name:n,status:s,enabled:t,tools:i,prompts:c,resources:d})));export{M as A,de as C,ce as g,ae as s,le as t};
4
- //# sourceMappingURL=costRevision-Ntl7VtWo.js.map
4
+ //# sourceMappingURL=costRevision-Dzec2k_b.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"costRevision-Ntl7VtWo.js","sources":["../../src/utils/toolDescription.ts","../../src/utils/mcpClientSnippets.ts","../../src/components/ui/CopyClientConfigDialog.tsx","../../src/utils/costRevision.ts"],"sourcesContent":["type ToolDescriptionSource = {\n description?: string;\n defaultDescription?: string;\n hasDescriptionOverride?: boolean;\n};\n\nexport interface ToolDescriptionInfo {\n currentDescription: string;\n defaultDescription?: string;\n hasDescriptionOverride: boolean;\n}\n\nconst normalizeDescription = (description: string | undefined, fallback: string): string => {\n return description && description.trim() ? description : fallback;\n};\n\nexport const getToolDescriptionInfo = (\n tool: ToolDescriptionSource,\n noDescriptionText: string,\n): ToolDescriptionInfo => {\n const currentDescription = normalizeDescription(tool.description, noDescriptionText);\n\n if (!tool.hasDescriptionOverride) {\n return {\n currentDescription,\n hasDescriptionOverride: false,\n };\n }\n\n return {\n currentDescription,\n defaultDescription: normalizeDescription(tool.defaultDescription, noDescriptionText),\n hasDescriptionOverride: true,\n };\n};\n","/**\n * Per-client MCP configuration presets for the copy actions (#1165).\n *\n * MCPHub hands out HTTP endpoints, so every preset describes the same target -\n * a name, an optional URL/headers pair (HTTP) or command/args/env triple\n * (stdio) - in the shape the selected client expects. Eleven of the fourteen\n * presets were verified against the upstream sources below (URLs re-checked\n * 2026-09, the month this file landed); the remaining three reuse the generic\n * `mcpServers` shape (or, for the generic-http escape hatch, the bare entry)\n * these copy actions always produced.\n *\n * Which clients document an explicit transport field, and which do not:\n * - Documents a transport field (the `type` key is written into the entry):\n * claude-code (`type: \"http\" | \"sse\" | \"stdio\"`; a remote entry with `url`\n * but no `type` is a configuration error per\n * https://code.claude.com/docs/en/mcp), vscode (`\"http\" | \"sse\" | \"stdio\"`,\n * final URL after the 2026-09 migration:\n * https://code.visualstudio.com/docs/agent-customization/mcp-servers),\n * codebuddy (`type` marked Required; fixed `\"http\"` for HTTP, `\"sse\"` for\n * SSE and `\"stdio\"` for stdio, per\n * https://www.codebuddy.ai/docs/cli/mcp), qoder (every remote\n * example carries `type`; for Streamable HTTP the doc says to configure the\n * URL the same way as SSE and the IDE auto-detects it, so the http target\n * deliberately keeps the bare `url` + `headers` shape rather than tagging\n * it `sse`; https://docs.qoder.com/zh/user-guide/chat/model-context-protocol),\n * cherry-studio (`baseUrl` + `'sse' | 'streamableHttp'`, strict schema),\n * opencode (`type: 'remote' | 'local'`).\n * - Field table does not list a transport key: cursor\n * (https://cursor.com/docs/mcp), trae (https://docs.trae.ai/ide/add-mcp-servers),\n * zcode (https://zcode.z.ai/en/docs/mcp-services), windsurf\n * (https://docs.windsurf.com/windsurf/cascade/mcp). The `type: \"sse\"` marker\n * the generic shape injects for SSE targets on these presets is added on\n * the assumption that unknown keys are ignored, not required by their\n * documented shape; it is kept because SSE upstreams must not be dialed as\n * streamable HTTP.\n * - Verified but the format carries no `type` key at all: codex (TOML;\n * `codex-rs/config/src/mcp_types.rs`,\n * https://github.com/openai/codex/blob/main/codex-rs/config/src/mcp_types.rs).\n * (The generic-http escape hatch, one of the three unverified presets, is not\n * a \"no `type`\" format: for a remote target it always writes an explicit\n * `type` - `\"http\"`, or `\"sse\"` for an SSE upstream.)\n *\n * Upstream sources used:\n * - Claude Code MCP configuration: https://code.claude.com/docs/en/mcp\n * - Cursor MCP docs: https://cursor.com/docs/mcp\n * - VS Code MCP configuration reference:\n * https://code.visualstudio.com/docs/agent-customization/mcp-servers\n * - Codex `codex-rs/config/src/mcp_types.rs`:\n * https://github.com/openai/codex/blob/main/codex-rs/config/src/mcp_types.rs\n * - OpenCode mcp-servers docs: https://opencode.ai/docs/mcp-servers/\n * - Cherry Studio `mcpProtocolInstall.ts`:\n * https://github.com/CherryHQ/cherry-studio/blob/main/src/shared/data/types/mcpProtocolInstall.ts\n * - CodeBuddy MCP CLI docs: https://www.codebuddy.ai/docs/cli/mcp\n * - Qoder MCP user guide:\n * https://docs.qoder.com/zh/user-guide/chat/model-context-protocol\n * - Trae MCP docs: https://docs.trae.ai/ide/add-mcp-servers\n * - Windsurf MCP docs: https://docs.windsurf.com/windsurf/cascade/mcp\n * - ZCode MCP docs: https://zcode.z.ai/en/docs/mcp-services\n */\n\nexport type ClientSnippetId =\n | 'generic-mcp-servers'\n | 'generic-http'\n | 'claude-code'\n | 'cursor'\n | 'vscode'\n | 'codex'\n | 'opencode'\n | 'windsurf'\n | 'cherry-studio'\n | 'codebuddy'\n | 'qoder'\n | 'trae'\n | 'zcode'\n | 'workbuddy';\n\nexport interface ClientSnippetPreset {\n id: ClientSnippetId;\n}\n\n/** Tab the dialog opens on; the shape the copy actions produced before. */\nexport const DEFAULT_CLIENT_SNIPPET_ID: ClientSnippetId = 'generic-mcp-servers';\n\n/**\n * Tabs shown in the copy dialog, most common clients first. The first entry is\n * the shape the copy actions produced before the presets existed, so the\n * default tab stays familiar; the bare HTTP entry is the escape hatch for\n * clients that wrap the entry in their own container.\n */\nexport const CLIENT_SNIPPET_PRESETS: readonly ClientSnippetPreset[] = [\n { id: 'generic-mcp-servers' },\n { id: 'claude-code' },\n { id: 'cursor' },\n { id: 'vscode' },\n { id: 'codex' },\n { id: 'opencode' },\n { id: 'windsurf' },\n { id: 'cherry-studio' },\n { id: 'codebuddy' },\n { id: 'qoder' },\n { id: 'trae' },\n { id: 'zcode' },\n { id: 'workbuddy' },\n { id: 'generic-http' },\n];\n\nexport interface ClientSnippetTarget {\n name: string;\n /** Stored transport type; `sse` is called out so clients do not misdial it as http. */\n type?: 'stdio' | 'sse' | 'streamable-http' | 'openapi';\n /** HTTP targets carry a URL (and usually an Authorization header). */\n url?: string;\n headers?: Record<string, string>;\n /** stdio targets carry a command instead. */\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n description?: string;\n}\n\nexport interface ClientSnippetBlock {\n kind: 'config' | 'command';\n text: string;\n}\n\nexport interface ClientSnippetOptions {\n /** Description used by the VS Code token input prompt. */\n tokenPromptDescription?: string;\n}\n\n/** Placeholder MCPHub uses for snippets that need a token pasted in by hand. */\nexport const ACCESS_TOKEN_PLACEHOLDER = '<your-access-token>';\nconst TOKEN_INPUT_ID = 'mcphub-token';\n\nconst isHttpTarget = (target: ClientSnippetTarget): boolean => Boolean(target.url);\n\nconst definedHeaders = (target: ClientSnippetTarget): Record<string, string> | undefined => {\n const entries = Object.entries(target.headers ?? {}).filter(([, value]) => value !== undefined);\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst definedEnv = (target: ClientSnippetTarget): Record<string, string> | undefined => {\n const entries = Object.entries(target.env ?? {}).filter(([, value]) => value !== undefined);\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst stdioEntry = (target: ClientSnippetTarget): Record<string, unknown> => {\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n const env = definedEnv(target);\n return {\n command: target.command ?? '',\n ...(args.length > 0 ? { args } : {}),\n ...(env ? { env } : {}),\n };\n};\n\n/**\n * HTTP/stdio entry for clients whose docs make the transport a required field.\n * Claude Code skips an entry that has a `url` but no `type` (it reads the\n * entry as stdio and reports a configuration error), and CodeBuddy's field\n * table marks `type` as Required, fixed to `\"http\"` for the HTTP form and\n * `\"sse\"` for the SSE form.\n *\n * Stdio targets differ between the two clients. Claude Code reads a type-less\n * entry as stdio (https://code.claude.com/docs/en/mcp), so its stdio shape\n * keeps the plain command/args/env form with no `type` key. CodeBuddy's docs\n * mark `type` as Required with the fixed value `\"stdio\"`\n * (https://www.codebuddy.ai/docs/cli/mcp), so its stdio shape writes\n * `\"type\": \"stdio\"`; pass `stdioType` only for CodeBuddy.\n */\nconst typedTransportEntry = (\n target: ClientSnippetTarget,\n stdioType: boolean = false,\n): Record<string, unknown> => {\n if (!isHttpTarget(target)) {\n return stdioType ? { type: 'stdio', ...stdioEntry(target) } : stdioEntry(target);\n }\n\n const headers = definedHeaders(target);\n return {\n type: target.type === 'sse' ? 'sse' : 'http',\n url: target.url ?? '',\n ...(headers ? { headers } : {}),\n };\n};\n\n// This helper feeds the generic `mcpServers` shape for the generic-mcp-servers,\n// qoder, cursor, windsurf, trae, zcode and workbuddy presets. Only qoder's\n// upstream docs carry a transport field for this shape; for the rest\n// (generic-mcp-servers, cursor, windsurf, trae, zcode, workbuddy) the\n// `type: \"sse\"` marker is added on the assumption that unknown keys are\n// ignored, so it is unverified against a field table. It is kept because an\n// SSE upstream must not be dialed as streamable HTTP.\nconst httpEntry = (target: ClientSnippetTarget): Record<string, unknown> => {\n const headers = definedHeaders(target);\n return {\n // Only `sse` is spelled out: it is the one HTTP transport a client might\n // otherwise misdial as streamable HTTP. Every other target keeps the bare\n // `url` + `headers` shape these presets always produced.\n ...(target.type === 'sse' ? { type: 'sse' } : {}),\n url: target.url ?? '',\n ...(headers ? { headers } : {}),\n };\n};\n\nconst withWrappedName = (\n wrapper: string,\n name: string,\n entry: Record<string, unknown>,\n): string => JSON.stringify({ [wrapper]: { [name]: entry } }, null, 2);\n\nconst jsonSnippet = (target: ClientSnippetTarget): string =>\n withWrappedName(\n 'mcpServers',\n target.name,\n isHttpTarget(target) ? httpEntry(target) : stdioEntry(target),\n );\n\n// VS Code wants the transport spelled out and can prompt for the token itself,\n// so the shared placeholder becomes an input reference plus an `inputs` entry.\nconst vscodeSnippet = (target: ClientSnippetTarget, options: ClientSnippetOptions): string => {\n const headers = definedHeaders(target);\n const placeholder = `Bearer ${ACCESS_TOKEN_PLACEHOLDER}`;\n const needsTokenInput = headers?.Authorization === placeholder;\n\n const body = needsTokenInput\n ? { ...headers, Authorization: `Bearer \\${input:${TOKEN_INPUT_ID}}` }\n : headers;\n\n const entry = isHttpTarget(target)\n ? {\n // VS Code distinguishes the SSE transport from streamable HTTP.\n type: target.type === 'sse' ? 'sse' : 'http',\n url: target.url,\n ...(body ? { headers: body } : {}),\n }\n : { type: 'stdio', ...stdioEntry(target) };\n\n return JSON.stringify(\n {\n servers: { [target.name]: entry },\n ...(needsTokenInput\n ? {\n inputs: [\n {\n id: TOKEN_INPUT_ID,\n type: 'promptString',\n description: options.tokenPromptDescription ?? 'MCPHub access token',\n password: true,\n },\n ],\n }\n : {}),\n },\n null,\n 2,\n );\n};\n\nconst TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nconst tomlKey = (key: string): string => (TOML_BARE_KEY.test(key) ? key : JSON.stringify(key));\nconst tomlValue = (value: string): string => JSON.stringify(value);\nconst tomlInlineTable = (entries: Record<string, string>): string =>\n `{ ${Object.entries(entries)\n .map(([key, value]) => `${tomlKey(key)} = ${tomlValue(value)}`)\n .join(', ')} }`;\n\n// Codex calls the header map `http_headers` (there is no `headers` key), and\n// needs quoted table keys for names that are not bare TOML keys - group names\n// may contain spaces or CJK. Note `#[schemars(deny_unknown_fields)]` in\n// `mcp_types.rs` only constrains the generated JSON Schema; the Rust struct\n// itself has no `#[serde(deny_unknown_fields)]`, so TOML parsing may silently\n// ignore unknown fields instead of rejecting them.\nconst codexSnippet = (target: ClientSnippetTarget): string => {\n const key = tomlKey(target.name);\n const lines = [`[mcp_servers.${key}]`];\n\n if (isHttpTarget(target)) {\n lines.push(`url = ${tomlValue(target.url ?? '')}`);\n const headers = definedHeaders(target);\n if (headers) {\n lines.push(`http_headers = ${tomlInlineTable(headers)}`);\n }\n return lines.join('\\n');\n }\n\n lines.push(`command = ${tomlValue(target.command ?? '')}`);\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n if (args.length > 0) {\n lines.push(`args = [${args.map(tomlValue).join(', ')}]`);\n }\n const env = definedEnv(target);\n if (env) {\n lines.push('', `[mcp_servers.${key}.env]`);\n for (const [name, value] of Object.entries(env)) {\n lines.push(`${tomlKey(name)} = ${tomlValue(value)}`);\n }\n }\n return lines.join('\\n');\n};\n\nconst openCodeSnippet = (target: ClientSnippetTarget): string => {\n if (!isHttpTarget(target)) {\n const env = definedEnv(target);\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n return JSON.stringify(\n {\n mcp: {\n [target.name]: {\n type: 'local',\n command: [target.command ?? '', ...args],\n ...(env ? { environment: env } : {}),\n },\n },\n },\n null,\n 2,\n );\n }\n\n const headers = definedHeaders(target);\n return JSON.stringify(\n {\n mcp: {\n [target.name]: {\n type: 'remote',\n url: target.url,\n ...(headers ? { headers } : {}),\n },\n },\n },\n null,\n 2,\n );\n};\n\n// Cherry Studio's `ProtocolMcpServerConfigSchema` is a strict object: remote\n// servers use `baseUrl` + `type: 'sse' | 'streamableHttp'` (no `url` key) and\n// stdio servers use command/args/env. The install-time metadata\n// (`installSource`, `isTrusted`, `installedAt`, `isActive: false`) belongs to\n// `ProtocolMcpServerInstallSchema`, the install *request* schema, not to this\n// hand-pasted config; an entry carrying `isActive` would not satisfy\n// `ProtocolMcpServerConfigSchema` (which has no such key).\nconst cherryStudioSnippet = (target: ClientSnippetTarget): string => {\n const base = {\n name: target.name,\n description: target.description ?? '',\n };\n\n const headers = definedHeaders(target);\n const entry = isHttpTarget(target)\n ? {\n ...base,\n // The schema accepts exactly two remote transports; keep SSE upstreams\n // spelled out so Cherry Studio does not dial them as streamable HTTP.\n type: target.type === 'sse' ? 'sse' : 'streamableHttp',\n baseUrl: target.url,\n ...(headers ? { headers } : {}),\n }\n : { ...base, type: 'stdio', ...stdioEntry(target) };\n\n return withWrappedName('mcpServers', target.name, entry);\n};\n\nconst CLI_SAFE = /^[A-Za-z0-9._:@/=-]+$/;\n\n// Single quotes are the only reliable POSIX quoting for a pasted command:\n// double quotes still let the shell expand `$(...)`, backticks and `${...}`\n// inside them. Within single quotes nothing is expanded, so the only character\n// needing special handling is `'` itself, escaped as `'\\''` (close quote,\n// escaped quote, reopen).\nconst shellQuote = (value: string): string => `'${value.replace(/'/g, \"'\\\\''\")}'`;\n\nconst cliArg = (value: string): string =>\n CLI_SAFE.test(value) ? value : shellQuote(value);\n\n// Claude Code's one-liner equivalent of the JSON block, in the documented\n// HTTP shape: `claude mcp add --transport <t> <name> <url> [--header ...]`.\n//\n// A positional that starts with `-` cannot be spelled safely in that shape:\n// shell quoting is gone by the time the CLI's tokenizer sees the value, and\n// the documented `--` separator belongs to the stdio form, so relying on it\n// for the HTTP form would be an unverified parsing assumption. Since\n// SERVER_NAME_PATTERN admits names like `-foo`, a name or URL starting with\n// a dash omits the command block outright - the JSON config block still\n// carries the full entry - rather than emitting a command the CLI would\n// misparse as one of its own options.\nconst claudeCodeCommand = (target: ClientSnippetTarget): string | undefined => {\n if (!isHttpTarget(target)) {\n return undefined;\n }\n\n if (target.name.startsWith('-') || (target.url ?? '').startsWith('-')) {\n return undefined;\n }\n\n const parts = [\n 'claude',\n 'mcp',\n 'add',\n '--transport',\n // SSE endpoints must not be dialed as streamable HTTP.\n target.type === 'sse' ? 'sse' : 'http',\n cliArg(target.name),\n cliArg(target.url ?? ''),\n ];\n for (const [name, value] of Object.entries(definedHeaders(target) ?? {})) {\n parts.push('--header', cliArg(`${name}: ${value}`));\n }\n return parts.join(' ');\n};\n\nconst asString = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined;\n\n/** Narrow a stored `type` to the four legal server types; anything else is dropped. */\nconst asServerType = (value: unknown): ClientSnippetTarget['type'] => {\n switch (value) {\n case 'stdio':\n case 'sse':\n case 'streamable-http':\n case 'openapi':\n return value;\n default:\n return undefined;\n }\n};\n\nconst asStringRecord = (value: unknown): Record<string, string> | undefined => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n );\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst asStringArray = (value: unknown): string[] | undefined => {\n if (!Array.isArray(value)) {\n return undefined;\n }\n\n const items = value.filter((item): item is string => typeof item === 'string');\n return items.length > 0 ? items : undefined;\n};\n\n/**\n * Narrow a stored server configuration (the `mcp-settings/export` payload\n * carries MCPHub's internal fields too) down to what a client snippet needs.\n */\nexport const toClientSnippetTarget = (\n name: string,\n config: Record<string, unknown> | undefined,\n): ClientSnippetTarget => {\n const source = config ?? {};\n\n return {\n name,\n ...(asServerType(source.type) ? { type: asServerType(source.type) } : {}),\n ...(asString(source.url) ? { url: asString(source.url) } : {}),\n ...(asStringRecord(source.headers) ? { headers: asStringRecord(source.headers) } : {}),\n ...(asString(source.command) ? { command: asString(source.command) } : {}),\n ...(asStringArray(source.args) ? { args: asStringArray(source.args) } : {}),\n ...(asStringRecord(source.env) ? { env: asStringRecord(source.env) } : {}),\n ...(asString(source.description) ? { description: asString(source.description) } : {}),\n };\n};\n\nconst configSnippet = (\n id: ClientSnippetId,\n target: ClientSnippetTarget,\n options: ClientSnippetOptions,\n): string => {\n switch (id) {\n case 'generic-mcp-servers':\n return jsonSnippet(target);\n case 'generic-http':\n return JSON.stringify(\n isHttpTarget(target)\n ? { type: 'http', ...httpEntry(target) }\n : stdioEntry(target),\n null,\n 2,\n );\n case 'vscode':\n return vscodeSnippet(target, options);\n case 'codex':\n return codexSnippet(target);\n case 'opencode':\n return openCodeSnippet(target);\n case 'cherry-studio':\n return cherryStudioSnippet(target);\n case 'claude-code':\n return withWrappedName('mcpServers', target.name, typedTransportEntry(target));\n case 'codebuddy':\n // CodeBuddy's field table marks `type` as Required, fixed to `\"stdio\"`\n // for stdio entries, so unlike Claude Code it always carries the type.\n return withWrappedName('mcpServers', target.name, typedTransportEntry(target, true));\n default:\n return jsonSnippet(target);\n }\n};\n\nexport const buildClientSnippetBlocks = (\n id: ClientSnippetId,\n target: ClientSnippetTarget,\n options: ClientSnippetOptions = {},\n): ClientSnippetBlock[] => {\n // OpenAPI-backed servers carry neither a URL nor a command, so every preset\n // would degrade to filler like `command: \"\"`. Return no blocks and let the\n // dialog explain that there is no client-side transport to copy.\n if (!target.url && !target.command) {\n return [];\n }\n\n const blocks: ClientSnippetBlock[] = [{ kind: 'config', text: configSnippet(id, target, options) }];\n\n if (id === 'claude-code') {\n const command = claudeCodeCommand(target);\n if (command) {\n blocks.push({ kind: 'command', text: command });\n }\n }\n\n return blocks;\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Check, Copy, X } from 'lucide-react';\nimport {\n buildClientSnippetBlocks,\n CLIENT_SNIPPET_PRESETS,\n DEFAULT_CLIENT_SNIPPET_ID,\n type ClientSnippetId,\n type ClientSnippetTarget,\n} from '@/utils/mcpClientSnippets';\nimport { copyText } from '@/utils/clipboard';\nimport { useToast } from '@/contexts/ToastContext';\n\ninterface CopyClientConfigDialogProps {\n isOpen: boolean;\n onClose: () => void;\n /** Target to render; `null` keeps the dialog closed. */\n target: ClientSnippetTarget | null;\n}\n\n/**\n * Per-client MCP configuration presets for the copy actions (#1165): one tab\n * per client, each with the path it belongs in and a ready-to-paste snippet.\n */\nconst CopyClientConfigDialog = ({ isOpen, onClose, target }: CopyClientConfigDialogProps) => {\n const { t } = useTranslation();\n const { showToast } = useToast();\n const [activeId, setActiveId] = useState<ClientSnippetId>(DEFAULT_CLIENT_SNIPPET_ID);\n const [copiedBlock, setCopiedBlock] = useState<string | null>(null);\n const dialogRef = useRef<HTMLDivElement>(null);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!isOpen) {\n setCopiedBlock(null);\n setActiveId(DEFAULT_CLIENT_SNIPPET_ID);\n }\n }, [isOpen]);\n\n // Like the other hand-rolled modals in this codebase the dialog does not trap\n // focus; it does move focus inside on open and hand it back to the trigger on\n // close, so keyboard users are not left behind on the page underneath.\n useEffect(() => {\n if (!isOpen) return;\n returnFocusRef.current =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n dialogRef.current?.focus();\n\n return () => {\n returnFocusRef.current?.focus();\n };\n }, [isOpen]);\n\n useEffect(() => {\n if (!isOpen) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onClose();\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [isOpen, onClose]);\n\n const blocks = useMemo(\n () =>\n target\n ? buildClientSnippetBlocks(activeId, target, {\n tokenPromptDescription: t('clientConfig.tokenPrompt'),\n })\n : [],\n [activeId, target, t],\n );\n\n if (!isOpen || !target) {\n return null;\n }\n\n const handleCopy = async (key: string, value: string) => {\n const ok = await copyText(value);\n if (!ok) {\n showToast(t('common.copyFailed'), 'error');\n return;\n }\n setCopiedBlock(key);\n showToast(t('common.copySuccess'), 'success');\n setTimeout(() => setCopiedBlock((current) => (current === key ? null : current)), 1500);\n };\n\n return (\n <div\n className=\"fixed inset-0 bg-black/50 z-[100] flex items-center justify-center p-4\"\n onClick={(event) => {\n if (event.target === event.currentTarget) onClose();\n }}\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n className=\"hub-card w-full max-w-2xl\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={t('clientConfig.title')}\n >\n <div\n className=\"flex items-start justify-between gap-3 px-4 py-3\"\n style={{ borderBottom: '1px solid var(--hub-line-2)' }}\n >\n <div className=\"min-w-0\">\n <div className=\"text-[14px] font-medium\" style={{ color: 'var(--hub-ink)' }}>\n {t('clientConfig.title')}\n </div>\n <div className=\"text-[12px] mt-0.5\" style={{ color: 'var(--hub-ink-3)' }}>\n {t('clientConfig.description', { name: target.name })}\n </div>\n </div>\n <button className=\"hub-icon-btn sm\" onClick={onClose} aria-label={t('app.closeButton')}>\n <X size={13} />\n </button>\n </div>\n\n {/* A target with neither URL nor command (OpenAPI-backed servers) has no\n transport, so every preset returns the same empty result: offering 14\n clickable tabs would only suggest the dialog is broken. The tab strip\n and its per-client hint are therefore hidden and only the explanation\n below is shown. */}\n {blocks.length > 0 && (\n <div className=\"px-4 pt-3\">\n <div\n className=\"flex flex-wrap gap-1\"\n role=\"tablist\"\n aria-label={t('clientConfig.title')}\n >\n {CLIENT_SNIPPET_PRESETS.map((preset) => {\n const active = preset.id === activeId;\n return (\n <button\n key={preset.id}\n role=\"tab\"\n aria-selected={active}\n onClick={() => setActiveId(preset.id)}\n className=\"px-2.5 py-1 rounded-md text-[12px] transition-colors hover:bg-[var(--hub-surface-hover)]\"\n style={{\n background: active ? 'var(--hub-surface)' : 'transparent',\n border: '1px solid ' + (active ? 'var(--hub-line)' : 'transparent'),\n color: active ? 'var(--hub-ink)' : 'var(--hub-ink-2)',\n }}\n >\n {t(`clientConfig.clients.${preset.id}.label`)}\n </button>\n );\n })}\n </div>\n\n <p className=\"text-[12px] mt-3\" style={{ color: 'var(--hub-ink-3)' }}>\n {t(`clientConfig.clients.${activeId}.hint`)}\n </p>\n </div>\n )}\n\n <div className=\"px-4 pb-4 pt-2 space-y-3 max-h-[60vh] overflow-auto\">\n {blocks.length === 0 ? (\n // OpenAPI-backed servers have no client-side transport to copy.\n <p className=\"text-[12px]\" style={{ color: 'var(--hub-ink-3)' }}>\n {t('clientConfig.noTransport')}\n </p>\n ) : (\n blocks.map((block) => {\n const key = `${activeId}:${block.kind}`;\n const copied = copiedBlock === key;\n return (\n <div key={key}>\n <div className=\"flex items-center justify-between mb-1\">\n <span className=\"text-[12px]\" style={{ color: 'var(--hub-ink-2)' }}>\n {t(\n block.kind === 'command'\n ? 'clientConfig.commandBlock'\n : 'clientConfig.configBlock',\n )}\n </span>\n <button\n className=\"hub-btn sm\"\n onClick={() => void handleCopy(key, block.text)}\n >\n {copied ? <Check size={12} className=\"text-[var(--hub-ok)]\" /> : <Copy size={12} />}\n {copied ? t('clientConfig.copied') : t('common.copy')}\n </button>\n </div>\n <pre\n className=\"hub-mono m-0 p-3 rounded-md overflow-auto\"\n style={{\n background: 'var(--hub-bg-2)',\n border: '1px solid var(--hub-line-2)',\n fontSize: 12,\n lineHeight: 1.5,\n color: 'var(--hub-ink-2)',\n maxHeight: 320,\n }}\n >\n <code>{block.text}</code>\n </pre>\n </div>\n );\n })\n )}\n </div>\n\n <div\n className=\"flex justify-end px-4 py-3\"\n style={{ borderTop: '1px solid var(--hub-line-2)' }}\n >\n <button className=\"hub-btn\" onClick={onClose}>\n {t('common.cancel')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default CopyClientConfigDialog;\n","import type { Server } from '../types';\n\n// Ignore transient polling metadata, but include full definitions: equal tool\n// counts do not imply equal context costs.\nexport const serverCostRevision = (servers: Server[]): string =>\n JSON.stringify(\n servers.map(({ name, status, enabled, tools, prompts, resources }) => ({\n name,\n status,\n enabled,\n tools,\n prompts,\n resources,\n })),\n );\n"],"names":["normalizeDescription","description","fallback","getToolDescriptionInfo","tool","noDescriptionText","currentDescription","DEFAULT_CLIENT_SNIPPET_ID","CLIENT_SNIPPET_PRESETS","ACCESS_TOKEN_PLACEHOLDER","TOKEN_INPUT_ID","isHttpTarget","target","definedHeaders","entries","value","definedEnv","stdioEntry","args","_a","arg","env","typedTransportEntry","stdioType","headers","httpEntry","withWrappedName","wrapper","name","entry","jsonSnippet","vscodeSnippet","options","placeholder","needsTokenInput","body","TOML_BARE_KEY","tomlKey","key","tomlValue","tomlInlineTable","codexSnippet","lines","openCodeSnippet","cherryStudioSnippet","base","CLI_SAFE","shellQuote","cliArg","claudeCodeCommand","parts","asString","asServerType","asStringRecord","asStringArray","items","item","toClientSnippetTarget","config","source","configSnippet","id","buildClientSnippetBlocks","blocks","command","CopyClientConfigDialog","isOpen","onClose","useTranslation","showToast","useToast","activeId","setActiveId","useState","copiedBlock","setCopiedBlock","dialogRef","useRef","returnFocusRef","useEffect","onKeyDown","event","useMemo","handleCopy","copyText","current","jsx","jsxs","X","preset","active","block","copied","Check","Copy","serverCostRevision","servers","status","enabled","tools","prompts","resources"],"mappings":"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"}
1
+ {"version":3,"file":"costRevision-Dzec2k_b.js","sources":["../../src/utils/toolDescription.ts","../../src/utils/mcpClientSnippets.ts","../../src/components/ui/CopyClientConfigDialog.tsx","../../src/utils/costRevision.ts"],"sourcesContent":["type ToolDescriptionSource = {\n description?: string;\n defaultDescription?: string;\n hasDescriptionOverride?: boolean;\n};\n\nexport interface ToolDescriptionInfo {\n currentDescription: string;\n defaultDescription?: string;\n hasDescriptionOverride: boolean;\n}\n\nconst normalizeDescription = (description: string | undefined, fallback: string): string => {\n return description && description.trim() ? description : fallback;\n};\n\nexport const getToolDescriptionInfo = (\n tool: ToolDescriptionSource,\n noDescriptionText: string,\n): ToolDescriptionInfo => {\n const currentDescription = normalizeDescription(tool.description, noDescriptionText);\n\n if (!tool.hasDescriptionOverride) {\n return {\n currentDescription,\n hasDescriptionOverride: false,\n };\n }\n\n return {\n currentDescription,\n defaultDescription: normalizeDescription(tool.defaultDescription, noDescriptionText),\n hasDescriptionOverride: true,\n };\n};\n","/**\n * Per-client MCP configuration presets for the copy actions (#1165).\n *\n * MCPHub hands out HTTP endpoints, so every preset describes the same target -\n * a name, an optional URL/headers pair (HTTP) or command/args/env triple\n * (stdio) - in the shape the selected client expects. Eleven of the fourteen\n * presets were verified against the upstream sources below (URLs re-checked\n * 2026-09, the month this file landed); the remaining three reuse the generic\n * `mcpServers` shape (or, for the generic-http escape hatch, the bare entry)\n * these copy actions always produced.\n *\n * Which clients document an explicit transport field, and which do not:\n * - Documents a transport field (the `type` key is written into the entry):\n * claude-code (`type: \"http\" | \"sse\" | \"stdio\"`; a remote entry with `url`\n * but no `type` is a configuration error per\n * https://code.claude.com/docs/en/mcp), vscode (`\"http\" | \"sse\" | \"stdio\"`,\n * final URL after the 2026-09 migration:\n * https://code.visualstudio.com/docs/agent-customization/mcp-servers),\n * codebuddy (`type` marked Required; fixed `\"http\"` for HTTP, `\"sse\"` for\n * SSE and `\"stdio\"` for stdio, per\n * https://www.codebuddy.ai/docs/cli/mcp), qoder (every remote\n * example carries `type`; for Streamable HTTP the doc says to configure the\n * URL the same way as SSE and the IDE auto-detects it, so the http target\n * deliberately keeps the bare `url` + `headers` shape rather than tagging\n * it `sse`; https://docs.qoder.com/zh/user-guide/chat/model-context-protocol),\n * cherry-studio (`baseUrl` + `'sse' | 'streamableHttp'`, strict schema),\n * opencode (`type: 'remote' | 'local'`).\n * - Field table does not list a transport key: cursor\n * (https://cursor.com/docs/mcp), trae (https://docs.trae.ai/ide/add-mcp-servers),\n * zcode (https://zcode.z.ai/en/docs/mcp-services), windsurf\n * (https://docs.windsurf.com/windsurf/cascade/mcp). The `type: \"sse\"` marker\n * the generic shape injects for SSE targets on these presets is added on\n * the assumption that unknown keys are ignored, not required by their\n * documented shape; it is kept because SSE upstreams must not be dialed as\n * streamable HTTP.\n * - Verified but the format carries no `type` key at all: codex (TOML;\n * `codex-rs/config/src/mcp_types.rs`,\n * https://github.com/openai/codex/blob/main/codex-rs/config/src/mcp_types.rs).\n * (The generic-http escape hatch, one of the three unverified presets, is not\n * a \"no `type`\" format: for a remote target it always writes an explicit\n * `type` - `\"http\"`, or `\"sse\"` for an SSE upstream.)\n *\n * Upstream sources used:\n * - Claude Code MCP configuration: https://code.claude.com/docs/en/mcp\n * - Cursor MCP docs: https://cursor.com/docs/mcp\n * - VS Code MCP configuration reference:\n * https://code.visualstudio.com/docs/agent-customization/mcp-servers\n * - Codex `codex-rs/config/src/mcp_types.rs`:\n * https://github.com/openai/codex/blob/main/codex-rs/config/src/mcp_types.rs\n * - OpenCode mcp-servers docs: https://opencode.ai/docs/mcp-servers/\n * - Cherry Studio `mcpProtocolInstall.ts`:\n * https://github.com/CherryHQ/cherry-studio/blob/main/src/shared/data/types/mcpProtocolInstall.ts\n * - CodeBuddy MCP CLI docs: https://www.codebuddy.ai/docs/cli/mcp\n * - Qoder MCP user guide:\n * https://docs.qoder.com/zh/user-guide/chat/model-context-protocol\n * - Trae MCP docs: https://docs.trae.ai/ide/add-mcp-servers\n * - Windsurf MCP docs: https://docs.windsurf.com/windsurf/cascade/mcp\n * - ZCode MCP docs: https://zcode.z.ai/en/docs/mcp-services\n */\n\nexport type ClientSnippetId =\n | 'generic-mcp-servers'\n | 'generic-http'\n | 'claude-code'\n | 'cursor'\n | 'vscode'\n | 'codex'\n | 'opencode'\n | 'windsurf'\n | 'cherry-studio'\n | 'codebuddy'\n | 'qoder'\n | 'trae'\n | 'zcode'\n | 'workbuddy';\n\nexport interface ClientSnippetPreset {\n id: ClientSnippetId;\n}\n\n/** Tab the dialog opens on; the shape the copy actions produced before. */\nexport const DEFAULT_CLIENT_SNIPPET_ID: ClientSnippetId = 'generic-mcp-servers';\n\n/**\n * Tabs shown in the copy dialog, most common clients first. The first entry is\n * the shape the copy actions produced before the presets existed, so the\n * default tab stays familiar; the bare HTTP entry is the escape hatch for\n * clients that wrap the entry in their own container.\n */\nexport const CLIENT_SNIPPET_PRESETS: readonly ClientSnippetPreset[] = [\n { id: 'generic-mcp-servers' },\n { id: 'claude-code' },\n { id: 'cursor' },\n { id: 'vscode' },\n { id: 'codex' },\n { id: 'opencode' },\n { id: 'windsurf' },\n { id: 'cherry-studio' },\n { id: 'codebuddy' },\n { id: 'qoder' },\n { id: 'trae' },\n { id: 'zcode' },\n { id: 'workbuddy' },\n { id: 'generic-http' },\n];\n\nexport interface ClientSnippetTarget {\n name: string;\n /** Stored transport type; `sse` is called out so clients do not misdial it as http. */\n type?: 'stdio' | 'sse' | 'streamable-http' | 'openapi';\n /** HTTP targets carry a URL (and usually an Authorization header). */\n url?: string;\n headers?: Record<string, string>;\n /** stdio targets carry a command instead. */\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n description?: string;\n}\n\nexport interface ClientSnippetBlock {\n kind: 'config' | 'command';\n text: string;\n}\n\nexport interface ClientSnippetOptions {\n /** Description used by the VS Code token input prompt. */\n tokenPromptDescription?: string;\n}\n\n/** Placeholder MCPHub uses for snippets that need a token pasted in by hand. */\nexport const ACCESS_TOKEN_PLACEHOLDER = '<your-access-token>';\nconst TOKEN_INPUT_ID = 'mcphub-token';\n\nconst isHttpTarget = (target: ClientSnippetTarget): boolean => Boolean(target.url);\n\nconst definedHeaders = (target: ClientSnippetTarget): Record<string, string> | undefined => {\n const entries = Object.entries(target.headers ?? {}).filter(([, value]) => value !== undefined);\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst definedEnv = (target: ClientSnippetTarget): Record<string, string> | undefined => {\n const entries = Object.entries(target.env ?? {}).filter(([, value]) => value !== undefined);\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst stdioEntry = (target: ClientSnippetTarget): Record<string, unknown> => {\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n const env = definedEnv(target);\n return {\n command: target.command ?? '',\n ...(args.length > 0 ? { args } : {}),\n ...(env ? { env } : {}),\n };\n};\n\n/**\n * HTTP/stdio entry for clients whose docs make the transport a required field.\n * Claude Code skips an entry that has a `url` but no `type` (it reads the\n * entry as stdio and reports a configuration error), and CodeBuddy's field\n * table marks `type` as Required, fixed to `\"http\"` for the HTTP form and\n * `\"sse\"` for the SSE form.\n *\n * Stdio targets differ between the two clients. Claude Code reads a type-less\n * entry as stdio (https://code.claude.com/docs/en/mcp), so its stdio shape\n * keeps the plain command/args/env form with no `type` key. CodeBuddy's docs\n * mark `type` as Required with the fixed value `\"stdio\"`\n * (https://www.codebuddy.ai/docs/cli/mcp), so its stdio shape writes\n * `\"type\": \"stdio\"`; pass `stdioType` only for CodeBuddy.\n */\nconst typedTransportEntry = (\n target: ClientSnippetTarget,\n stdioType: boolean = false,\n): Record<string, unknown> => {\n if (!isHttpTarget(target)) {\n return stdioType ? { type: 'stdio', ...stdioEntry(target) } : stdioEntry(target);\n }\n\n const headers = definedHeaders(target);\n return {\n type: target.type === 'sse' ? 'sse' : 'http',\n url: target.url ?? '',\n ...(headers ? { headers } : {}),\n };\n};\n\n// This helper feeds the generic `mcpServers` shape for the generic-mcp-servers,\n// qoder, cursor, windsurf, trae, zcode and workbuddy presets. Only qoder's\n// upstream docs carry a transport field for this shape; for the rest\n// (generic-mcp-servers, cursor, windsurf, trae, zcode, workbuddy) the\n// `type: \"sse\"` marker is added on the assumption that unknown keys are\n// ignored, so it is unverified against a field table. It is kept because an\n// SSE upstream must not be dialed as streamable HTTP.\nconst httpEntry = (target: ClientSnippetTarget): Record<string, unknown> => {\n const headers = definedHeaders(target);\n return {\n // Only `sse` is spelled out: it is the one HTTP transport a client might\n // otherwise misdial as streamable HTTP. Every other target keeps the bare\n // `url` + `headers` shape these presets always produced.\n ...(target.type === 'sse' ? { type: 'sse' } : {}),\n url: target.url ?? '',\n ...(headers ? { headers } : {}),\n };\n};\n\nconst withWrappedName = (\n wrapper: string,\n name: string,\n entry: Record<string, unknown>,\n): string => JSON.stringify({ [wrapper]: { [name]: entry } }, null, 2);\n\nconst jsonSnippet = (target: ClientSnippetTarget): string =>\n withWrappedName(\n 'mcpServers',\n target.name,\n isHttpTarget(target) ? httpEntry(target) : stdioEntry(target),\n );\n\n// VS Code wants the transport spelled out and can prompt for the token itself,\n// so the shared placeholder becomes an input reference plus an `inputs` entry.\nconst vscodeSnippet = (target: ClientSnippetTarget, options: ClientSnippetOptions): string => {\n const headers = definedHeaders(target);\n const placeholder = `Bearer ${ACCESS_TOKEN_PLACEHOLDER}`;\n const needsTokenInput = headers?.Authorization === placeholder;\n\n const body = needsTokenInput\n ? { ...headers, Authorization: `Bearer \\${input:${TOKEN_INPUT_ID}}` }\n : headers;\n\n const entry = isHttpTarget(target)\n ? {\n // VS Code distinguishes the SSE transport from streamable HTTP.\n type: target.type === 'sse' ? 'sse' : 'http',\n url: target.url,\n ...(body ? { headers: body } : {}),\n }\n : { type: 'stdio', ...stdioEntry(target) };\n\n return JSON.stringify(\n {\n servers: { [target.name]: entry },\n ...(needsTokenInput\n ? {\n inputs: [\n {\n id: TOKEN_INPUT_ID,\n type: 'promptString',\n description: options.tokenPromptDescription ?? 'MCPHub access token',\n password: true,\n },\n ],\n }\n : {}),\n },\n null,\n 2,\n );\n};\n\nconst TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nconst tomlKey = (key: string): string => (TOML_BARE_KEY.test(key) ? key : JSON.stringify(key));\nconst tomlValue = (value: string): string => JSON.stringify(value);\nconst tomlInlineTable = (entries: Record<string, string>): string =>\n `{ ${Object.entries(entries)\n .map(([key, value]) => `${tomlKey(key)} = ${tomlValue(value)}`)\n .join(', ')} }`;\n\n// Codex calls the header map `http_headers` (there is no `headers` key), and\n// needs quoted table keys for names that are not bare TOML keys - group names\n// may contain spaces or CJK. Note `#[schemars(deny_unknown_fields)]` in\n// `mcp_types.rs` only constrains the generated JSON Schema; the Rust struct\n// itself has no `#[serde(deny_unknown_fields)]`, so TOML parsing may silently\n// ignore unknown fields instead of rejecting them.\nconst codexSnippet = (target: ClientSnippetTarget): string => {\n const key = tomlKey(target.name);\n const lines = [`[mcp_servers.${key}]`];\n\n if (isHttpTarget(target)) {\n lines.push(`url = ${tomlValue(target.url ?? '')}`);\n const headers = definedHeaders(target);\n if (headers) {\n lines.push(`http_headers = ${tomlInlineTable(headers)}`);\n }\n return lines.join('\\n');\n }\n\n lines.push(`command = ${tomlValue(target.command ?? '')}`);\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n if (args.length > 0) {\n lines.push(`args = [${args.map(tomlValue).join(', ')}]`);\n }\n const env = definedEnv(target);\n if (env) {\n lines.push('', `[mcp_servers.${key}.env]`);\n for (const [name, value] of Object.entries(env)) {\n lines.push(`${tomlKey(name)} = ${tomlValue(value)}`);\n }\n }\n return lines.join('\\n');\n};\n\nconst openCodeSnippet = (target: ClientSnippetTarget): string => {\n if (!isHttpTarget(target)) {\n const env = definedEnv(target);\n const args = target.args?.filter((arg) => arg !== undefined) ?? [];\n return JSON.stringify(\n {\n mcp: {\n [target.name]: {\n type: 'local',\n command: [target.command ?? '', ...args],\n ...(env ? { environment: env } : {}),\n },\n },\n },\n null,\n 2,\n );\n }\n\n const headers = definedHeaders(target);\n return JSON.stringify(\n {\n mcp: {\n [target.name]: {\n type: 'remote',\n url: target.url,\n ...(headers ? { headers } : {}),\n },\n },\n },\n null,\n 2,\n );\n};\n\n// Cherry Studio's `ProtocolMcpServerConfigSchema` is a strict object: remote\n// servers use `baseUrl` + `type: 'sse' | 'streamableHttp'` (no `url` key) and\n// stdio servers use command/args/env. The install-time metadata\n// (`installSource`, `isTrusted`, `installedAt`, `isActive: false`) belongs to\n// `ProtocolMcpServerInstallSchema`, the install *request* schema, not to this\n// hand-pasted config; an entry carrying `isActive` would not satisfy\n// `ProtocolMcpServerConfigSchema` (which has no such key).\nconst cherryStudioSnippet = (target: ClientSnippetTarget): string => {\n const base = {\n name: target.name,\n description: target.description ?? '',\n };\n\n const headers = definedHeaders(target);\n const entry = isHttpTarget(target)\n ? {\n ...base,\n // The schema accepts exactly two remote transports; keep SSE upstreams\n // spelled out so Cherry Studio does not dial them as streamable HTTP.\n type: target.type === 'sse' ? 'sse' : 'streamableHttp',\n baseUrl: target.url,\n ...(headers ? { headers } : {}),\n }\n : { ...base, type: 'stdio', ...stdioEntry(target) };\n\n return withWrappedName('mcpServers', target.name, entry);\n};\n\nconst CLI_SAFE = /^[A-Za-z0-9._:@/=-]+$/;\n\n// Single quotes are the only reliable POSIX quoting for a pasted command:\n// double quotes still let the shell expand `$(...)`, backticks and `${...}`\n// inside them. Within single quotes nothing is expanded, so the only character\n// needing special handling is `'` itself, escaped as `'\\''` (close quote,\n// escaped quote, reopen).\nconst shellQuote = (value: string): string => `'${value.replace(/'/g, \"'\\\\''\")}'`;\n\nconst cliArg = (value: string): string =>\n CLI_SAFE.test(value) ? value : shellQuote(value);\n\n// Claude Code's one-liner equivalent of the JSON block, in the documented\n// HTTP shape: `claude mcp add --transport <t> <name> <url> [--header ...]`.\n//\n// A positional that starts with `-` cannot be spelled safely in that shape:\n// shell quoting is gone by the time the CLI's tokenizer sees the value, and\n// the documented `--` separator belongs to the stdio form, so relying on it\n// for the HTTP form would be an unverified parsing assumption. Since\n// SERVER_NAME_PATTERN admits names like `-foo`, a name or URL starting with\n// a dash omits the command block outright - the JSON config block still\n// carries the full entry - rather than emitting a command the CLI would\n// misparse as one of its own options.\nconst claudeCodeCommand = (target: ClientSnippetTarget): string | undefined => {\n if (!isHttpTarget(target)) {\n return undefined;\n }\n\n if (target.name.startsWith('-') || (target.url ?? '').startsWith('-')) {\n return undefined;\n }\n\n const parts = [\n 'claude',\n 'mcp',\n 'add',\n '--transport',\n // SSE endpoints must not be dialed as streamable HTTP.\n target.type === 'sse' ? 'sse' : 'http',\n cliArg(target.name),\n cliArg(target.url ?? ''),\n ];\n for (const [name, value] of Object.entries(definedHeaders(target) ?? {})) {\n parts.push('--header', cliArg(`${name}: ${value}`));\n }\n return parts.join(' ');\n};\n\nconst asString = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined;\n\n/** Narrow a stored `type` to the four legal server types; anything else is dropped. */\nconst asServerType = (value: unknown): ClientSnippetTarget['type'] => {\n switch (value) {\n case 'stdio':\n case 'sse':\n case 'streamable-http':\n case 'openapi':\n return value;\n default:\n return undefined;\n }\n};\n\nconst asStringRecord = (value: unknown): Record<string, string> | undefined => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n );\n return entries.length > 0 ? Object.fromEntries(entries) : undefined;\n};\n\nconst asStringArray = (value: unknown): string[] | undefined => {\n if (!Array.isArray(value)) {\n return undefined;\n }\n\n const items = value.filter((item): item is string => typeof item === 'string');\n return items.length > 0 ? items : undefined;\n};\n\n/**\n * Narrow a stored server configuration (the `mcp-settings/export` payload\n * carries MCPHub's internal fields too) down to what a client snippet needs.\n */\nexport const toClientSnippetTarget = (\n name: string,\n config: Record<string, unknown> | undefined,\n): ClientSnippetTarget => {\n const source = config ?? {};\n\n return {\n name,\n ...(asServerType(source.type) ? { type: asServerType(source.type) } : {}),\n ...(asString(source.url) ? { url: asString(source.url) } : {}),\n ...(asStringRecord(source.headers) ? { headers: asStringRecord(source.headers) } : {}),\n ...(asString(source.command) ? { command: asString(source.command) } : {}),\n ...(asStringArray(source.args) ? { args: asStringArray(source.args) } : {}),\n ...(asStringRecord(source.env) ? { env: asStringRecord(source.env) } : {}),\n ...(asString(source.description) ? { description: asString(source.description) } : {}),\n };\n};\n\nconst configSnippet = (\n id: ClientSnippetId,\n target: ClientSnippetTarget,\n options: ClientSnippetOptions,\n): string => {\n switch (id) {\n case 'generic-mcp-servers':\n return jsonSnippet(target);\n case 'generic-http':\n return JSON.stringify(\n isHttpTarget(target)\n ? { type: 'http', ...httpEntry(target) }\n : stdioEntry(target),\n null,\n 2,\n );\n case 'vscode':\n return vscodeSnippet(target, options);\n case 'codex':\n return codexSnippet(target);\n case 'opencode':\n return openCodeSnippet(target);\n case 'cherry-studio':\n return cherryStudioSnippet(target);\n case 'claude-code':\n return withWrappedName('mcpServers', target.name, typedTransportEntry(target));\n case 'codebuddy':\n // CodeBuddy's field table marks `type` as Required, fixed to `\"stdio\"`\n // for stdio entries, so unlike Claude Code it always carries the type.\n return withWrappedName('mcpServers', target.name, typedTransportEntry(target, true));\n default:\n return jsonSnippet(target);\n }\n};\n\nexport const buildClientSnippetBlocks = (\n id: ClientSnippetId,\n target: ClientSnippetTarget,\n options: ClientSnippetOptions = {},\n): ClientSnippetBlock[] => {\n // OpenAPI-backed servers carry neither a URL nor a command, so every preset\n // would degrade to filler like `command: \"\"`. Return no blocks and let the\n // dialog explain that there is no client-side transport to copy.\n if (!target.url && !target.command) {\n return [];\n }\n\n const blocks: ClientSnippetBlock[] = [{ kind: 'config', text: configSnippet(id, target, options) }];\n\n if (id === 'claude-code') {\n const command = claudeCodeCommand(target);\n if (command) {\n blocks.push({ kind: 'command', text: command });\n }\n }\n\n return blocks;\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { Check, Copy, X } from 'lucide-react';\nimport {\n buildClientSnippetBlocks,\n CLIENT_SNIPPET_PRESETS,\n DEFAULT_CLIENT_SNIPPET_ID,\n type ClientSnippetId,\n type ClientSnippetTarget,\n} from '@/utils/mcpClientSnippets';\nimport { copyText } from '@/utils/clipboard';\nimport { useToast } from '@/contexts/ToastContext';\n\ninterface CopyClientConfigDialogProps {\n isOpen: boolean;\n onClose: () => void;\n /** Target to render; `null` keeps the dialog closed. */\n target: ClientSnippetTarget | null;\n}\n\n/**\n * Per-client MCP configuration presets for the copy actions (#1165): one tab\n * per client, each with the path it belongs in and a ready-to-paste snippet.\n */\nconst CopyClientConfigDialog = ({ isOpen, onClose, target }: CopyClientConfigDialogProps) => {\n const { t } = useTranslation();\n const { showToast } = useToast();\n const [activeId, setActiveId] = useState<ClientSnippetId>(DEFAULT_CLIENT_SNIPPET_ID);\n const [copiedBlock, setCopiedBlock] = useState<string | null>(null);\n const dialogRef = useRef<HTMLDivElement>(null);\n const returnFocusRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!isOpen) {\n setCopiedBlock(null);\n setActiveId(DEFAULT_CLIENT_SNIPPET_ID);\n }\n }, [isOpen]);\n\n // Like the other hand-rolled modals in this codebase the dialog does not trap\n // focus; it does move focus inside on open and hand it back to the trigger on\n // close, so keyboard users are not left behind on the page underneath.\n useEffect(() => {\n if (!isOpen) return;\n returnFocusRef.current =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n dialogRef.current?.focus();\n\n return () => {\n returnFocusRef.current?.focus();\n };\n }, [isOpen]);\n\n useEffect(() => {\n if (!isOpen) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') onClose();\n };\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [isOpen, onClose]);\n\n const blocks = useMemo(\n () =>\n target\n ? buildClientSnippetBlocks(activeId, target, {\n tokenPromptDescription: t('clientConfig.tokenPrompt'),\n })\n : [],\n [activeId, target, t],\n );\n\n if (!isOpen || !target) {\n return null;\n }\n\n const handleCopy = async (key: string, value: string) => {\n const ok = await copyText(value);\n if (!ok) {\n showToast(t('common.copyFailed'), 'error');\n return;\n }\n setCopiedBlock(key);\n showToast(t('common.copySuccess'), 'success');\n setTimeout(() => setCopiedBlock((current) => (current === key ? null : current)), 1500);\n };\n\n return (\n <div\n className=\"fixed inset-0 bg-black/50 z-[100] flex items-center justify-center p-4\"\n onClick={(event) => {\n if (event.target === event.currentTarget) onClose();\n }}\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n className=\"hub-card w-full max-w-2xl\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={t('clientConfig.title')}\n >\n <div\n className=\"flex items-start justify-between gap-3 px-4 py-3\"\n style={{ borderBottom: '1px solid var(--hub-line-2)' }}\n >\n <div className=\"min-w-0\">\n <div className=\"text-[14px] font-medium\" style={{ color: 'var(--hub-ink)' }}>\n {t('clientConfig.title')}\n </div>\n <div className=\"text-[12px] mt-0.5\" style={{ color: 'var(--hub-ink-3)' }}>\n {t('clientConfig.description', { name: target.name })}\n </div>\n </div>\n <button className=\"hub-icon-btn sm\" onClick={onClose} aria-label={t('app.closeButton')}>\n <X size={13} />\n </button>\n </div>\n\n {/* A target with neither URL nor command (OpenAPI-backed servers) has no\n transport, so every preset returns the same empty result: offering 14\n clickable tabs would only suggest the dialog is broken. The tab strip\n and its per-client hint are therefore hidden and only the explanation\n below is shown. */}\n {blocks.length > 0 && (\n <div className=\"px-4 pt-3\">\n <div\n className=\"flex flex-wrap gap-1\"\n role=\"tablist\"\n aria-label={t('clientConfig.title')}\n >\n {CLIENT_SNIPPET_PRESETS.map((preset) => {\n const active = preset.id === activeId;\n return (\n <button\n key={preset.id}\n role=\"tab\"\n aria-selected={active}\n onClick={() => setActiveId(preset.id)}\n className=\"px-2.5 py-1 rounded-md text-[12px] transition-colors hover:bg-[var(--hub-surface-hover)]\"\n style={{\n background: active ? 'var(--hub-surface)' : 'transparent',\n border: '1px solid ' + (active ? 'var(--hub-line)' : 'transparent'),\n color: active ? 'var(--hub-ink)' : 'var(--hub-ink-2)',\n }}\n >\n {t(`clientConfig.clients.${preset.id}.label`)}\n </button>\n );\n })}\n </div>\n\n <p className=\"text-[12px] mt-3\" style={{ color: 'var(--hub-ink-3)' }}>\n {t(`clientConfig.clients.${activeId}.hint`)}\n </p>\n </div>\n )}\n\n <div className=\"px-4 pb-4 pt-2 space-y-3 max-h-[60vh] overflow-auto\">\n {blocks.length === 0 ? (\n // OpenAPI-backed servers have no client-side transport to copy.\n <p className=\"text-[12px]\" style={{ color: 'var(--hub-ink-3)' }}>\n {t('clientConfig.noTransport')}\n </p>\n ) : (\n blocks.map((block) => {\n const key = `${activeId}:${block.kind}`;\n const copied = copiedBlock === key;\n return (\n <div key={key}>\n <div className=\"flex items-center justify-between mb-1\">\n <span className=\"text-[12px]\" style={{ color: 'var(--hub-ink-2)' }}>\n {t(\n block.kind === 'command'\n ? 'clientConfig.commandBlock'\n : 'clientConfig.configBlock',\n )}\n </span>\n <button\n className=\"hub-btn sm\"\n onClick={() => void handleCopy(key, block.text)}\n >\n {copied ? <Check size={12} className=\"text-[var(--hub-ok)]\" /> : <Copy size={12} />}\n {copied ? t('clientConfig.copied') : t('common.copy')}\n </button>\n </div>\n <pre\n className=\"hub-mono m-0 p-3 rounded-md overflow-auto\"\n style={{\n background: 'var(--hub-bg-2)',\n border: '1px solid var(--hub-line-2)',\n fontSize: 12,\n lineHeight: 1.5,\n color: 'var(--hub-ink-2)',\n maxHeight: 320,\n }}\n >\n <code>{block.text}</code>\n </pre>\n </div>\n );\n })\n )}\n </div>\n\n <div\n className=\"flex justify-end px-4 py-3\"\n style={{ borderTop: '1px solid var(--hub-line-2)' }}\n >\n <button className=\"hub-btn\" onClick={onClose}>\n {t('common.cancel')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default CopyClientConfigDialog;\n","import type { Server } from '../types';\n\n// Ignore transient polling metadata, but include full definitions: equal tool\n// counts do not imply equal context costs.\nexport const serverCostRevision = (servers: Server[]): string =>\n JSON.stringify(\n servers.map(({ name, status, enabled, tools, prompts, resources }) => ({\n name,\n status,\n enabled,\n tools,\n prompts,\n resources,\n })),\n );\n"],"names":["normalizeDescription","description","fallback","getToolDescriptionInfo","tool","noDescriptionText","currentDescription","DEFAULT_CLIENT_SNIPPET_ID","CLIENT_SNIPPET_PRESETS","ACCESS_TOKEN_PLACEHOLDER","TOKEN_INPUT_ID","isHttpTarget","target","definedHeaders","entries","value","definedEnv","stdioEntry","args","_a","arg","env","typedTransportEntry","stdioType","headers","httpEntry","withWrappedName","wrapper","name","entry","jsonSnippet","vscodeSnippet","options","placeholder","needsTokenInput","body","TOML_BARE_KEY","tomlKey","key","tomlValue","tomlInlineTable","codexSnippet","lines","openCodeSnippet","cherryStudioSnippet","base","CLI_SAFE","shellQuote","cliArg","claudeCodeCommand","parts","asString","asServerType","asStringRecord","asStringArray","items","item","toClientSnippetTarget","config","source","configSnippet","id","buildClientSnippetBlocks","blocks","command","CopyClientConfigDialog","isOpen","onClose","useTranslation","showToast","useToast","activeId","setActiveId","useState","copiedBlock","setCopiedBlock","dialogRef","useRef","returnFocusRef","useEffect","onKeyDown","event","useMemo","handleCopy","copyText","current","jsx","jsxs","X","preset","active","block","copied","Check","Copy","serverCostRevision","servers","status","enabled","tools","prompts","resources"],"mappings":"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"}