agim-cli 1.2.1 → 1.2.17

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 (199) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +18 -5
  3. package/README.zh-CN.md +20 -7
  4. package/dist/cli-ui/entry-menu.d.ts +2 -0
  5. package/dist/cli-ui/entry-menu.d.ts.map +1 -1
  6. package/dist/cli-ui/entry-menu.js +1 -0
  7. package/dist/cli-ui/entry-menu.js.map +1 -1
  8. package/dist/cli-ui/i18n.d.ts +5 -0
  9. package/dist/cli-ui/i18n.d.ts.map +1 -1
  10. package/dist/cli-ui/i18n.js +10 -0
  11. package/dist/cli-ui/i18n.js.map +1 -1
  12. package/dist/cli.js +117 -0
  13. package/dist/cli.js.map +1 -1
  14. package/dist/core/admin-bootstrap.d.ts +14 -0
  15. package/dist/core/admin-bootstrap.d.ts.map +1 -1
  16. package/dist/core/admin-bootstrap.js +21 -0
  17. package/dist/core/admin-bootstrap.js.map +1 -1
  18. package/dist/core/commands/builtin.d.ts.map +1 -1
  19. package/dist/core/commands/builtin.js +2 -0
  20. package/dist/core/commands/builtin.js.map +1 -1
  21. package/dist/core/commands/router.js +1 -1
  22. package/dist/core/commands/router.js.map +1 -1
  23. package/dist/core/commands/web.d.ts +3 -0
  24. package/dist/core/commands/web.d.ts.map +1 -0
  25. package/dist/core/commands/web.js +28 -0
  26. package/dist/core/commands/web.js.map +1 -0
  27. package/dist/core/intent.d.ts +11 -2
  28. package/dist/core/intent.d.ts.map +1 -1
  29. package/dist/core/intent.js +26 -4
  30. package/dist/core/intent.js.map +1 -1
  31. package/dist/core/memory.js.map +1 -1
  32. package/dist/core/render-router.d.ts.map +1 -1
  33. package/dist/core/render-router.js +3 -2
  34. package/dist/core/render-router.js.map +1 -1
  35. package/dist/core/router.d.ts.map +1 -1
  36. package/dist/core/router.js +8 -1
  37. package/dist/core/router.js.map +1 -1
  38. package/dist/core/types.d.ts +3 -0
  39. package/dist/core/types.d.ts.map +1 -1
  40. package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -1
  41. package/dist/plugins/agents/acp/acp-client.js +7 -0
  42. package/dist/plugins/agents/acp/acp-client.js.map +1 -1
  43. package/dist/plugins/agents/acp/discovery.d.ts.map +1 -1
  44. package/dist/plugins/agents/acp/discovery.js +4 -0
  45. package/dist/plugins/agents/acp/discovery.js.map +1 -1
  46. package/dist/plugins/agents/acp/url-guard.d.ts +44 -0
  47. package/dist/plugins/agents/acp/url-guard.d.ts.map +1 -0
  48. package/dist/plugins/agents/acp/url-guard.js +109 -0
  49. package/dist/plugins/agents/acp/url-guard.js.map +1 -0
  50. package/dist/web/env-mask.d.ts +21 -0
  51. package/dist/web/env-mask.d.ts.map +1 -0
  52. package/dist/web/env-mask.js +44 -0
  53. package/dist/web/env-mask.js.map +1 -0
  54. package/dist/web/public/assets/a2a-Dk2fSs33.js +7 -0
  55. package/dist/web/public/assets/a2a-Dk2fSs33.js.map +1 -0
  56. package/dist/web/public/assets/activity-eiIPshcV.js +7 -0
  57. package/dist/web/public/assets/activity-eiIPshcV.js.map +1 -0
  58. package/dist/web/public/assets/admins-DlbQYdW_.js +12 -0
  59. package/dist/web/public/assets/admins-DlbQYdW_.js.map +1 -0
  60. package/dist/web/public/assets/agents-BMI1WbZj.js +12 -0
  61. package/dist/web/public/assets/agents-BMI1WbZj.js.map +1 -0
  62. package/dist/web/public/assets/approvals-DlXS_sKD.js +10 -0
  63. package/dist/web/public/assets/approvals-DlXS_sKD.js.map +1 -0
  64. package/dist/web/public/assets/audit-C8I8xC_6.js +2 -0
  65. package/dist/web/public/assets/audit-C8I8xC_6.js.map +1 -0
  66. package/dist/web/public/assets/bgjobs-PFYinH7D.js +7 -0
  67. package/dist/web/public/assets/bgjobs-PFYinH7D.js.map +1 -0
  68. package/dist/web/public/assets/brain-DEEJttEL.js +7 -0
  69. package/dist/web/public/assets/brain-DEEJttEL.js.map +1 -0
  70. package/dist/web/public/assets/briefcase-BlMy8gI6.js +7 -0
  71. package/dist/web/public/assets/briefcase-BlMy8gI6.js.map +1 -0
  72. package/dist/web/public/assets/browser-ponyfill-BOcGq8h9.js +3 -0
  73. package/dist/web/public/assets/browser-ponyfill-BOcGq8h9.js.map +1 -0
  74. package/dist/web/public/assets/chevron-right-DmABPvoA.js +7 -0
  75. package/dist/web/public/assets/chevron-right-DmABPvoA.js.map +1 -0
  76. package/dist/web/public/assets/circle-check-C0Qpg1vL.js +7 -0
  77. package/dist/web/public/assets/circle-check-C0Qpg1vL.js.map +1 -0
  78. package/dist/web/public/assets/circle-check-big-C8LG3beV.js +7 -0
  79. package/dist/web/public/assets/circle-check-big-C8LG3beV.js.map +1 -0
  80. package/dist/web/public/assets/circle-x-D_cRHcHK.js +7 -0
  81. package/dist/web/public/assets/circle-x-D_cRHcHK.js.map +1 -0
  82. package/dist/web/public/assets/confirm-dialog-Baz_xFle.js +2 -0
  83. package/dist/web/public/assets/confirm-dialog-Baz_xFle.js.map +1 -0
  84. package/dist/web/public/assets/data-table--I_ktDF4.js +17 -0
  85. package/dist/web/public/assets/data-table--I_ktDF4.js.map +1 -0
  86. package/dist/web/public/assets/dialog-DZpoEskO.js +6 -0
  87. package/dist/web/public/assets/dialog-DZpoEskO.js.map +1 -0
  88. package/dist/web/public/assets/download-DbFGHwZ5.js +7 -0
  89. package/dist/web/public/assets/download-DbFGHwZ5.js.map +1 -0
  90. package/dist/web/public/assets/email-BB1Hq8eE.js +7 -0
  91. package/dist/web/public/assets/email-BB1Hq8eE.js.map +1 -0
  92. package/dist/web/public/assets/empty-state-DXNa90pP.js +2 -0
  93. package/dist/web/public/assets/empty-state-DXNa90pP.js.map +1 -0
  94. package/dist/web/public/assets/env-Bqrb9XkC.js +2 -0
  95. package/dist/web/public/assets/env-Bqrb9XkC.js.map +1 -0
  96. package/dist/web/public/assets/external-link-nhnJN0qg.js +7 -0
  97. package/dist/web/public/assets/external-link-nhnJN0qg.js.map +1 -0
  98. package/dist/web/public/assets/eye-IKkn_oUo.js +12 -0
  99. package/dist/web/public/assets/eye-IKkn_oUo.js.map +1 -0
  100. package/dist/web/public/assets/facts-C7Qy9vTw.js +2 -0
  101. package/dist/web/public/assets/facts-C7Qy9vTw.js.map +1 -0
  102. package/dist/web/public/assets/health-CMRdeNEW.js +2 -0
  103. package/dist/web/public/assets/health-CMRdeNEW.js.map +1 -0
  104. package/dist/web/public/assets/hot-Bh5Nrc7i.js +17 -0
  105. package/dist/web/public/assets/hot-Bh5Nrc7i.js.map +1 -0
  106. package/dist/web/public/assets/index-CpGWCLE5.js +166 -0
  107. package/dist/web/public/assets/index-CpGWCLE5.js.map +1 -0
  108. package/dist/web/public/assets/index-GpceOxum.css +1 -0
  109. package/dist/web/public/assets/installed-FYLkPij2.js +7 -0
  110. package/dist/web/public/assets/installed-FYLkPij2.js.map +1 -0
  111. package/dist/web/public/assets/jobs-BmqLUzHp.js +2 -0
  112. package/dist/web/public/assets/jobs-BmqLUzHp.js.map +1 -0
  113. package/dist/web/public/assets/layout-9Gp_myEd.js +2 -0
  114. package/dist/web/public/assets/layout-9Gp_myEd.js.map +1 -0
  115. package/dist/web/public/assets/layout-BZaHqf69.js +2 -0
  116. package/dist/web/public/assets/layout-BZaHqf69.js.map +1 -0
  117. package/dist/web/public/assets/layout-CXsUyEpG.js +2 -0
  118. package/dist/web/public/assets/layout-CXsUyEpG.js.map +1 -0
  119. package/dist/web/public/assets/layout-DFxtpNut.js +2 -0
  120. package/dist/web/public/assets/layout-DFxtpNut.js.map +1 -0
  121. package/dist/web/public/assets/layout-d8qxPKQk.js +2 -0
  122. package/dist/web/public/assets/layout-d8qxPKQk.js.map +1 -0
  123. package/dist/web/public/assets/loader-circle-JaKY-xMt.js +7 -0
  124. package/dist/web/public/assets/loader-circle-JaKY-xMt.js.map +1 -0
  125. package/dist/web/public/assets/map-pin-hFFSWZ3B.js +7 -0
  126. package/dist/web/public/assets/map-pin-hFFSWZ3B.js.map +1 -0
  127. package/dist/web/public/assets/memos-EhjMUvVZ.js +12 -0
  128. package/dist/web/public/assets/memos-EhjMUvVZ.js.map +1 -0
  129. package/dist/web/public/assets/messengers-BRV1IVGX.js +7 -0
  130. package/dist/web/public/assets/messengers-BRV1IVGX.js.map +1 -0
  131. package/dist/web/public/assets/network-DtCI2ZUU.js +7 -0
  132. package/dist/web/public/assets/network-DtCI2ZUU.js.map +1 -0
  133. package/dist/web/public/assets/outbox-CxUbMp6o.js +7 -0
  134. package/dist/web/public/assets/outbox-CxUbMp6o.js.map +1 -0
  135. package/dist/web/public/assets/pagination-CkZY8YNa.js +17 -0
  136. package/dist/web/public/assets/pagination-CkZY8YNa.js.map +1 -0
  137. package/dist/web/public/assets/persona-B6TFMSnI.js +2 -0
  138. package/dist/web/public/assets/persona-B6TFMSnI.js.map +1 -0
  139. package/dist/web/public/assets/play-BxRcWaH5.js +7 -0
  140. package/dist/web/public/assets/play-BxRcWaH5.js.map +1 -0
  141. package/dist/web/public/assets/policy-ndE1Y8zD.js +2 -0
  142. package/dist/web/public/assets/policy-ndE1Y8zD.js.map +1 -0
  143. package/dist/web/public/assets/react-C9F3QeMB.js +33 -0
  144. package/dist/web/public/assets/react-C9F3QeMB.js.map +1 -0
  145. package/dist/web/public/assets/refresh-ccw-Bx817_KW.js +7 -0
  146. package/dist/web/public/assets/refresh-ccw-Bx817_KW.js.map +1 -0
  147. package/dist/web/public/assets/reminders-XynkGQc5.js +17 -0
  148. package/dist/web/public/assets/reminders-XynkGQc5.js.map +1 -0
  149. package/dist/web/public/assets/save-CqMcATrh.js +7 -0
  150. package/dist/web/public/assets/save-CqMcATrh.js.map +1 -0
  151. package/dist/web/public/assets/schedules-VM02w_Om.js +7 -0
  152. package/dist/web/public/assets/schedules-VM02w_Om.js.map +1 -0
  153. package/dist/web/public/assets/search-Ba-e1t1P.js +7 -0
  154. package/dist/web/public/assets/search-Ba-e1t1P.js.map +1 -0
  155. package/dist/web/public/assets/service-C-wnwJ-b.js +7 -0
  156. package/dist/web/public/assets/service-C-wnwJ-b.js.map +1 -0
  157. package/dist/web/public/assets/status-badge-CsdJ6k8Q.js +2 -0
  158. package/dist/web/public/assets/status-badge-CsdJ6k8Q.js.map +1 -0
  159. package/dist/web/public/assets/subtasks-mGRKpF0G.js +7 -0
  160. package/dist/web/public/assets/subtasks-mGRKpF0G.js.map +1 -0
  161. package/dist/web/public/assets/table-vmLMgj6_.js +2 -0
  162. package/dist/web/public/assets/table-vmLMgj6_.js.map +1 -0
  163. package/dist/web/public/assets/topn-nu66Fotx.js +7 -0
  164. package/dist/web/public/assets/topn-nu66Fotx.js.map +1 -0
  165. package/dist/web/public/assets/trash-2-ZIitN_U3.js +7 -0
  166. package/dist/web/public/assets/trash-2-ZIitN_U3.js.map +1 -0
  167. package/dist/web/public/assets/use-event-stream-BGeFcayX.js +2 -0
  168. package/dist/web/public/assets/use-event-stream-BGeFcayX.js.map +1 -0
  169. package/dist/web/public/assets/use-memory-DgEqHEca.js +2 -0
  170. package/dist/web/public/assets/use-memory-DgEqHEca.js.map +1 -0
  171. package/dist/web/public/assets/use-observability-CQev_A8e.js +2 -0
  172. package/dist/web/public/assets/use-observability-CQev_A8e.js.map +1 -0
  173. package/dist/web/public/assets/use-settings-CU-UcrVD.js +2 -0
  174. package/dist/web/public/assets/use-settings-CU-UcrVD.js.map +1 -0
  175. package/dist/web/public/assets/use-skills-Dr77CXLA.js +2 -0
  176. package/dist/web/public/assets/use-skills-Dr77CXLA.js.map +1 -0
  177. package/dist/web/public/assets/use-workspace-PNv9Z4de.js +2 -0
  178. package/dist/web/public/assets/use-workspace-PNv9Z4de.js.map +1 -0
  179. package/dist/web/public/assets/useQuery-BTyugXYV.js +2 -0
  180. package/dist/web/public/assets/useQuery-BTyugXYV.js.map +1 -0
  181. package/dist/web/public/assets/vector-w-Ea3pg6.js +2 -0
  182. package/dist/web/public/assets/vector-w-Ea3pg6.js.map +1 -0
  183. package/dist/web/public/assets/viewer-DKA7QP9U.js +12 -0
  184. package/dist/web/public/assets/viewer-DKA7QP9U.js.map +1 -0
  185. package/dist/web/public/assets/workspace-DVLZca7t.js +17 -0
  186. package/dist/web/public/assets/workspace-DVLZca7t.js.map +1 -0
  187. package/dist/web/public/assets/workspaces-DYZsMmY-.js +7 -0
  188. package/dist/web/public/assets/workspaces-DYZsMmY-.js.map +1 -0
  189. package/dist/web/public/assets/x-Ru3rHT82.js +7 -0
  190. package/dist/web/public/assets/x-Ru3rHT82.js.map +1 -0
  191. package/dist/web/public/favicon.svg +4 -0
  192. package/dist/web/public/index.html +37 -928
  193. package/dist/web/public/manifest.webmanifest +19 -0
  194. package/dist/web/public/tasks.html +362 -6
  195. package/dist/web/public/vendor/chart.umd.min.js +20 -0
  196. package/dist/web/server.d.ts.map +1 -1
  197. package/dist/web/server.js +694 -60
  198. package/dist/web/server.js.map +1 -1
  199. package/package.json +4 -4
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "Agim",
3
+ "short_name": "Agim",
4
+ "description": "agim — multi-agent gateway console",
5
+ "start_url": "/",
6
+ "scope": "/",
7
+ "display": "standalone",
8
+ "orientation": "any",
9
+ "theme_color": "#4f46e5",
10
+ "background_color": "#f7f8fa",
11
+ "icons": [
12
+ {
13
+ "src": "/favicon.svg",
14
+ "sizes": "any",
15
+ "type": "image/svg+xml",
16
+ "purpose": "any maskable"
17
+ }
18
+ ]
19
+ }
@@ -233,6 +233,22 @@
233
233
  // ── v1.2 Cost & Health tab ──
