botmux 2.98.4-canary.2 → 2.99.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 (205) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/adapters/cli/genius.d.ts +4 -0
  4. package/dist/adapters/cli/genius.d.ts.map +1 -0
  5. package/dist/adapters/cli/genius.js +216 -0
  6. package/dist/adapters/cli/genius.js.map +1 -0
  7. package/dist/adapters/cli/registry.d.ts +2 -1
  8. package/dist/adapters/cli/registry.d.ts.map +1 -1
  9. package/dist/adapters/cli/registry.js +3 -1
  10. package/dist/adapters/cli/registry.js.map +1 -1
  11. package/dist/adapters/cli/types.d.ts +1 -1
  12. package/dist/adapters/cli/types.d.ts.map +1 -1
  13. package/dist/core/dashboard-rows.d.ts +1 -1
  14. package/dist/core/dashboard-rows.d.ts.map +1 -1
  15. package/dist/core/dashboard-rows.js +2 -1
  16. package/dist/core/dashboard-rows.js.map +1 -1
  17. package/dist/dashboard/session-card-model.d.ts +2 -2
  18. package/dist/dashboard/session-card-model.d.ts.map +1 -1
  19. package/dist/dashboard/session-card-model.js +5 -2
  20. package/dist/dashboard/session-card-model.js.map +1 -1
  21. package/dist/dashboard/web/app.d.ts.map +1 -1
  22. package/dist/dashboard/web/app.js +28 -63
  23. package/dist/dashboard/web/app.js.map +1 -1
  24. package/dist/dashboard/web/bot-defaults-page.d.ts +3 -0
  25. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -0
  26. package/dist/dashboard/web/bot-defaults-page.js +23 -0
  27. package/dist/dashboard/web/bot-defaults-page.js.map +1 -0
  28. package/dist/dashboard/web/bot-defaults.d.ts +2 -1
  29. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  30. package/dist/dashboard/web/bot-defaults.js +30 -30
  31. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  32. package/dist/dashboard/web/connectors-page.d.ts +14 -0
  33. package/dist/dashboard/web/connectors-page.d.ts.map +1 -0
  34. package/dist/dashboard/web/connectors-page.js +303 -0
  35. package/dist/dashboard/web/connectors-page.js.map +1 -0
  36. package/dist/dashboard/web/connectors.d.ts +1 -12
  37. package/dist/dashboard/web/connectors.d.ts.map +1 -1
  38. package/dist/dashboard/web/connectors.js +1 -325
  39. package/dist/dashboard/web/connectors.js.map +1 -1
  40. package/dist/dashboard/web/dashboard-routes.d.ts +14 -0
  41. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -0
  42. package/dist/dashboard/web/dashboard-routes.js +176 -0
  43. package/dist/dashboard/web/dashboard-routes.js.map +1 -0
  44. package/dist/dashboard/web/groups-page.d.ts +3 -0
  45. package/dist/dashboard/web/groups-page.d.ts.map +1 -0
  46. package/dist/dashboard/web/groups-page.js +26 -0
  47. package/dist/dashboard/web/groups-page.js.map +1 -0
  48. package/dist/dashboard/web/groups.d.ts +1 -1
  49. package/dist/dashboard/web/groups.d.ts.map +1 -1
  50. package/dist/dashboard/web/groups.js +113 -52
  51. package/dist/dashboard/web/groups.js.map +1 -1
  52. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  53. package/dist/dashboard/web/i18n.js +18 -0
  54. package/dist/dashboard/web/i18n.js.map +1 -1
  55. package/dist/dashboard/web/insights-page.d.ts +3 -0
  56. package/dist/dashboard/web/insights-page.d.ts.map +1 -0
  57. package/dist/dashboard/web/insights-page.js +26 -0
  58. package/dist/dashboard/web/insights-page.js.map +1 -0
  59. package/dist/dashboard/web/insights.d.ts +18 -1
  60. package/dist/dashboard/web/insights.d.ts.map +1 -1
  61. package/dist/dashboard/web/insights.js +25 -61
  62. package/dist/dashboard/web/insights.js.map +1 -1
  63. package/dist/dashboard/web/office-page.d.ts +3 -0
  64. package/dist/dashboard/web/office-page.d.ts.map +1 -0
  65. package/dist/dashboard/web/office-page.js +149 -0
  66. package/dist/dashboard/web/office-page.js.map +1 -0
  67. package/dist/dashboard/web/office.d.ts +1 -1
  68. package/dist/dashboard/web/office.d.ts.map +1 -1
  69. package/dist/dashboard/web/office.js +1 -139
  70. package/dist/dashboard/web/office.js.map +1 -1
  71. package/dist/dashboard/web/overview-page.d.ts +3 -0
  72. package/dist/dashboard/web/overview-page.d.ts.map +1 -0
  73. package/dist/dashboard/web/overview-page.js +149 -0
  74. package/dist/dashboard/web/overview-page.js.map +1 -0
  75. package/dist/dashboard/web/overview.d.ts +2 -2
  76. package/dist/dashboard/web/overview.d.ts.map +1 -1
  77. package/dist/dashboard/web/overview.js +14 -3
  78. package/dist/dashboard/web/overview.js.map +1 -1
  79. package/dist/dashboard/web/react-hooks.d.ts +8 -0
  80. package/dist/dashboard/web/react-hooks.d.ts.map +1 -0
  81. package/dist/dashboard/web/react-hooks.js +43 -0
  82. package/dist/dashboard/web/react-hooks.js.map +1 -0
  83. package/dist/dashboard/web/react-mount.d.ts +4 -0
  84. package/dist/dashboard/web/react-mount.d.ts.map +1 -0
  85. package/dist/dashboard/web/react-mount.js +7 -0
  86. package/dist/dashboard/web/react-mount.js.map +1 -0
  87. package/dist/dashboard/web/roles-page.d.ts +4 -0
  88. package/dist/dashboard/web/roles-page.d.ts.map +1 -0
  89. package/dist/dashboard/web/roles-page.js +26 -0
  90. package/dist/dashboard/web/roles-page.js.map +1 -0
  91. package/dist/dashboard/web/roles.d.ts +1 -2
  92. package/dist/dashboard/web/roles.d.ts.map +1 -1
  93. package/dist/dashboard/web/roles.js +189 -135
  94. package/dist/dashboard/web/roles.js.map +1 -1
  95. package/dist/dashboard/web/route-lifecycle.d.ts +14 -0
  96. package/dist/dashboard/web/route-lifecycle.d.ts.map +1 -0
  97. package/dist/dashboard/web/route-lifecycle.js +29 -0
  98. package/dist/dashboard/web/route-lifecycle.js.map +1 -0
  99. package/dist/dashboard/web/schedules-page.d.ts +14 -0
  100. package/dist/dashboard/web/schedules-page.d.ts.map +1 -0
  101. package/dist/dashboard/web/schedules-page.js +84 -0
  102. package/dist/dashboard/web/schedules-page.js.map +1 -0
  103. package/dist/dashboard/web/schedules.d.ts +1 -1
  104. package/dist/dashboard/web/schedules.d.ts.map +1 -1
  105. package/dist/dashboard/web/schedules.js +1 -115
  106. package/dist/dashboard/web/schedules.js.map +1 -1
  107. package/dist/dashboard/web/sessions-page.d.ts +3 -0
  108. package/dist/dashboard/web/sessions-page.d.ts.map +1 -0
  109. package/dist/dashboard/web/sessions-page.js +28 -0
  110. package/dist/dashboard/web/sessions-page.js.map +1 -0
  111. package/dist/dashboard/web/sessions.d.ts +4 -1
  112. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  113. package/dist/dashboard/web/sessions.js +43 -10
  114. package/dist/dashboard/web/sessions.js.map +1 -1
  115. package/dist/dashboard/web/settings-page.d.ts +3 -0
  116. package/dist/dashboard/web/settings-page.d.ts.map +1 -0
  117. package/dist/dashboard/web/settings-page.js +387 -0
  118. package/dist/dashboard/web/settings-page.js.map +1 -0
  119. package/dist/dashboard/web/settings.d.ts +1 -1
  120. package/dist/dashboard/web/settings.d.ts.map +1 -1
  121. package/dist/dashboard/web/settings.js +1 -534
  122. package/dist/dashboard/web/settings.js.map +1 -1
  123. package/dist/dashboard/web/skills-page.d.ts +33 -0
  124. package/dist/dashboard/web/skills-page.d.ts.map +1 -0
  125. package/dist/dashboard/web/skills-page.js +515 -0
  126. package/dist/dashboard/web/skills-page.js.map +1 -0
  127. package/dist/dashboard/web/skills.d.ts +1 -1
  128. package/dist/dashboard/web/skills.d.ts.map +1 -1
  129. package/dist/dashboard/web/skills.js +1 -711
  130. package/dist/dashboard/web/skills.js.map +1 -1
  131. package/dist/dashboard/web/store.d.ts +9 -0
  132. package/dist/dashboard/web/store.d.ts.map +1 -1
  133. package/dist/dashboard/web/store.js +19 -2
  134. package/dist/dashboard/web/store.js.map +1 -1
  135. package/dist/dashboard/web/team-federation-page.d.ts +4 -0
  136. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -0
  137. package/dist/dashboard/web/team-federation-page.js +47 -0
  138. package/dist/dashboard/web/team-federation-page.js.map +1 -0
  139. package/dist/dashboard/web/team-federation.d.ts +2 -2
  140. package/dist/dashboard/web/team-federation.d.ts.map +1 -1
  141. package/dist/dashboard/web/team-federation.js +104 -97
  142. package/dist/dashboard/web/team-federation.js.map +1 -1
  143. package/dist/dashboard/web/v3-page.d.ts +3 -0
  144. package/dist/dashboard/web/v3-page.d.ts.map +1 -0
  145. package/dist/dashboard/web/v3-page.js +27 -0
  146. package/dist/dashboard/web/v3-page.js.map +1 -0
  147. package/dist/dashboard/web/v3.d.ts +2 -1
  148. package/dist/dashboard/web/v3.d.ts.map +1 -1
  149. package/dist/dashboard/web/v3.js +18 -43
  150. package/dist/dashboard/web/v3.js.map +1 -1
  151. package/dist/dashboard/web/whiteboards-page.d.ts +3 -0
  152. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -0
  153. package/dist/dashboard/web/whiteboards-page.js +203 -0
  154. package/dist/dashboard/web/whiteboards-page.js.map +1 -0
  155. package/dist/dashboard/web/whiteboards.d.ts +1 -1
  156. package/dist/dashboard/web/whiteboards.d.ts.map +1 -1
  157. package/dist/dashboard/web/whiteboards.js +1 -275
  158. package/dist/dashboard/web/whiteboards.js.map +1 -1
  159. package/dist/dashboard-web/app.js +36 -2550
  160. package/dist/dashboard-web/chunks/bot-defaults-page-3EV7K6LJ.js +358 -0
  161. package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +1 -0
  162. package/dist/dashboard-web/chunks/chunk-76J7PNZI.js +1 -0
  163. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +1 -0
  164. package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +54 -0
  165. package/dist/dashboard-web/chunks/chunk-JXLHRRJD.js +31 -0
  166. package/dist/dashboard-web/chunks/chunk-NASYW5FD.js +1 -0
  167. package/dist/dashboard-web/chunks/connectors-XPZKEHF6.js +1 -0
  168. package/dist/dashboard-web/chunks/groups-page-C54PVKYP.js +207 -0
  169. package/dist/dashboard-web/chunks/insights-page-BBQYR7WY.js +309 -0
  170. package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +1 -0
  171. package/dist/dashboard-web/chunks/overview-page-5PJCAMID.js +1 -0
  172. package/dist/dashboard-web/chunks/roles-page-APUDLPVM.js +98 -0
  173. package/dist/dashboard-web/chunks/schedules-WG2UKHSO.js +1 -0
  174. package/dist/dashboard-web/chunks/sessions-page-F3U5NFPD.js +238 -0
  175. package/dist/dashboard-web/chunks/settings-page-OSBZNCXL.js +2 -0
  176. package/dist/dashboard-web/chunks/skills-page-Y3VUAOYG.js +2 -0
  177. package/dist/dashboard-web/chunks/team-federation-page-CRH3WZPH.js +10 -0
  178. package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +149 -0
  179. package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +1 -0
  180. package/dist/dashboard-web/chunks/workflows-UB7OPYGI.js +184 -0
  181. package/dist/dashboard-web/index.html +1 -1
  182. package/dist/dashboard-web/style.css +82 -1
  183. package/dist/dashboard.js +5 -6
  184. package/dist/dashboard.js.map +1 -1
  185. package/dist/i18n/en.d.ts.map +1 -1
  186. package/dist/i18n/en.js +1 -0
  187. package/dist/i18n/en.js.map +1 -1
  188. package/dist/i18n/zh.d.ts.map +1 -1
  189. package/dist/i18n/zh.js +1 -0
  190. package/dist/i18n/zh.js.map +1 -1
  191. package/dist/im/lark/card-builder.d.ts.map +1 -1
  192. package/dist/im/lark/card-builder.js +1 -0
  193. package/dist/im/lark/card-builder.js.map +1 -1
  194. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  195. package/dist/im/lark/event-dispatcher.js +7 -0
  196. package/dist/im/lark/event-dispatcher.js.map +1 -1
  197. package/dist/im/lark/sessions-card.d.ts.map +1 -1
  198. package/dist/im/lark/sessions-card.js +4 -1
  199. package/dist/im/lark/sessions-card.js.map +1 -1
  200. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  201. package/dist/setup/bot-config-editor.js +16 -14
  202. package/dist/setup/bot-config-editor.js.map +1 -1
  203. package/dist/worker.js +1 -1
  204. package/dist/worker.js.map +1 -1
  205. package/package.json +8 -2