234
234
  tabsCost: '💰 Cost & Health',
235
235
  tabsMemory: '🧠 Memory',
236
+ tabsSkills: '🛠 Skills',
237
+ skillsH2: '🛠 Installed Skills',
238
+ skillsIntro: 'Read-only inventory of skills installed for claude-code / opencode on this host. Click a row for the full description. To install a new skill, send <code>/find-skills</code> in IM, or browse the skillhub.cn hot list below.',
239
+ skillsSearchPh: 'Search (name / description)',
240
+ skillsCatAll: 'All categories',
241
+ skillsRemoteH2: 'Hot on skillhub.cn',
242
+ skillsRemoteOpen: 'Open skillhub.cn ↗',
243
+ skillsRemoteIntro: 'Live top-50 hot downloads from skillhub.cn (5-min cache). Click any entry for the description + install instructions. agim never auto-runs installs — you copy the command and paste into shell or IM.',
244
+ skillsRemoteCached: 'cache hit',
245
+ skillsRemoteStale: 'upstream down, serving cached',
246
+ skillsCol: 'slug',
247
+ skillsName: 'name / description',
248
+ skillsStars: '⭐ stars',
249
+ skillsInstalls: '⬇ installs',
250
+ skillsHowInstall: 'Install (pick one; agim never auto-runs)',
251
+ skillsHostHint: 'The first two require skillhub CLI on the host. See docs/skills.md or skillhub.cn.',
236
252
  costH2: '💰 Cost & Health',
237
253
  costWindow: 'Window',
238
254
  costDays1: '1 day',
@@ -259,6 +275,7 @@
259
275
  memoryPersonaPlaceholder: '(No persona for this user yet — will be auto-generated at next consolidation, or write one manually.)',
260
276
  memoryPersonaSave: 'Save persona',
261
277
  memoryPersonaDelete: 'Delete persona',
278
+ memoryPersonaConsolidate: '🔁 Rebuild persona now',
262
279
  memoryFactsCard: 'Facts',
263
280
  memoryFactsSearch: 'Search (FTS5)',
264
281
  memoryFactsCatAll: 'All types',
@@ -506,6 +523,22 @@
506
523
  // ── v1.2 Cost & Health tab ──
507
524
  tabsCost: '💰 成本与健康',
508
525
  tabsMemory: '🧠 记忆',
526
+ tabsSkills: '🛠 技能',
527
+ skillsH2: '🛠 已安装的技能',
528
+ skillsIntro: '本机已安装的 claude / opencode skills 索引。点击行查看完整描述。要安装新 skill 请在 IM 里发 <code>/find-skills</code>,或浏览下方 skillhub.cn 热门榜。',
529
+ skillsSearchPh: '搜索(名称 / 描述)',
530
+ skillsCatAll: '所有类别',
531
+ skillsRemoteH2: 'skillhub.cn 热门',
532
+ skillsRemoteOpen: '前往 skillhub.cn ↗',
533
+ skillsRemoteIntro: '实时拉取 skillhub.cn top 50 热门下载(5 分钟缓存)。点击任意条目查看描述 + 安装命令。agim 不会自动跑安装——命令复制后由你贴去 shell 或 IM 触发。',
534
+ skillsRemoteCached: '缓存命中',
535
+ skillsRemoteStale: '上游不可达,用旧数据',
536
+ skillsCol: 'slug',
537
+ skillsName: '名称 / 描述',
538
+ skillsStars: '⭐ stars',
539
+ skillsInstalls: '⬇ installs',
540
+ skillsHowInstall: '安装方式(任选一个;agim 不会自动跑)',
541
+ skillsHostHint: '前两条要先在 host 上跑 skillhub install.sh。详见 docs/skills.md 或 skillhub.cn。',
509
542
  costH2: '💰 成本与健康',