@@ -1,712 +1,2 @@
1
- import { botAvatarHtml, escapeHtml, loadingHtml, t } from './ui.js';
2
- let state = { skills: [], nativeSkillGroups: [], bots: [], trustProjectSkills: 'off', delivery: 'auto' };
3
- let loadError = null;
4
- const INSTALLED_SKILLS_ROWS_PER_PAGE = 2;
5
- let installedSkillsPage = 0;
6
- let activeDiscoveryGroupKey = null;
7
- function pageHtml() {
8
- return `<section class="page">
9
- <div class="page-heading">
10
- <div>
11
- <p class="eyebrow">${t('nav.skills')}</p>
12
- <h1>${t('skills.title')}</h1>
13
- <p>${t('skills.subtitle')}</p>
14
- </div>
15
- <button type="button" id="skills-refresh">${t('skills.refresh')}</button>
16
- </div>
17
- <div id="skills-body"></div>
18
- </section>`;
19
- }
20
- function sourceLabel(skill) {
21
- const source = skill.source ?? {};
22
- if (source.type === 'github')
23
- return `github:${source.owner}/${source.repo}/${source.path ?? ''}`;
24
- if (source.type === 'git')
25
- return `${source.url ?? 'git'}#${source.path ?? ''}`;
26
- if (source.type === 'local-link')
27
- return nativeLibraryLabel(source.path) ?? t('skills.sourceLocalLink');
28
- if (source.type === 'local-copy')
29
- return t('skills.sourceBotmuxCopy');
30
- return String(source.type ?? 'unknown');
31
- }
32
- function nativeLibraryLabel(path) {
33
- const p = String(path ?? '').replace(/\\/g, '/');
34
- if (p.includes('/.codex/skills/'))
35
- return t('skills.sourceCodex');
36
- if (p.includes('/.claude/skills/'))
37
- return t('skills.sourceClaude');
38
- if (p.includes('/.trae/skills/'))
39
- return t('skills.sourceTrae');
40
- if (p.includes('/.cursor/skills/'))
41
- return t('skills.sourceCursor');
42
- if (p.includes('/.gemini/skills/'))
43
- return t('skills.sourceGemini');
44
- if (p.includes('/.config/opencode/skills/'))
45
- return t('skills.sourceOpenCode');
46
- return null;
47
- }
48
- function priorityNames(policy) {
49
- return (policy?.include ?? [])
50
- .filter((item) => item.startsWith('skill:'))
51
- .map((item) => item.slice('skill:'.length));
52
- }
53
- function policyReferenceCount(policy) {
54
- return priorityNames(policy).length;
55
- }
56
- function policyConfigured(policy) {
57
- return priorityNames(policy).length > 0;
58
- }
59
- function installedSkillNames() {
60
- return new Set(state.skills.map(skill => skill.name));
61
- }
62
- function discoveryGroupKey(group) {
63
- return `${group.cliId}\n${group.rootDir}`;
64
- }
65
- function discoveryGroups() {
66
- return state.nativeSkillGroups;
67
- }
68
- function activeDiscoveryGroup(groups) {
69
- if (groups.length === 0)
70
- return undefined;
71
- const active = activeDiscoveryGroupKey
72
- ? groups.find(group => discoveryGroupKey(group) === activeDiscoveryGroupKey)
73
- : undefined;
74
- const fallback = active ?? groups.find(group => group.skills.length > 0) ?? groups[0];
75
- activeDiscoveryGroupKey = discoveryGroupKey(fallback);
76
- return fallback;
77
- }
78
- function referencingBotLabels(skillName) {
79
- return state.bots
80
- .filter(bot => priorityNames(bot.skills).includes(skillName))
81
- .map(bot => bot.botName ?? bot.larkAppId);
82
- }
83
- function renderInstallForm() {
84
- return `<article class="bd-card skills-install-panel">
85
- <div class="skills-install-title">
86
- <h3 class="bd-section-title">${t('skills.install')}</h3>
87
- <span class="skills-help-tip">
88
- <button type="button" class="skills-help-button" aria-label="${t('skills.installInfoLabel')}">?</button>
89
- <span class="skills-help-popover" role="tooltip">${t('skills.installInfo')}</span>
90
- </span>
91
- </div>
92
- <div class="skills-install-grid">
93
- <label class="skills-source-label"><span>${t('skills.source')}</span>
94
- <div class="skills-source-control">
95
- <input type="text" data-install="source" placeholder="${t('skills.sourcePlaceholder')}">
96
- <button type="button" data-action="discover-native-skills">${t('skills.discover')}</button>
97
- </div>
98
- </label>
99
- <div class="bd-section-note skills-install-note">
100
- <span><strong>${t('skills.sourceHelpRemoteLabel')}</strong>${t('skills.sourceHelpRemote')}</span>
101
- <span><strong>${t('skills.sourceHelpLocalLabel')}</strong>${t('skills.sourceHelpLocal')}</span>
102
- </div>
103
- <label class="skills-install-field-wide"><span>${t('skills.path')}</span>
104
- <input type="text" data-install="path" placeholder="${t('skills.pathPlaceholder')}">
105
- </label>
106
- <label class="skills-install-field-wide"><span>${t('skills.ref')}</span>
107
- <input type="text" data-install="ref" placeholder="${t('skills.refPlaceholder')}">
108
- </label>
109
- </div>
110
- <div class="actions">
111
- <button type="button" class="primary" data-action="install">${t('skills.installSubmit')}</button>
112
- <span class="oncall-status" data-skills-status></span>
113
- </div>
114
- </article>`;
115
- }
116
- function renderDiscoveryDialog() {
117
- const installed = installedSkillNames();
118
- const groups = discoveryGroups();
119
- const active = activeDiscoveryGroup(groups);
120
- const content = !active
121
- ? `<p class="empty">${t('skills.discoverEmpty')}</p>`
122
- : `<div class="skills-discovery-tabs" role="tablist" aria-label="${t('skills.discoverTitle')}">
123
- ${groups.map(group => {
124
- const key = discoveryGroupKey(group);
125
- const selected = key === discoveryGroupKey(active);
126
- return `<button type="button" role="tab" data-discovery-tab="${escapeHtml(key)}" class="${selected ? 'selected' : ''}" aria-selected="${selected ? 'true' : 'false'}">
127
- <strong>${escapeHtml(group.label ?? group.cliId)}</strong>
128
- <small>${t('skills.skillCount', { count: group.skills.length })}</small>
129
- </button>`;
130
- }).join('')}
131
- </div>
132
- <div class="skills-discovery-path"><code>${escapeHtml(active.rootDir)}</code></div>
133
- ${groups.map(group => {
134
- const key = discoveryGroupKey(group);
135
- const selected = active && key === discoveryGroupKey(active);
136
- return `<section class="skills-discovery-group" data-discovery-panel="${escapeHtml(key)}" ${selected ? '' : 'hidden'}>
137
- ${group.skills.length === 0
138
- ? `<p class="empty">${t('skills.discoverGroupEmpty')}</p>`
139
- : `<div class="skills-discovery-list">
140
- ${group.skills.map(skill => {
141
- const already = installed.has(skill.name);
142
- const path = skill.rootDir ?? skill.source?.root ?? '';
143
- return `<label class="skills-discovery-row${already ? ' installed' : ''}">
144
- <input type="checkbox" data-discovered-skill value="${escapeHtml(path)}" ${already ? 'disabled' : ''}>
145
- <span>
146
- <strong>${escapeHtml(skill.name)}</strong>
147
- ${skill.description ? `<small>${escapeHtml(skill.description)}</small>` : ''}
148
- </span>
149
- ${already ? `<em>${t('skills.discoverRegistered')}</em>` : ''}
150
- </label>`;
151
- }).join('')}
152
- </div>`}
153
- </section>`;
154
- }).join('')}`;
155
- return `<dialog class="skills-discovery-dialog" id="skills-discovery-dialog">
156
- <article>
157
- <header>
158
- <h3>${t('skills.discoverTitle')}</h3>
159
- <p>${t('skills.discoverHelp')}</p>
160
- </header>
161
- <div class="skills-discovery-body">${content}</div>
162
- <footer class="actions">
163
- <button type="button" data-action="toggle-discovered-skills">${t('skills.discoverSelectAll')}</button>
164
- <button type="button" class="primary" data-action="register-discovered-skills">${t('skills.discoverRegister')}</button>
165
- <button type="button" data-action="close-discovery">${t('skills.discoverClose')}</button>
166
- </footer>
167
- </article>
168
- </dialog>`;
169
- }
170
- function renderInstalledSkills() {
171
- if (state.skills.length === 0)
172
- return `<p class="empty">${t('skills.empty')}</p>`;
173
- clampInstalledSkillsPage();
174
- const pageSize = installedSkillsPageSize();
175
- const start = installedSkillsPage * pageSize;
176
- const visibleSkills = state.skills.slice(start, start + pageSize);
177
- return `<div class="skills-list">${visibleSkills.map(skill => `
178
- <article class="skills-row skills-installed-card" data-skill="${escapeHtml(skill.name)}">
179
- <div class="skills-row-body">
180
- <strong>${escapeHtml(skill.name)}</strong>
181
- ${skill.description ? `<p>${escapeHtml(skill.description)}</p>` : ''}
182
- <small class="skills-source-badge">${escapeHtml(sourceLabel(skill))}</small>
183
- </div>
184
- <div class="skills-card-actions">
185
- <button type="button" data-action="update-skill">${t('skills.update')}</button>
186
- <button type="button" data-action="remove-skill">${t('skills.remove')}</button>
187
- </div>
188
- </article>`).join('')}</div>`;
189
- }
190
- function installedSkillsColumnCount() {
191
- const width = typeof window === 'undefined' ? 1440 : window.innerWidth;
192
- if (width >= 1600)
193
- return 4;
194
- if (width <= 620)
195
- return 1;
196
- if (width <= 980)
197
- return 2;
198
- return 3;
199
- }
200
- function installedSkillsPageSize() {
201
- return installedSkillsColumnCount() * INSTALLED_SKILLS_ROWS_PER_PAGE;
202
- }
203
- function installedSkillsPageCount() {
204
- return Math.max(1, Math.ceil(state.skills.length / installedSkillsPageSize()));
205
- }
206
- function clampInstalledSkillsPage() {
207
- installedSkillsPage = Math.min(Math.max(0, installedSkillsPage), installedSkillsPageCount() - 1);
208
- }
209
- function renderInstalledToolbar() {
210
- clampInstalledSkillsPage();
211
- const count = `<span class="skills-count-pill">${t('skills.skillCount', { count: state.skills.length })}</span>`;
212
- const pageCount = installedSkillsPageCount();
213
- if (pageCount <= 1)
214
- return count;
215
- return `<div class="skills-installed-toolbar">
216
- ${count}
217
- <div class="skills-pager">
218
- <button type="button" class="skills-pager-button" data-action="page-installed-skills" data-dir="-1" aria-label="${t('skills.prevPage')}" title="${t('skills.prevPage')}" ${installedSkillsPage === 0 ? 'disabled' : ''}>&lsaquo;</button>
219
- <span>${t('skills.pageStatus', { page: installedSkillsPage + 1, pages: pageCount })}</span>
220
- <button type="button" class="skills-pager-button" data-action="page-installed-skills" data-dir="1" aria-label="${t('skills.nextPage')}" title="${t('skills.nextPage')}" ${installedSkillsPage >= pageCount - 1 ? 'disabled' : ''}>&rsaquo;</button>
221
- </div>
222
- </div>`;
223
- }
224
- function renderGlobalPolicy() {
225
- const deliveryOptions = [
226
- ['auto', t('skills.deliveryAuto'), t('skills.deliveryAutoHelp')],
227
- ['prompt', t('skills.deliveryPrompt'), t('skills.deliveryPromptHelp')],
228
- ['native', t('skills.deliveryNative'), t('skills.deliveryNativeHelp')],
229
- ];
230
- return `<article class="bd-card skills-defaults-panel">
231
- <h3 class="bd-section-title">${t('skills.globalDefaults')}</h3>
232
- <div class="skills-control-block">
233
- <span class="skills-control-label">${t('skills.globalProject')}</span>
234
- <div class="skills-choice-group skills-choice-group-compact skills-project-group">
235
- ${[
236
- ['off', t('skills.globalProjectOff'), t('skills.globalProjectOffHelp')],
237
- ['all', t('skills.globalProjectAll'), t('skills.globalProjectAllHelp')],
238
- ].map(([value, label, help]) => `<button type="button" class="skills-choice${state.trustProjectSkills === value ? ' selected' : ''}" data-global-project-value="${value}" aria-pressed="${state.trustProjectSkills === value ? 'true' : 'false'}">
239
- <strong>${label}</strong><small>${help}</small>
240
- </button>`).join('')}
241
- </div>
242
- </div>
243
- <div class="skills-control-block">
244
- <span class="skills-control-label">${t('skills.globalDelivery')}</span>
245
- <div class="skills-choice-group skills-delivery-group">
246
- ${deliveryOptions.map(([value, label, help]) => `<button type="button" class="skills-choice${state.delivery === value ? ' selected' : ''}" data-global-delivery-value="${value}" aria-pressed="${state.delivery === value ? 'true' : 'false'}">
247
- <strong>${label}</strong><small>${help}</small>
248
- </button>`).join('')}
249
- </div>
250
- </div>
251
- </article>`;
252
- }
253
- function renderSkillPicker(bot) {
254
- const attached = new Set(priorityNames(bot.skills));
255
- const options = state.skills.filter(skill => !attached.has(skill.name));
256
- if (options.length === 0)
257
- return `<button type="button" disabled>${t('skills.attach')}</button>`;
258
- return `<select data-attach-picker>
259
- ${options.map(skill => `<option value="${escapeHtml(skill.name)}">${escapeHtml(skill.name)}</option>`).join('')}
260
- </select>
261
- <button type="button" data-action="attach-skill">${t('skills.attach')}</button>`;
262
- }
263
- function renderBotPolicy(bot) {
264
- if (bot.error) {
265
- return `<article class="bd-card skills-bot-card" data-appid="${escapeHtml(bot.larkAppId)}">
266
- <header>${botAvatarHtml({ name: bot.botName ?? bot.larkAppId, larkAppId: bot.larkAppId, size: 'sm' })}
267
- <strong>${escapeHtml(bot.botName ?? bot.larkAppId)}</strong></header>
268
- <p class="hint-warn-inline">${escapeHtml(bot.error)}</p>
269
- </article>`;
270
- }
271
- const names = priorityNames(bot.skills);
272
- const installed = installedSkillNames();
273
- return `<article class="bd-card skills-bot-card" data-appid="${escapeHtml(bot.larkAppId)}">
274
- <header class="skills-bot-head">
275
- ${botAvatarHtml({ name: bot.botName ?? bot.larkAppId, larkAppId: bot.larkAppId, size: 'sm', dot: 'ok' })}
276
- <div><strong>${escapeHtml(bot.botName ?? bot.larkAppId)}</strong><code>${escapeHtml(bot.larkAppId)}</code></div>
277
- <span class="skills-count-pill">${t('skills.skillCount', { count: names.length })}</span>
278
- </header>
279
- <section class="bd-section">
280
- <h3 class="bd-section-title">${t('skills.priority')}</h3>
281
- ${names.length === 0
282
- ? `<p class="bd-section-note">${t('skills.noPriority')}</p>`
283
- : `<div class="skills-chip-list">${names.map(name => {
284
- const dangling = !installed.has(name);
285
- return `<span class="skills-priority-row${dangling ? ' skills-priority-dangling' : ''}" title="${dangling ? escapeHtml(t('skills.dangling')) : ''}">
286
- <span class="skills-priority-name">${escapeHtml(name)}${dangling ? `<small>${t('skills.dangling')}</small>` : ''}</span>
287
- <button type="button" class="skills-priority-remove" data-action="detach-skill" data-name="${escapeHtml(name)}" aria-label="${escapeHtml(t('skills.detachNamed', { skill: name }))}">&times;</button>
288
- </span>`;
289
- }).join('')}</div>`}
290
- <div class="actions skills-attach-row">${renderSkillPicker(bot)}</div>
291
- </section>
292
- <span class="oncall-status" data-bot-status></span>
293
- </article>`;
294
- }
295
- function attachedSkillRefCount() {
296
- return state.bots.reduce((sum, bot) => sum + policyReferenceCount(bot.skills), 0);
297
- }
298
- function configuredBotCount() {
299
- return state.bots.filter(bot => policyConfigured(bot.skills)).length;
300
- }
301
- function renderOverview() {
302
- return `<section class="skills-overview">
303
- <div class="skills-overview-copy">
304
- <h2>${t('skills.overviewTitle')}</h2>
305
- <p>${t('skills.overviewBody')}</p>
306
- </div>
307
- <div class="skills-metric-strip">
308
- <span><small>${t('skills.metricInstalled')}</small><strong>${state.skills.length}</strong></span>
309
- <span><small>${t('skills.metricBots')}</small><strong>${configuredBotCount()}/${state.bots.length}</strong></span>
310
- <span><small>${t('skills.metricAttached')}</small><strong>${attachedSkillRefCount()}</strong></span>
311
- </div>
312
- </section>`;
313
- }
314
- function renderBotRailActions() {
315
- const count = `<span class="skills-count-pill">${t('skills.botCount', { count: state.bots.length })}</span>`;
316
- if (state.bots.length <= 3)
317
- return count;
318
- return `<div class="skills-bot-rail-actions">
319
- <button type="button" class="skills-rail-button" data-action="scroll-bots" data-dir="-1" aria-label="${t('skills.scrollBotsPrev')}" title="${t('skills.scrollBotsPrev')}">&lsaquo;</button>
320
- ${count}
321
- <button type="button" class="skills-rail-button" data-action="scroll-bots" data-dir="1" aria-label="${t('skills.scrollBotsNext')}" title="${t('skills.scrollBotsNext')}">&rsaquo;</button>
322
- </div>`;
323
- }
324
- function renderBody() {
325
- if (loadError)
326
- return `<p class="hint-warn">${escapeHtml(loadError)}</p>`;
327
- return `<div class="skills-page-grid">
328
- <aside class="skills-side-rail">
329
- ${renderGlobalPolicy()}
330
- ${renderInstallForm()}
331
- </aside>
332
- <section class="skills-main-panel">
333
- ${renderOverview()}
334
- <section class="skills-bots-panel">
335
- <div class="skills-section-head skills-section-head-row">
336
- <div>
337
- <h2>${t('skills.bots')}</h2>
338
- <p>${t('skills.botsHelp')}</p>
339
- </div>
340
- ${renderBotRailActions()}
341
- </div>
342
- <div class="skills-bot-grid">${state.bots.map(renderBotPolicy).join('')}</div>
343
- </section>
344
- <section class="bd-card skills-installed-panel">
345
- <div class="skills-section-head skills-section-head-row">
346
- <div>
347
- <h2>${t('skills.installed')}</h2>
348
- <p>${t('skills.installedHelp')}</p>
349
- </div>
350
- ${renderInstalledToolbar()}
351
- </div>
352
- ${renderInstalledSkills()}
353
- </section>
354
- </section>
355
- ${renderDiscoveryDialog()}
356
- </div>`;
357
- }
358
- async function loadData() {
359
- try {
360
- const [skillsRes, botsRes] = await Promise.all([
361
- fetch('/api/skills'),
362
- fetch('/api/bots'),
363
- ]);
364
- const skillsBody = await skillsRes.json().catch(() => ({}));
365
- const botsBody = await botsRes.json().catch(() => ({}));
366
- if (!skillsRes.ok) {
367
- const error = skillsBody?.error ?? `skills HTTP ${skillsRes.status}`;
368
- throw new Error(error === 'not_found_yet' || error === 'not_found' ? t('skills.apiUnavailable') : error);
369
- }
370
- if (!botsRes.ok)
371
- throw new Error(botsBody?.error ?? `bots HTTP ${botsRes.status}`);
372
- state = {
373
- skills: Array.isArray(skillsBody.skills) ? skillsBody.skills : [],
374
- nativeSkillGroups: Array.isArray(skillsBody.nativeSkillGroups) ? skillsBody.nativeSkillGroups : [],
375
- bots: Array.isArray(botsBody.bots) ? botsBody.bots : [],
376
- trustProjectSkills: skillsBody.trustProjectSkills === 'all' ? 'all' : 'off',
377
- delivery: skillsBody.delivery === 'prompt' || skillsBody.delivery === 'native' ? skillsBody.delivery : 'auto',
378
- };
379
- clampInstalledSkillsPage();
380
- loadError = null;
381
- }
382
- catch (err) {
383
- loadError = err?.message ?? String(err);
384
- }
385
- }
386
- async function jsonRequest(url, init) {
387
- const r = await fetch(url, {
388
- ...init,
389
- headers: { 'content-type': 'application/json', ...(init.headers ?? {}) },
390
- });
391
- const body = await r.json().catch(() => ({}));
392
- if (!r.ok || body.ok === false) {
393
- const err = new Error(body?.error ?? `HTTP ${r.status}`);
394
- err.status = r.status;
395
- err.body = body;
396
- throw err;
397
- }
398
- return body;
399
- }
400
- function sleep(ms) {
401
- return new Promise(resolve => setTimeout(resolve, ms));
402
- }
403
- export async function renderSkillsPage(root) {
404
- root.innerHTML = pageHtml();
405
- const bodyEl = root.querySelector('#skills-body');
406
- const refreshBtn = root.querySelector('#skills-refresh');
407
- async function refresh() {
408
- bodyEl.innerHTML = loadingHtml();
409
- await loadData();
410
- rerender();
411
- }
412
- function status(scope) {
413
- return scope?.querySelector('[data-skills-status], [data-bot-status]') ?? null;
414
- }
415
- function showStatus(el, text, ok) {
416
- if (!el)
417
- return;
418
- el.textContent = text;
419
- el.className = `oncall-status ${ok ? 'hint-ok' : 'hint-warn-inline'}`;
420
- }
421
- function setChoiceButtonsDisabled(selector, disabled) {
422
- bodyEl.querySelectorAll(selector).forEach(button => { button.disabled = disabled; });
423
- }
424
- function syncChoiceButtons(selector, datasetKey, selectedValue) {
425
- bodyEl.querySelectorAll(selector).forEach(button => {
426
- const selected = button.dataset[datasetKey] === selectedValue;
427
- button.classList.toggle('selected', selected);
428
- button.setAttribute('aria-pressed', selected ? 'true' : 'false');
429
- });
430
- }
431
- function rerender() {
432
- bodyEl.innerHTML = renderBody();
433
- wire();
434
- }
435
- async function waitForSkillJob(job, statusEl, refreshOnSuccess = true) {
436
- let current = job;
437
- showStatus(statusEl, t('skills.jobRunning'), true);
438
- for (;;) {
439
- if (current.status === 'succeeded') {
440
- showStatus(statusEl, t('skills.saved'), true);
441
- if (refreshOnSuccess)
442
- await refresh();
443
- return;
444
- }
445
- if (current.status === 'failed') {
446
- throw new Error(current.error ?? 'job_failed');
447
- }
448
- await sleep(800);
449
- const body = await jsonRequest(`/api/skills/jobs/${encodeURIComponent(current.id)}`, { method: 'GET' });
450
- current = body.job;
451
- }
452
- }
453
- function activeDiscoveryPanel() {
454
- return bodyEl.querySelector('[data-discovery-panel]:not([hidden])');
455
- }
456
- function updateDiscoveryToggleButton() {
457
- const button = bodyEl.querySelector('[data-action="toggle-discovered-skills"]');
458
- if (!button)
459
- return;
460
- const inputs = [...(activeDiscoveryPanel()?.querySelectorAll('[data-discovered-skill]:not(:disabled)') ?? [])];
461
- const hasUnchecked = inputs.some(input => !input.checked);
462
- button.textContent = inputs.length > 0 && !hasUnchecked ? t('skills.discoverClearSelection') : t('skills.discoverSelectAll');
463
- button.disabled = inputs.length === 0;
464
- }
465
- function wire() {
466
- bodyEl.querySelector('[data-action="install"]')?.addEventListener('click', async () => {
467
- const panel = bodyEl.querySelector('.skills-install-panel');
468
- const statusEl = status(panel);
469
- const button = bodyEl.querySelector('[data-action="install"]');
470
- const source = bodyEl.querySelector('[data-install="source"]')?.value.trim() ?? '';
471
- const path = bodyEl.querySelector('[data-install="path"]')?.value.trim() ?? '';
472
- const ref = bodyEl.querySelector('[data-install="ref"]')?.value.trim() ?? '';
473
- try {
474
- if (button)
475
- button.disabled = true;
476
- const body = await jsonRequest('/api/skills/install', {
477
- method: 'POST',
478
- body: JSON.stringify({ source, path: path || undefined, ref: ref || undefined }),
479
- });
480
- await waitForSkillJob(body.job, statusEl);
481
- }
482
- catch (err) {
483
- showStatus(statusEl, `${t('skills.failed')}: ${err?.message ?? err}`, false);
484
- }
485
- finally {
486
- if (button)
487
- button.disabled = false;
488
- }
489
- });
490
- bodyEl.querySelector('[data-action="discover-native-skills"]')?.addEventListener('click', () => {
491
- bodyEl.querySelector('#skills-discovery-dialog')?.showModal();
492
- updateDiscoveryToggleButton();
493
- });
494
- bodyEl.querySelector('[data-action="close-discovery"]')?.addEventListener('click', () => {
495
- bodyEl.querySelector('#skills-discovery-dialog')?.close();
496
- });
497
- bodyEl.querySelectorAll('[data-discovery-tab]').forEach(button => {
498
- button.addEventListener('click', () => {
499
- const key = button.dataset.discoveryTab ?? '';
500
- activeDiscoveryGroupKey = key;
501
- bodyEl.querySelectorAll('[data-discovery-tab]').forEach(tab => {
502
- const selected = tab.dataset.discoveryTab === key;
503
- tab.classList.toggle('selected', selected);
504
- tab.setAttribute('aria-selected', selected ? 'true' : 'false');
505
- });
506
- bodyEl.querySelectorAll('[data-discovery-panel]').forEach(panel => {
507
- panel.hidden = panel.dataset.discoveryPanel !== key;
508
- });
509
- const group = state.nativeSkillGroups.find(item => discoveryGroupKey(item) === key);
510
- const pathEl = bodyEl.querySelector('.skills-discovery-path code');
511
- if (pathEl && group)
512
- pathEl.textContent = group.rootDir;
513
- updateDiscoveryToggleButton();
514
- });
515
- });
516
- bodyEl.querySelectorAll('[data-discovered-skill]').forEach(input => {
517
- input.addEventListener('change', updateDiscoveryToggleButton);
518
- });
519
- bodyEl.querySelector('[data-action="toggle-discovered-skills"]')?.addEventListener('click', () => {
520
- const inputs = [...(activeDiscoveryPanel()?.querySelectorAll('[data-discovered-skill]:not(:disabled)') ?? [])];
521
- const next = inputs.some(input => !input.checked);
522
- inputs.forEach(input => { input.checked = next; });
523
- updateDiscoveryToggleButton();
524
- });
525
- bodyEl.querySelector('[data-action="register-discovered-skills"]')?.addEventListener('click', async () => {
526
- const dialog = bodyEl.querySelector('#skills-discovery-dialog');
527
- const panel = bodyEl.querySelector('.skills-install-panel');
528
- const statusEl = status(panel);
529
- const selected = [...bodyEl.querySelectorAll('[data-discovered-skill]:checked:not(:disabled)')]
530
- .map(input => input.value)
531
- .filter(Boolean);
532
- if (selected.length === 0) {
533
- showStatus(statusEl, t('skills.discoverNothingSelected'), false);
534
- return;
535
- }
536
- const button = bodyEl.querySelector('[data-action="register-discovered-skills"]');
537
- try {
538
- if (button)
539
- button.disabled = true;
540
- showStatus(statusEl, t('skills.discoverRegisteringBatch', { total: selected.length }), true);
541
- await jsonRequest('/api/skills/install-local-links', {
542
- method: 'POST',
543
- body: JSON.stringify({ sources: selected }),
544
- });
545
- dialog?.close();
546
- await refresh();
547
- }
548
- catch (err) {
549
- showStatus(statusEl, `${t('skills.failed')}: ${err?.message ?? err}`, false);
550
- }
551
- finally {
552
- if (button)
553
- button.disabled = false;
554
- }
555
- });
556
- bodyEl.querySelectorAll('[data-global-project-value]').forEach(button => button.addEventListener('click', async () => {
557
- const next = button.dataset.globalProjectValue === 'all' ? 'all' : 'off';
558
- if (state.trustProjectSkills === next)
559
- return;
560
- try {
561
- setChoiceButtonsDisabled('[data-global-project-value]', true);
562
- const body = await jsonRequest('/api/skills/global', {
563
- method: 'PUT',
564
- body: JSON.stringify({ trustProjectSkills: next }),
565
- });
566
- state.trustProjectSkills = body.trustProjectSkills === 'all' ? 'all' : next;
567
- syncChoiceButtons('[data-global-project-value]', 'globalProjectValue', state.trustProjectSkills);
568
- }
569
- catch (err) {
570
- window.alert(`${t('skills.failed')}: ${err?.message ?? err}`);
571
- }
572
- finally {
573
- setChoiceButtonsDisabled('[data-global-project-value]', false);
574
- }
575
- }));
576
- bodyEl.querySelectorAll('[data-global-delivery-value]').forEach(button => button.addEventListener('click', async () => {
577
- const next = button.dataset.globalDeliveryValue === 'prompt' || button.dataset.globalDeliveryValue === 'native'
578
- ? button.dataset.globalDeliveryValue
579
- : 'auto';
580
- if (state.delivery === next)
581
- return;
582
- try {
583
- setChoiceButtonsDisabled('[data-global-delivery-value]', true);
584
- const body = await jsonRequest('/api/skills/global', {
585
- method: 'PUT',
586
- body: JSON.stringify({ delivery: next }),
587
- });
588
- state.delivery = body.delivery === 'prompt' || body.delivery === 'native' ? body.delivery : next;
589
- syncChoiceButtons('[data-global-delivery-value]', 'globalDeliveryValue', state.delivery);
590
- }
591
- catch (err) {
592
- window.alert(`${t('skills.failed')}: ${err?.message ?? err}`);
593
- }
594
- finally {
595
- setChoiceButtonsDisabled('[data-global-delivery-value]', false);
596
- }
597
- }));
598
- bodyEl.querySelectorAll('[data-action="scroll-bots"]').forEach(button => button.addEventListener('click', () => {
599
- const grid = bodyEl.querySelector('.skills-bot-grid');
600
- const card = grid?.querySelector('.skills-bot-card');
601
- if (!grid || !card)
602
- return;
603
- const style = window.getComputedStyle(grid);
604
- const gap = Number.parseFloat(style.columnGap || style.gap || '0') || 0;
605
- const dir = button.dataset.dir === '-1' ? -1 : 1;
606
- grid.scrollBy({ left: dir * (card.getBoundingClientRect().width + gap), behavior: 'smooth' });
607
- }));
608
- bodyEl.querySelectorAll('[data-action="page-installed-skills"]').forEach(button => button.addEventListener('click', () => {
609
- const dir = button.dataset.dir === '-1' ? -1 : 1;
610
- installedSkillsPage += dir;
611
- clampInstalledSkillsPage();
612
- rerender();
613
- }));
614
- bodyEl.querySelectorAll('.skills-row').forEach(row => {
615
- const name = row.dataset.skill ?? '';
616
- row.querySelector('[data-action="update-skill"]')?.addEventListener('click', async () => {
617
- const button = row.querySelector('[data-action="update-skill"]');
618
- const panel = bodyEl.querySelector('.skills-install-panel');
619
- const statusEl = status(panel);
620
- try {
621
- if (button)
622
- button.disabled = true;
623
- const body = await jsonRequest(`/api/skills/${encodeURIComponent(name)}/update`, { method: 'POST', body: '{}' });
624
- await waitForSkillJob(body.job, statusEl);
625
- }
626
- catch (err) {
627
- window.alert(`${t('skills.failed')}: ${err?.message ?? err}`);
628
- }
629
- finally {
630
- if (button)
631
- button.disabled = false;
632
- }
633
- });
634
- row.querySelector('[data-action="remove-skill"]')?.addEventListener('click', async () => {
635
- if (!window.confirm(`${t('skills.remove')} ${name}?`))
636
- return;
637
- try {
638
- await jsonRequest(`/api/skills/${encodeURIComponent(name)}`, { method: 'DELETE', body: '{}' });
639
- await refresh();
640
- }
641
- catch (err) {
642
- if (err?.status === 409 && err?.body?.error === 'skill_in_use') {
643
- const affected = Array.isArray(err.body.affectedBots)
644
- ? err.body.affectedBots.map((bot) => {
645
- const label = bot?.botName || bot?.larkAppId;
646
- return label ? `${label}` : '';
647
- }).filter(Boolean)
648
- : referencingBotLabels(name);
649
- const refs = [
650
- affected.length ? `Bot: ${affected.join(', ')}` : '',
651
- ].filter(Boolean).join('; ') || '-';
652
- if (!window.confirm(t('skills.removeInUse', { skill: name, refs })))
653
- return;
654
- try {
655
- await jsonRequest(`/api/skills/${encodeURIComponent(name)}?force=1`, { method: 'DELETE', body: '{}' });
656
- await refresh();
657
- return;
658
- }
659
- catch (forceErr) {
660
- window.alert(`${t('skills.failed')}: ${forceErr?.message ?? forceErr}`);
661
- return;
662
- }
663
- }
664
- window.alert(`${t('skills.failed')}: ${err?.message ?? err}`);
665
- }
666
- });
667
- });
668
- bodyEl.querySelectorAll('.skills-bot-card').forEach(card => {
669
- const appId = card.dataset.appid ?? '';
670
- const bot = state.bots.find(b => b.larkAppId === appId);
671
- if (!bot)
672
- return;
673
- card.querySelector('[data-action="attach-skill"]')?.addEventListener('click', async () => {
674
- const name = card.querySelector('[data-attach-picker]')?.value;
675
- if (!name)
676
- return;
677
- try {
678
- const body = await jsonRequest(`/api/bots/${encodeURIComponent(appId)}/skills`, {
679
- method: 'PUT',
680
- body: JSON.stringify({ action: 'attach', name }),
681
- });
682
- bot.skills = body.skills ?? null;
683
- rerender();
684
- }
685
- catch (err) {
686
- showStatus(status(card), `${t('skills.failed')}: ${err?.message ?? err}`, false);
687
- }
688
- });
689
- card.querySelectorAll('[data-action="detach-skill"]').forEach(btn => {
690
- btn.addEventListener('click', async () => {
691
- const name = btn.dataset.name;
692
- if (!name)
693
- return;
694
- try {
695
- const body = await jsonRequest(`/api/bots/${encodeURIComponent(appId)}/skills`, {
696
- method: 'PUT',
697
- body: JSON.stringify({ action: 'detach', name }),
698
- });
699
- bot.skills = body.skills ?? null;
700
- rerender();
701
- }
702
- catch (err) {
703
- showStatus(status(card), `${t('skills.failed')}: ${err?.message ?? err}`, false);
704
- }
705
- });
706
- });
707
- });
708
- }
709
- refreshBtn.onclick = () => { void refresh(); };
710
- await refresh();
711
- }
1
+ export { renderSkillsPage } from './skills-page.js';
712
2
  //# sourceMappingURL=skills.js.map