510
543
  costWindow: '窗口',
511
544
  costDays1: '1 天',
@@ -532,6 +565,7 @@
532
565
  memoryPersonaPlaceholder: '(此用户暂无记忆画像 — 等下次 consolidation 自动生成,或手动写入)',
533
566
  memoryPersonaSave: '保存记忆画像',
534
567
  memoryPersonaDelete: '删除记忆画像',
568
+ memoryPersonaConsolidate: '🔁 立刻重建画像',
535
569
  memoryFactsCard: 'Facts',
536
570
  memoryFactsSearch: '搜索(FTS5)',
537
571
  memoryFactsCatAll: '所有类型',
@@ -910,6 +944,7 @@
910
944
  <button type="button" class="tab" data-tab="a2a" id="tab-a2a">A2A</button>
911
945
  <button type="button" class="tab" data-tab="cost" id="tab-cost">💰 Cost &amp; Health</button>
912
946
  <button type="button" class="tab" data-tab="memory" id="tab-memory">🧠 Memory</button>
947
+ <button type="button" class="tab" data-tab="skills" id="tab-skills">🛠 Skills</button>
913
948
  </div>
914
949
 
915
950
  <section id="jobs-pane">
@@ -1244,9 +1279,10 @@
1244
1279
  <span class="muted" id="mem-persona-meta" style="font-size:12px"></span>
1245
1280
  </div>
1246
1281
  <textarea id="mem-persona-text" rows="6" style="width:100%;font-family:ui-monospace,monospace;font-size:13px;box-sizing:border-box" data-i18n-attr="placeholder:memoryPersonaPlaceholder" placeholder="(此用户暂无记忆画像 — 等下次 consolidation 自动生成,或手动写入)"></textarea>
1247
- <div class="actions" style="margin-top:8px;display:flex;gap:8px">
1282
+ <div class="actions" style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap">
1248
1283
  <button type="button" id="btn-mem-persona-save" class="btn btn-primary" data-i18n="memoryPersonaSave">保存记忆画像</button>
1249
1284
  <button type="button" id="btn-mem-persona-delete" class="btn" data-i18n="memoryPersonaDelete">删除记忆画像</button>
1285
+ <button type="button" id="btn-mem-persona-consolidate" class="btn" title="立刻跑一次 consolidation 重建画像,不等 24h 周期" data-i18n="memoryPersonaConsolidate">🔁 立刻重建画像</button>
1250
1286
  </div>
1251
1287
  <p class="muted" id="mem-persona-status" style="margin-top:6px;font-size:12px"></p>
1252
1288
  </div>
@@ -1283,6 +1319,43 @@
1283
1319
  <p class="muted" id="mem-facts-status" style="margin-top:6px;font-size:12px"></p>
1284
1320
  </div>
1285
1321
  </section>
1322
+
1323
+ <!-- v1.2.3 — Skills browser: read-only inventory of installed claude /
1324
+ opencode skills + a remote "hot on skillhub.cn" panel. -->
1325
+ <section id="skills-pane" hidden>
1326
+ <h2 style="margin-top:0" data-i18n="skillsH2">🛠 已安装的 Skills</h2>
1327
+ <p class="muted" style="margin-top:-4px;margin-bottom:12px;font-size:13px" data-i18n="skillsIntro">
1328
+ 本机已安装的 claude / opencode skills 索引。点击行查看完整描述。
1329
+ 要安装新 skill 请在 IM 里发 <code>/find-skills</code>,或浏览下方 skillhub.cn 热门榜。
1330
+ </p>
1331
+ <div class="toolbar" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:12px">
1332
+ <input type="text" id="skills-search" placeholder="搜索(名称 / 描述)" data-i18n-attr="placeholder:skillsSearchPh" style="width:240px;font-size:13px" />
1333
+ <select id="skills-cat-filter" style="font-size:13px;min-width:160px">
1334
+ <option value="" data-i18n="skillsCatAll">所有类别</option>
1335
+ </select>
1336
+ <button type="button" id="btn-skills-refresh" data-i18n="refresh">刷新</button>
1337
+ <span id="skills-stats" class="muted" style="font-size:12px;margin-left:8px"></span>
1338
+ </div>
1339
+ <div id="skills-list">…</div>
1340
+
1341
+ <!-- Remote: skillhub.cn hot showcase -->
1342
+ <div style="margin-top:32px;border-top:1px solid var(--border,#d0d7de);padding-top:16px">
1343
+ <div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:6px">
1344
+ <h2 style="margin:0">🔥 <span data-i18n="skillsRemoteH2">skillhub.cn 热门</span>
1345
+ <span id="skills-remote-meta" class="muted" style="font-weight:400;font-size:12px"></span>
1346
+ </h2>
1347
+ <div>
1348
+ <a href="https://skillhub.cn" target="_blank" rel="noopener" class="btn" data-i18n="skillsRemoteOpen">前往 skillhub.cn ↗</a>
1349
+ <button type="button" id="btn-skills-remote-refresh" class="btn" data-i18n="refresh" style="margin-left:6px">刷新</button>
1350
+ </div>
1351
+ </div>
1352
+ <p class="muted" style="font-size:12px;margin:0 0 12px" data-i18n="skillsRemoteIntro">
1353
+ 实时拉取 skillhub.cn top 50 热门下载(5 分钟缓存)。点击任意条目查看描述。
1354
+ 安装提示见弹窗末端 — 用 skillhub CLI 或在 IM 里告诉 agent 通过 skillhub 安装。
1355
+ </p>
1356
+ <div id="skills-remote-list"><p class="muted">…</p></div>
1357
+ </div>
1358
+ </section>
1286
1359
  </main>
1287
1360
 
1288
1361
  <div class="modal-bg" id="modal-bg">
@@ -1316,6 +1389,7 @@
1316
1389
  document.getElementById('tab-a2a').textContent = T.tabsA2A;
1317
1390
  if (T.tabsCost) document.getElementById('tab-cost').textContent = T.tabsCost;
1318
1391
  if (T.tabsMemory) document.getElementById('tab-memory').textContent = T.tabsMemory;
1392
+ if (T.tabsSkills) document.getElementById('tab-skills').textContent = T.tabsSkills;
1319
1393
  document.getElementById('hdr-outbox').textContent = T.tabsOutbox;
1320
1394
  document.getElementById('hdr-a2a').textContent = T.tabsA2A;
1321
1395
 
@@ -1417,6 +1491,7 @@
1417
1491
  document.getElementById('a2a-pane').hidden = tab !== 'a2a';
1418
1492
  document.getElementById('cost-pane').hidden = tab !== 'cost';
1419
1493
  document.getElementById('memory-pane').hidden = tab !== 'memory';
1494
+ document.getElementById('skills-pane').hidden = tab !== 'skills';
1420
1495
  // Lazy-load on first activation; auto-refresh hooks below kick in too.
1421
1496
  if (tab === 'schedules') loadSchedules();
1422
1497
  if (tab === 'background') { ensureBgRootsLoaded().then(loadBgjobs); }
@@ -1436,6 +1511,7 @@
1436
1511
  checkMemoryEnabledThen(() => { loadMemVecConfig(); loadMemVecStatus(); });
1437
1512
  }
1438
1513
  if (tab !== 'memory') stopMemVecPolling();
1514
+ if (tab === 'skills') loadSkills();
1439
1515
  // Pause/resume auto-refresh so hidden tabs don't poll.
1440
1516
  setupBgAutoRefresh();
1441
1517
  setupApprovalsAutoRefresh();
@@ -2744,14 +2820,19 @@
2744
2820
  function ensureChartJs() {
2745
2821
  if (window.Chart) return Promise.resolve();
2746
2822
  if (costChartJsLoaded) return costChartJsLoaded;
2747
- costChartJsLoaded = new Promise((resolve, reject) => {
2823
+ // v1.2.3 try the vendored copy first (no CDN dependency, works in
2824
+ // CN where jsdelivr is often blocked). Fall back to jsdelivr if the
2825
+ // vendor file is missing (e.g. dev hot-reload before build copied it).
2826
+ const tryLoad = (src) => new Promise((resolve, reject) => {
2748
2827
  const s = document.createElement('script');
2749
- s.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js';
2828
+ s.src = src;
2750
2829
  s.crossOrigin = 'anonymous';
2751
2830
  s.onload = () => resolve();
2752
- s.onerror = () => reject(new Error('chart.js failed to load'));
2831
+ s.onerror = () => reject(new Error('failed: ' + src));
2753
2832
  document.head.appendChild(s);
2754
2833
  });
2834
+ costChartJsLoaded = tryLoad('/vendor/chart.umd.min.js')
2835
+ .catch(() => tryLoad('https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js'));
2755
2836
  return costChartJsLoaded;
2756
2837
  }
2757
2838
 
@@ -2769,8 +2850,22 @@
2769
2850
  const data = await api('/api/health/summary?days=' + days);
2770
2851
  renderCostKpis(data);
2771
2852
  renderCostRange(data);
2772
- await ensureChartJs().catch(() => null);
2773
- renderCostTrend(data);
2853
+ let chartLoadErr = null;
2854
+ await ensureChartJs().catch((e) => { chartLoadErr = e; });
2855
+ if (chartLoadErr) {
2856
+ // Visible hint when both the vendored copy AND CDN fail
2857
+ // (cached browser, blocked CDN, etc). Without this, the chart
2858
+ // area is empty + silent.
2859
+ const c = document.getElementById('cost-trend');
2860
+ if (c && c.parentElement) {
2861
+ c.parentElement.innerHTML = `<p style="color:#d00;font-size:12px">
2862
+ chart.js 加载失败:${escapeHtmlSafe(String(chartLoadErr.message || chartLoadErr))}
2863
+ (检查 /vendor/chart.umd.min.js 是否存在,或浏览器是否屏蔽了 jsdelivr)
2864
+ </p>`;
2865
+ }
2866
+ } else {
2867
+ renderCostTrend(data);
2868
+ }
2774
2869
  } catch (err) {
2775
2870
  document.getElementById('cost-kpi').innerHTML = '<div class="error">加载失败: ' + (err.message || err) + '</div>';
2776
2871
  }
@@ -3329,6 +3424,57 @@
3329
3424
  if (status) status.textContent = '删除失败: ' + (err.message || err);
3330
3425
  }
3331
3426
  });
3427
+ document.getElementById('btn-mem-persona-consolidate')?.addEventListener('click', async () => {
3428
+ const status = document.getElementById('mem-persona-status');
3429
+ const btn = document.getElementById('btn-mem-persona-consolidate');
3430
+ if (btn) btn.disabled = true;
3431
+ if (status) status.textContent = '⏳ 已提交后台任务(每用户 1 次 LLM 调用,10–60 秒)…';
3432
+ let pollTimer = null;
3433
+ const stopPoll = () => { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } };
3434
+ try {
3435
+ // Async kickoff: server returns 202 + jobId immediately, then the
3436
+ // LLM call runs in the background. Polling avoids cloudflare /
3437
+ // nginx / browser fetch timeouts on long-held POSTs.
3438
+ const r = await api('/api/memory/consolidate', { method: 'POST' });
3439
+ const targetJobId = r.jobId;
3440
+ if (!targetJobId) throw new Error('server did not return jobId');
3441
+ const startedAt = Date.now();
3442
+ pollTimer = setInterval(async () => {
3443
+ try {
3444
+ const s = await api('/api/memory/consolidate/status');
3445
+ const job = (s.jobs || []).find(j => j.id === targetJobId);
3446
+ if (!job) return;
3447
+ const elapsed = ((Date.now() - startedAt) / 1000).toFixed(0);
3448
+ if (job.phase === 'running') {
3449
+ if (status) status.textContent = `⏳ 重建中 (${elapsed}s)…`;
3450
+ return;
3451
+ }
3452
+ stopPoll();
3453
+ if (btn) btn.disabled = false;
3454
+ if (job.phase === 'done') {
3455
+ const u = (job.result && job.result.updated) || 0;
3456
+ const n = (job.result && job.result.users) || 0;
3457
+ if (status) status.textContent = `✓ 完成 (${elapsed}s) · 处理 ${n} 个用户、更新 ${u} 个画像`;
3458
+ await loadMemoryUserDetail();
3459
+ } else {
3460
+ const e = (job.result && job.result.error) || 'unknown error';
3461
+ if (status) status.textContent = '失败: ' + e;
3462
+ }
3463
+ } catch (pollErr) {
3464
+ // Poll failures are non-fatal; UI keeps trying. Stop after 5 min.
3465
+ if (Date.now() - startedAt > 300_000) {
3466
+ stopPoll();
3467
+ if (btn) btn.disabled = false;
3468
+ if (status) status.textContent = '轮询超时(>5min),请刷新查看实际结果';
3469
+ }
3470
+ }
3471
+ }, 2000);
3472
+ } catch (err) {
3473
+ if (status) status.textContent = '失败: ' + (err.message || err);
3474
+ if (btn) btn.disabled = false;
3475
+ stopPoll();
3476
+ }
3477
+ });
3332
3478
  document.getElementById('btn-mem-facts-clear-lowconf')?.addEventListener('click', async () => {
3333
3479
  if (!memCurrentUser) return;
3334
3480
  if (!confirm('删除 confidence ≤ 40% 的所有事实?')) return;
@@ -3361,6 +3507,216 @@
3361
3507
  }
3362
3508
  });
3363
3509
 
3510
+ // ============================================
3511
+ // v1.2.3 — Skills tab
3512
+ // ============================================
3513
+ let skillsCache = null; // {total, byCategory, skills}
3514
+ async function loadSkills() {
3515
+ const listEl = document.getElementById('skills-list');
3516
+ if (listEl) listEl.innerHTML = '<p class="muted">' + (T.loading || 'Loading…') + '</p>';
3517
+ try {
3518
+ const data = await api('/api/skills');
3519
+ skillsCache = data;
3520
+ renderSkillsCatFilter(data);
3521
+ renderSkills();
3522
+ const stats = document.getElementById('skills-stats');
3523
+ if (stats) {
3524
+ const cats = Object.keys(data.byCategory || {}).length;
3525
+ stats.textContent = `· ${data.total} skills · ${cats} categories`;
3526
+ }
3527
+ } catch (err) {
3528
+ if (listEl) listEl.innerHTML = '<p style="color:#d00">' + escapeHtmlSafe(String(err.message || err)) + '</p>';
3529
+ }
3530
+ // Kick off remote hot panel in parallel — fire-and-forget; its own
3531
+ // error rendering is in loadSkillsRemote.
3532
+ loadSkillsRemote();
3533
+ }
3534
+ function renderSkillsCatFilter(data) {
3535
+ const sel = document.getElementById('skills-cat-filter');
3536
+ if (!sel) return;
3537
+ const prev = sel.value;
3538
+ // Keep first option (all), append discovered categories
3539
+ const allOpt = sel.options[0];
3540
+ sel.innerHTML = '';
3541
+ sel.appendChild(allOpt);
3542
+ const cats = Object.keys(data.byCategory || {}).sort();
3543
+ for (const c of cats) {
3544
+ const o = document.createElement('option');
3545
+ o.value = c;
3546
+ o.textContent = `${c} (${data.byCategory[c]})`;
3547
+ sel.appendChild(o);
3548
+ }
3549
+ sel.value = prev;
3550
+ }
3551
+ function renderSkills() {
3552
+ if (!skillsCache) return;
3553
+ const listEl = document.getElementById('skills-list');
3554
+ if (!listEl) return;
3555
+ const q = (document.getElementById('skills-search')?.value || '').toLowerCase().trim();
3556
+ const cat = document.getElementById('skills-cat-filter')?.value || '';
3557
+ const filtered = skillsCache.skills.filter(s => {
3558
+ if (cat && s.category !== cat) return false;
3559
+ if (!q) return true;
3560
+ return s.slug.toLowerCase().includes(q)
3561
+ || (s.name || '').toLowerCase().includes(q)
3562
+ || (s.description || '').toLowerCase().includes(q);
3563
+ });
3564
+ if (filtered.length === 0) {
3565
+ listEl.innerHTML = '<p class="muted">(no skills match)</p>';
3566
+ return;
3567
+ }
3568
+ // Group by category for clearer scan
3569
+ const byCat = {};
3570
+ for (const s of filtered) {
3571
+ if (!byCat[s.category]) byCat[s.category] = [];
3572
+ byCat[s.category].push(s);
3573
+ }
3574
+ const html = [];
3575
+ for (const cat of Object.keys(byCat).sort()) {
3576
+ html.push(`<h3 style="margin:18px 0 8px;font-size:14px;color:var(--muted,#656d76)">${escapeHtmlSafe(cat)} <span style="font-weight:400">(${byCat[cat].length})</span></h3>`);
3577
+ html.push('<table class="data-table"><tbody>');
3578
+ for (const s of byCat[cat]) {
3579
+ const agentBadge = s.agents.map(a => `<span style="display:inline-block;padding:1px 6px;margin-right:4px;border-radius:3px;background:${a==='claude'?'#fde68a':'#bfdbfe'};font-size:11px;color:#1f2328">${a}</span>`).join('');
3580
+ const descPreview = (s.description || '').slice(0, 140) + ((s.description || '').length > 140 ? '…' : '');
3581
+ html.push(`<tr style="cursor:pointer" data-skill="${escapeHtmlSafe(s.slug)}">
3582
+ <td style="width:240px;vertical-align:top"><code>${escapeHtmlSafe(s.slug)}</code></td>
3583
+ <td style="width:120px;vertical-align:top">${agentBadge}</td>
3584
+ <td style="vertical-align:top;color:var(--muted,#656d76)">${escapeHtmlSafe(descPreview)}</td>
3585
+ </tr>`);
3586
+ }
3587
+ html.push('</tbody></table>');
3588
+ }
3589
+ listEl.innerHTML = html.join('');
3590
+ listEl.querySelectorAll('[data-skill]').forEach(tr => {
3591
+ tr.addEventListener('click', () => openSkillModal(tr.getAttribute('data-skill')));
3592
+ });
3593
+ }
3594
+ async function openSkillModal(slug) {
3595
+ const m = document.getElementById('modal');
3596
+ const bg = document.getElementById('modal-bg');
3597
+ m.innerHTML = `<p class="muted">Loading ${escapeHtmlSafe(slug)}…</p>`;
3598
+ bg.classList.add('show');
3599
+ try {
3600
+ const data = await api('/api/skills/' + encodeURIComponent(slug));
3601
+ // Render the SKILL.md content. Strip the frontmatter block, then
3602
+ // run through marked() if available, otherwise plain <pre>.
3603
+ let body = data.content || '';
3604
+ if (body.startsWith('---')) {
3605
+ const end = body.indexOf('\n---', 3);
3606
+ if (end >= 0) body = body.slice(end + 4).trimStart();
3607
+ }
3608
+ const rendered = (window.marked && typeof marked.parse === 'function')
3609
+ ? marked.parse(body)
3610
+ : `<pre style="white-space:pre-wrap;font-size:12px">${escapeHtmlSafe(body)}</pre>`;
3611
+ const agentBadge = data.agents.map(a => `<span style="display:inline-block;padding:1px 6px;margin-right:4px;border-radius:3px;background:${a==='claude'?'#fde68a':'#bfdbfe'};font-size:11px;color:#1f2328">${a}</span>`).join('');
3612
+ m.innerHTML = `
3613
+ <div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:8px">
3614
+ <h2 style="margin:0"><code>${escapeHtmlSafe(data.slug)}</code></h2>
3615
+ <button type="button" class="btn" onclick="document.getElementById('modal-bg').classList.remove('show')">${T.close || 'Close'}</button>
3616
+ </div>
3617
+ <p class="muted" style="font-size:12px;margin:0 0 8px">
3618
+ ${agentBadge} · <strong>${escapeHtmlSafe(data.category)}</strong>
3619
+ · <code style="font-size:11px">${escapeHtmlSafe(data.path)}</code>
3620
+ </p>
3621
+ <p style="margin:0 0 16px"><strong>${escapeHtmlSafe(data.name)}</strong> — ${escapeHtmlSafe(data.description)}</p>
3622
+ <div class="markdown-body" style="border-top:1px solid var(--border,#d0d7de);padding-top:12px;max-height:60vh;overflow-y:auto">${rendered}</div>
3623
+ `;
3624
+ } catch (err) {
3625
+ m.innerHTML = `<p style="color:#d00">${escapeHtmlSafe(String(err.message || err))}</p>
3626
+ <button type="button" class="btn" onclick="document.getElementById('modal-bg').classList.remove('show')">${T.close || 'Close'}</button>`;
3627
+ }
3628
+ }
3629
+ document.getElementById('skills-search')?.addEventListener('input', renderSkills);
3630
+ document.getElementById('skills-cat-filter')?.addEventListener('change', renderSkills);
3631
+ document.getElementById('btn-skills-refresh')?.addEventListener('click', loadSkills);
3632
+
3633
+ // ─── skillhub.cn remote hot ───
3634
+ async function loadSkillsRemote() {
3635
+ const listEl = document.getElementById('skills-remote-list');
3636
+ const metaEl = document.getElementById('skills-remote-meta');
3637
+ if (listEl) listEl.innerHTML = '<p class="muted">' + (T.loading || 'Loading…') + '</p>';
3638
+ try {
3639
+ const data = await api('/api/skills/remote/hot');
3640
+ const skills = (data && data.skills) || [];
3641
+ if (metaEl) {
3642
+ const cacheStr = data.cached
3643
+ ? ` · ${T.skillsRemoteCached || '缓存命中'}`
3644
+ : '';
3645
+ const staleStr = data.stale ? ` · ⚠️ ${T.skillsRemoteStale || '上游不可达,用旧数据'}` : '';
3646
+ metaEl.textContent = `(${skills.length}${cacheStr}${staleStr})`;
3647
+ }
3648
+ const html = ['<table class="data-table"><thead><tr>',
3649
+ `<th style="width:200px">${T.skillsCol || 'slug'}</th>`,
3650
+ `<th>${T.skillsName || '名称 / 描述'}</th>`,
3651
+ `<th style="width:100px;text-align:right">${T.skillsStars || '⭐ stars'}</th>`,
3652
+ `<th style="width:110px;text-align:right">${T.skillsInstalls || '⬇ installs'}</th>`,
3653
+ '</tr></thead><tbody>'];
3654
+ for (const s of skills) {
3655
+ const desc = (s.description || '').replace(/\n+/g, ' ').slice(0, 150);
3656
+ const more = (s.description || '').length > 150 ? '…' : '';
3657
+ html.push(`<tr style="cursor:pointer" data-remote-slug="${escapeHtmlSafe(s.slug)}">
3658
+ <td style="vertical-align:top"><code>${escapeHtmlSafe(s.slug)}</code><div class="muted" style="font-size:11px">${escapeHtmlSafe(s.category||'')}</div></td>
3659
+ <td style="vertical-align:top">
3660
+ <div><strong>${escapeHtmlSafe(s.name||s.slug)}</strong> <span class="muted" style="font-size:11px">v${escapeHtmlSafe(s.version||'?')}</span></div>
3661
+ <div class="muted" style="font-size:12px;margin-top:2px">${escapeHtmlSafe(desc)}${more}</div>
3662
+ </td>
3663
+ <td style="vertical-align:top;text-align:right">${(s.stars||0).toLocaleString()}</td>
3664
+ <td style="vertical-align:top;text-align:right">${(s.installs||0).toLocaleString()}</td>
3665
+ </tr>`);
3666
+ }
3667
+ html.push('</tbody></table>');
3668
+ if (skills.length === 0) listEl.innerHTML = '<p class="muted">(empty)</p>';
3669
+ else listEl.innerHTML = html.join('');
3670
+ listEl.querySelectorAll('[data-remote-slug]').forEach(tr => {
3671
+ tr.addEventListener('click', () => openRemoteSkillModal(tr.getAttribute('data-remote-slug'), skills));
3672
+ });
3673
+ } catch (err) {
3674
+ if (listEl) listEl.innerHTML = '<p style="color:#d00">' + escapeHtmlSafe(String(err.message || err)) + '</p>';
3675
+ }
3676
+ }
3677
+ function openRemoteSkillModal(slug, skills) {
3678
+ const s = skills.find(x => x.slug === slug);
3679
+ if (!s) return;
3680
+ const m = document.getElementById('modal');
3681
+ const bg = document.getElementById('modal-bg');
3682
+ const desc = escapeHtmlSafe(s.description || '');
3683
+ // Three install paths the user can pick. We never auto-execute —
3684
+ // operator's choice to copy + paste into shell or forward to agent.
3685
+ const installCmds = [
3686
+ { label: 'CLI (host)', cmd: 'skillhub install ' + s.slug },
3687
+ { label: 'CLI dry-run', cmd: 'skillhub search ' + s.slug },
3688
+ { label: 'IM 让 agent 安装', cmd: '请用 skillhub 安装 ' + s.slug + ' 技能' },
3689
+ ];
3690
+ const cmdRows = installCmds.map(c =>
3691
+ `<div style="margin-top:6px"><span class="muted" style="font-size:11px;display:inline-block;width:130px">${escapeHtmlSafe(c.label)}</span>
3692
+ <code style="font-size:12px">${escapeHtmlSafe(c.cmd)}</code>
3693
+ <button type="button" class="btn" style="font-size:11px;padding:1px 6px;margin-left:6px" onclick="navigator.clipboard.writeText('${c.cmd.replace(/'/g,"\\'")}').then(()=>{this.textContent='✓'; setTimeout(()=>{this.textContent='复制';},1500);})">复制</button>
3694
+ </div>`).join('');
3695
+ m.innerHTML = `
3696
+ <div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:8px">
3697
+ <h2 style="margin:0">${escapeHtmlSafe(s.name || s.slug)} <span class="muted" style="font-size:13px;font-weight:400">v${escapeHtmlSafe(s.version || '?')}</span></h2>
3698
+ <button type="button" class="btn" onclick="document.getElementById('modal-bg').classList.remove('show')">${T.close || 'Close'}</button>
3699
+ </div>
3700
+ <p class="muted" style="font-size:12px;margin:0 0 10px">
3701
+ <code>${escapeHtmlSafe(s.slug)}</code>
3702
+ ${s.category ? ' · ' + escapeHtmlSafe(s.category) : ''}
3703
+ · ⭐ ${(s.stars||0).toLocaleString()}
3704
+ · ⬇ ${(s.installs||0).toLocaleString()} installs
3705
+ · ${(s.downloads||0).toLocaleString()} downloads
3706
+ </p>
3707
+ <div style="white-space:pre-wrap;border-top:1px solid var(--border,#d0d7de);padding-top:10px;font-size:13px">${desc}</div>
3708
+ <div style="margin-top:14px;padding-top:10px;border-top:1px solid var(--border,#d0d7de)">
3709
+ <strong style="font-size:13px">${T.skillsHowInstall || '安装方式(任选一个;agim 不会自动跑)'}</strong>
3710
+ ${cmdRows}
3711
+ <p class="muted" style="font-size:11px;margin-top:8px">
3712
+ ${T.skillsHostHint || '前两条要先在 host 上跑 skillhub install.sh。详见 docs/memory-and-vector.md 或 skillhub.cn。'}
3713
+ </p>
3714
+ </div>
3715
+ `;
3716
+ bg.classList.add('show');
3717
+ }
3718
+ document.getElementById('btn-skills-remote-refresh')?.addEventListener('click', loadSkillsRemote);
3719
+
3364
3720
  // Initial load
3365
3721
  loadJobs();
3366
3722
  </script>