limixbot 0.3.22 → 0.3.24

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 (128) hide show
  1. package/dist/assets/{ActivityPanel-ChjWicVw.js → ActivityPanel-BjjmX49M.js} +1 -1
  2. package/dist/assets/App-BWtQhGWp.js +242 -0
  3. package/dist/assets/{AppEventEditor-NdlNZYiU.js → AppEventEditor-Bq1-MGxi.js} +1 -1
  4. package/dist/assets/{BotSettingsDialog-DKIuZdV6.js → BotSettingsDialog-CVlvG4Rb.js} +4 -4
  5. package/dist/assets/{BrowserPanel-DjEwiL31.js → BrowserPanel-CPLcno_m.js} +1 -1
  6. package/dist/assets/{BrowserProfilesManager-1DLRt4N9.js → BrowserProfilesManager-D9JsCp1k.js} +1 -1
  7. package/dist/assets/{CloudAddDialog-C0DMYGOm.js → CloudAddDialog-DcmN7A5a.js} +1 -1
  8. package/dist/assets/{ComputerFilesPane-Dk8NcRrf.js → ComputerFilesPane-Bjx6TGmM.js} +1 -1
  9. package/dist/assets/{ComputerPanel-DoVcLtW7.js → ComputerPanel-C9Xx4Ifs.js} +2 -2
  10. package/dist/assets/{DataPanel-C0Fx7K2G.js → DataPanel-IfpQCi3b.js} +1 -1
  11. package/dist/assets/{DesktopViewer-DTBmRKHz.js → DesktopViewer-RjjUUlrQ.js} +1 -1
  12. package/dist/assets/{FilesView-D5s67U7H.js → FilesView-D_0GwHAN.js} +1 -1
  13. package/dist/assets/{GrokMoveDialog-itIjM39_.js → GrokMoveDialog-CUie-0en.js} +1 -1
  14. package/dist/assets/{InspectorPanel-NMTlN3H-.js → InspectorPanel-BuFL6wWf.js} +1 -1
  15. package/dist/assets/{KeyboardShortcutsModal-O3skmvQm.js → KeyboardShortcutsModal-uOhXagTA.js} +1 -1
  16. package/dist/assets/{LiveDesktop-CO9mKNtY.js → LiveDesktop-BwpZa-Ri.js} +1 -1
  17. package/dist/assets/{LocalVmWorkspace-BLrh4g6E.js → LocalVmWorkspace-CousHKOb.js} +1 -1
  18. package/dist/assets/{MacLocalControl-DyhXTfbp.js → MacLocalControl-BKDzplO2.js} +1 -1
  19. package/dist/assets/{MarkdownFilePreview-CyJn1rQP.js → MarkdownFilePreview-CCwc1Tek.js} +1 -1
  20. package/dist/assets/{MenuMotion-CobYpP7L.js → MenuMotion-CSgaJLc2.js} +1 -1
  21. package/dist/assets/NewBotDialog-BtvoCpqN.js +1 -0
  22. package/dist/assets/{PhoneAppDialog-D_CgApJp.js → PhoneAppDialog-C1hIwyT8.js} +1 -1
  23. package/dist/assets/{PlacesRail-CUntVv3u.js → PlacesRail-PP97GpM6.js} +1 -1
  24. package/dist/assets/{PluginsPanel-C2bbrSB3.js → PluginsPanel-DpUEC1zc.js} +1 -1
  25. package/dist/assets/{ProIntroduction-8BVPIU2J.js → ProIntroduction-C-7uexIW.js} +1 -1
  26. package/dist/assets/{RemoteAgentSettingsPanel-JMsDeyDs.js → RemoteAgentSettingsPanel-DtofJtgF.js} +1 -1
  27. package/dist/assets/{RoomPanel-BcFX5BFK.js → RoomPanel-CHbgQ0xd.js} +1 -1
  28. package/dist/assets/{RoutinesPage-BZhG-dR9.js → RoutinesPage-ZdFTYQc-.js} +2 -2
  29. package/dist/assets/SettingsModal-Cdz3bEYZ.js +15 -0
  30. package/dist/assets/{ShareTeamDialog-Cf4VzjYH.js → ShareTeamDialog-CNu6Kwet.js} +1 -1
  31. package/dist/assets/{SignedInSites-31u9pN2b.js → SignedInSites-CyWcDbLW.js} +1 -1
  32. package/dist/assets/TeamLibraryPanel-DMhdlaSH.js +1 -0
  33. package/dist/assets/{TeamMapPage-BrrWMFul.js → TeamMapPage-C2PnNdil.js} +1 -1
  34. package/dist/assets/{TriggersPage-GuMhNUuU.js → TriggersPage-B_br6xBZ.js} +1 -1
  35. package/dist/assets/{VisibilitySection-C6eVm1Hd.js → VisibilitySection-DH0WrBCn.js} +1 -1
  36. package/dist/assets/{WebhookTrigger-BGm39FBT.js → WebhookTrigger-BASBoMb_.js} +1 -1
  37. package/dist/assets/{WorksOnGrid-9NWPGzbE.js → WorksOnGrid-CboITAAs.js} +1 -1
  38. package/dist/assets/{abnfDiagram-YUKMZFIV-DEyDkr0Q.js → abnfDiagram-YUKMZFIV-CJ69o_vp.js} +1 -1
  39. package/dist/assets/{activity-CbpNb0MQ.js → activity-BdRjGS2C.js} +1 -1
  40. package/dist/assets/{architectureDiagram-47P4ROYG-CGTtcA9N.js → architectureDiagram-47P4ROYG-BH57AmKC.js} +1 -1
  41. package/dist/assets/{blockDiagram-E7TT5TSB-DT9DFOL_.js → blockDiagram-E7TT5TSB-D3ebMs_o.js} +1 -1
  42. package/dist/assets/{c4Diagram-CRA5TL53-DpERK75F.js → c4Diagram-CRA5TL53-6TrdcGZR.js} +1 -1
  43. package/dist/assets/{calendar-days-DE6WL-x2.js → calendar-days-DiMBTH2e.js} +1 -1
  44. package/dist/assets/channel-k2J2GMwv.js +1 -0
  45. package/dist/assets/{chunk-2Q5K7J3B-wHwpVFi3.js → chunk-2Q5K7J3B-BemwHdji.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-CfqwqtdU.js → chunk-5VM5RSS4-CQZbLBld.js} +1 -1
  47. package/dist/assets/{chunk-7TKQ45FW-BRxQFxT-.js → chunk-7TKQ45FW-Dy6EXFTX.js} +1 -1
  48. package/dist/assets/{chunk-AW2ZBBNX-Ca3w_5Wz.js → chunk-AW2ZBBNX-BbKGGyK8.js} +1 -1
  49. package/dist/assets/{chunk-FVRAUYC3-CO8Syt2Z.js → chunk-FVRAUYC3-DHqUalc2.js} +2 -2
  50. package/dist/assets/{chunk-HTAEGDNF-Cnx9DnWv.js → chunk-HTAEGDNF-B66Vjlwq.js} +1 -1
  51. package/dist/assets/{chunk-JWPE2WC7-DefKEriK.js → chunk-JWPE2WC7-DrtdT6L3.js} +1 -1
  52. package/dist/assets/{chunk-KQW6MTUR-B6WWWz_R.js → chunk-KQW6MTUR-t7RW8fFT.js} +1 -1
  53. package/dist/assets/{chunk-XXDRQBXY-BFU1-B3X.js → chunk-XXDRQBXY-CntyS42N.js} +1 -1
  54. package/dist/assets/{classDiagram-v2-K4WV4PDN-BePCpZI3.js → classDiagram-v2-K4WV4PDN-KlNCgeTt.js} +1 -1
  55. package/dist/assets/{coins-D1fhjgas.js → coins-BHKpT34i.js} +1 -1
  56. package/dist/assets/{core-GxNVTB-X.js → core-DEy6KDLR.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-D8emtvin.js → cose-bilkent-JH36ORCC-C_cXoOGT.js} +1 -1
  58. package/dist/assets/{cynefin-EF2NZ3EQ-DUEsfFVq.js → cynefin-EF2NZ3EQ-DGuwW5zt.js} +1 -1
  59. package/dist/assets/{cynefinDiagram-3GCD6N5R-duonepNA.js → cynefinDiagram-3GCD6N5R-qMzxXkVk.js} +1 -1
  60. package/dist/assets/{dagre-W4DXFKR2-D4NywFNB.js → dagre-W4DXFKR2-Q-osY3Yi.js} +1 -1
  61. package/dist/assets/{diagram-2UJZ2QOL-cjiJTdiH.js → diagram-2UJZ2QOL-DyVJSBOF.js} +1 -1
  62. package/dist/assets/{diagram-OVF4WLC6-D6M8A9DS.js → diagram-OVF4WLC6-C5qBFdpm.js} +1 -1
  63. package/dist/assets/{diagram-PFPMY2P6-CVAzAz2N.js → diagram-PFPMY2P6-C1PW3U2P.js} +1 -1
  64. package/dist/assets/{diagram-UMYRVEAY-DLvfYJwO.js → diagram-UMYRVEAY-D2pa0Mzi.js} +1 -1
  65. package/dist/assets/{diagram-YEKJPTXX-D8qCH--0.js → diagram-YEKJPTXX-BIzOA1li.js} +1 -1
  66. package/dist/assets/{diagram-ZIFT7M5P-s55SHvTR.js → diagram-ZIFT7M5P-B1UL3O8m.js} +1 -1
  67. package/dist/assets/{ebnfDiagram-VR2GEFS7-B93cNJNn.js → ebnfDiagram-VR2GEFS7-Cxu-kU8s.js} +1 -1
  68. package/dist/assets/{elk-IJKZMXRS-BsJqO8aU.js → elk-IJKZMXRS-DJS39Bm9.js} +1 -1
  69. package/dist/assets/{erDiagram-O2IAPWRE-BXF1G3Js.js → erDiagram-O2IAPWRE-Cb31BS5L.js} +1 -1
  70. package/dist/assets/{file-up-Cg2kYQkd.js → file-up-DWGlVRGP.js} +1 -1
  71. package/dist/assets/{flowDiagram-OXPTDLAJ-BKjjYwz9.js → flowDiagram-OXPTDLAJ-BaPTzS6m.js} +1 -1
  72. package/dist/assets/{ganttDiagram-R7TSEDQI-DhtZJtPm.js → ganttDiagram-R7TSEDQI-B_Lr-26i.js} +1 -1
  73. package/dist/assets/{gitGraphDiagram-XJZIOB7I-BfRlcF5z.js → gitGraphDiagram-XJZIOB7I-UHyDFX6a.js} +1 -1
  74. package/dist/assets/{import-name-B8g_Hj2F.js → import-name-Cxwtm5XO.js} +1 -1
  75. package/dist/assets/{index-B8LCAG4-.js → index-BME9PdWM.js} +1 -1
  76. package/dist/assets/{index-LBC94myy.js → index-BSrb6nsc.js} +2 -2
  77. package/dist/assets/{infoDiagram-5W2HQ5XZ-CywmYwco.js → infoDiagram-5W2HQ5XZ-B7ovBJYA.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-K3B6WC7H-DpXXqau5.js → ishikawaDiagram-K3B6WC7H-BYS-TlrF.js} +1 -1
  79. package/dist/assets/{journeyDiagram-COXZFDF6-D1uMWGbz.js → journeyDiagram-COXZFDF6-jJxKYEtT.js} +1 -1
  80. package/dist/assets/{kanban-definition-P3RFRI5V-Bz0zjqQs.js → kanban-definition-P3RFRI5V-DyiFYaIK.js} +1 -1
  81. package/dist/assets/{mcp-servers-C_F4MXjY.js → mcp-servers-DXFuhTlS.js} +1 -1
  82. package/dist/assets/{mermaid.core-Dnv6mcJ4.js → mermaid.core-D3YJgsiB.js} +4 -4
  83. package/dist/assets/{mindmap-definition-LZFPQGKD-CFEkTgom.js → mindmap-definition-LZFPQGKD-DnbWodUY.js} +1 -1
  84. package/dist/assets/{minimize-o-OFVasg.js → minimize-Af1aU3Tz.js} +1 -1
  85. package/dist/assets/{monitor-7oLB-7rZ.js → monitor-DuYHWIyw.js} +1 -1
  86. package/dist/assets/{new-bot-CFOYR98I.js → new-bot-TwOnvVO1.js} +1 -1
  87. package/dist/assets/{org-library-TYYMsAm5.js → org-library-BLD6YHcM.js} +1 -1
  88. package/dist/assets/{package-format-CrrPupM8.js → package-format-72bbOy2w.js} +1 -1
  89. package/dist/assets/{pegDiagram-BGZESJAR-DKt4SMwc.js → pegDiagram-BGZESJAR-CcOuP6zx.js} +1 -1
  90. package/dist/assets/{pieDiagram-CAPJLFHJ-B9taKUge.js → pieDiagram-CAPJLFHJ-DhCU7Sfy.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-GDMTTRAM-DQjB7Jbz.js → quadrantDiagram-GDMTTRAM-7OziX26h.js} +1 -1
  92. package/dist/assets/{rail-BBdJiVR1.js → rail-DI2iZJYt.js} +1 -1
  93. package/dist/assets/{railroadDiagram-SM67HX2B-DQOivC56.js → railroadDiagram-SM67HX2B-rg44R4U2.js} +1 -1
  94. package/dist/assets/{remote-desktop-panel-BmwpQiDu.js → remote-desktop-panel-BPKKvcHT.js} +1 -1
  95. package/dist/assets/{requirementDiagram-X7JNWC4A-BLgwXDEl.js → requirementDiagram-X7JNWC4A-DFNvg72O.js} +1 -1
  96. package/dist/assets/{sankeyDiagram-UM26HJRW-g2GlJNjj.js → sankeyDiagram-UM26HJRW-CUOrQF2i.js} +1 -1
  97. package/dist/assets/{sequenceDiagram-ZO4K6R2Y-_08U7Ixl.js → sequenceDiagram-ZO4K6R2Y-4HKsH1wt.js} +1 -1
  98. package/dist/assets/{sizeCapture-INFHLROL-DZPfU3_U.js → sizeCapture-INFHLROL-BEXQDTx-.js} +1 -1
  99. package/dist/assets/{stateDiagram-v2-TBUQTH76-PXBjCkxd.js → stateDiagram-v2-TBUQTH76-BYBLUqiy.js} +1 -1
  100. package/dist/assets/{styles-BHtUK2qB.js → styles-C2_XZv1s.js} +1 -1
  101. package/dist/assets/styles-Di_mG4l3.css +1 -0
  102. package/dist/assets/{swimlanes-N4OXWK64-q1MhehBF.js → swimlanes-N4OXWK64-D-bp8wWN.js} +1 -1
  103. package/dist/assets/swimlanesDiagram-K3J5GTZL-DTQBdgKt.js +8 -0
  104. package/dist/assets/{team-share-xt--eeM_.js → team-share-Dpu1nJNz.js} +1 -1
  105. package/dist/assets/{timeline-definition-YOQAKGHF-UBKh0GG-.js → timeline-definition-YOQAKGHF-CtLFkcud.js} +1 -1
  106. package/dist/assets/{usecaseDiagram-VIAY4XPW-CccLioTT.js → usecaseDiagram-VIAY4XPW-AWOhUKvG.js} +1 -1
  107. package/dist/assets/{vennDiagram-BLWOH2XV-3Q39wQgE.js → vennDiagram-BLWOH2XV-C_OYDBvp.js} +1 -1
  108. package/dist/assets/{vi-CVDyZGky.js → vi-P4zxF4j0.js} +1 -1
  109. package/dist/assets/{wardleyDiagram-YMQ3BMBF-DtgEYK7j.js → wardleyDiagram-YMQ3BMBF-CdxMh8tE.js} +1 -1
  110. package/dist/assets/{xychartDiagram-TAQBALBS-DLTmsflK.js → xychartDiagram-TAQBALBS-BL2spC5z.js} +1 -1
  111. package/dist/index.html +3 -3
  112. package/dist/rail.html +5 -5
  113. package/dist-server/index.js +1591 -1578
  114. package/dist-server/package.json +1 -1
  115. package/dist-server/server/index.js +1 -1
  116. package/dist-server/server/initial-greeting.js +4 -0
  117. package/dist-server/server/limix-bot-library.js +178 -0
  118. package/dist-server/server/limix-skill-hub.js +6 -8
  119. package/dist-server/server/routes/bot-skills.js +6 -0
  120. package/dist-server/server/routes/teams.js +1 -1
  121. package/package.json +1 -1
  122. package/dist/assets/App-D-SXyZXT.js +0 -242
  123. package/dist/assets/NewBotDialog-coMM5O6x.js +0 -1
  124. package/dist/assets/SettingsModal-CWtO0RWS.js +0 -11
  125. package/dist/assets/TeamLibraryPanel-Bzvx_MlH.js +0 -1
  126. package/dist/assets/channel-CETDvzTH.js +0 -1
  127. package/dist/assets/styles-B9SGxFnK.css +0 -1
  128. package/dist/assets/swimlanesDiagram-K3J5GTZL-pLYSpGVX.js +0 -8
@@ -1 +1 @@
1
- {"type":"module","version":"0.3.22"}
1
+ {"type":"module","version":"0.3.24"}
@@ -661,7 +661,7 @@ const CLOUD_ENV = { ...process.env, ...CLOUD_SECRETS };
661
661
  const CLOUD_HOME = cloudHomeConfiguration(CLOUD_ENV);
662
662
  const CLOUD_TEAM = CLOUD_HOME?.team ?? null, CLOUD_PERSONAL = CLOUD_HOME !== null && CLOUD_TEAM === null, TEAM_LABEL = CLOUD_TEAM ? { label: CLOUD_TEAM.name } : {};
663
663
  /** The language set on this server, else (on a Team Cloud) the team's default from the Admin. threadLanguage: the one a line in a conversation is written in, its one person's, else the place's (server/server-lines.ts). */
664
- const appLanguage = () => cfg.language ?? CLOUD_TEAM?.language;
664
+ const appLanguage = () => cfg.language ?? CLOUD_TEAM?.language ?? (process.env.OMB_UI_LANGUAGE?.trim() || undefined);
665
665
  const personLanguages = createPersonLanguages({ dataDir: DATA_DIR, configured: appLanguage, privateTo: (threadId) => teamHost.privateTo(threadId) });
666
666
  const threadLanguage = personLanguages.forThread;
667
667
  setConversationLanguage(threadLanguage);
@@ -6,6 +6,7 @@ const GREETINGS = {
6
6
  es: (name, person) => `Hola${person ? ` ${person}` : ""}, soy ${name}. ¿Qué te gustaría que hiciera?`,
7
7
  "pt-br": (name, person) => `Oi${person ? ` ${person}` : ""}, eu sou ${name}. O que você gostaria que eu fizesse?`,
8
8
  ko: (name, person) => `안녕하세요${person ? `, ${person}님` : ""}. 저는 ${name}입니다. 무엇을 도와드릴까요?`,
9
+ vi: (name, person) => `Xin chào${person ? ` ${person}` : ""}, mình là ${name}. Bạn muốn mình giúp việc gì?`,
9
10
  "zh-tw": (name, person) => `嗨${person ? ` ${person}` : ""},我是 ${name}。你想要我幫你做什麼?`,
10
11
  };
11
12
  /** What the bot says the moment Set me up is pressed: no model is asked, so it is written here. */
@@ -14,6 +15,7 @@ const SETUP_REQUEST_LINES = {
14
15
  es: "Genial. Cuéntame con tus propias palabras en qué te gustaría que te ayude y me prepararé para ello. Por ejemplo: \"ordena mi bandeja de entrada cada mañana\" o \"investiga a nuestros competidores cada semana\".",
15
16
  "pt-br": "Ótimo. Me conte com suas palavras em que você gostaria de ajuda e eu me preparo para isso. Por exemplo: \"organize minha caixa de entrada toda manhã\" ou \"pesquise nossos concorrentes toda semana\".",
16
17
  ko: "좋아요. 도움이 필요한 일을 편하게 말씀해 주시면 거기에 맞게 제가 설정할게요. 예: \"매일 아침 받은편지함 정리해 줘\", \"매주 경쟁사 조사해 줘\".",
18
+ vi: "Tuyệt. Bạn cứ kể bằng lời của mình về việc cần giúp, mình sẽ tự thiết lập để làm việc đó. Ví dụ: \"sắp xếp hộp thư của tôi mỗi sáng\" hoặc \"tìm hiểu đối thủ của chúng tôi mỗi tuần\".",
17
19
  "zh-tw": "好的。用你自己的話告訴我想要幫忙的事,我會照著設定好自己。例如:「每天早上整理我的收件匣」或「每週研究我們的競爭對手」。",
18
20
  };
19
21
  /** The answers a blank bot's greeting offers on its setup card. Picking one sets the bot up for its words, as
@@ -27,6 +29,8 @@ const SETUP_CHOICES = {
27
29
  "Reservas, compras e tarefas", "Dados, planilhas e relatórios"],
28
30
  ko: ["리서치와 주제 깊이 파고들기", "이것저것 지켜보기", "이메일, 캘린더, 메시지",
29
31
  "예약, 쇼핑, 심부름", "데이터, 스프레드시트, 보고서"],
32
+ vi: ["Nghiên cứu và tìm hiểu sâu một chủ đề", "Theo dõi mọi việc giúp tôi", "Email, lịch và tin nhắn",
33
+ "Đặt chỗ, mua sắm và việc vặt", "Dữ liệu, bảng tính và báo cáo"],
30
34
  "zh-tw": ["研究與深入探討主題", "幫我留意各種事情", "電子郵件、行事曆和訊息",
31
35
  "預約、購物和跑腿", "資料、試算表和報告"],
32
36
  };
@@ -0,0 +1,178 @@
1
+ // Limix bot templates: the "Bot dựng sẵn" list in Templates comes from github.com/leomieo/limix-bot-teams.
2
+ // GET /api/limix/bots reads only the light list in catalog.json (cached ten minutes); GET /api/limix/bots/<id> fetches
3
+ // that one bot's file when the person presses "Dùng mẫu". Everything is checked here before it reaches the page, and a
4
+ // template only describes who the bot is: its routines always arrive paused and nothing is connected for the person.
5
+ import { MASCOT_BODY_IDS } from "../shared/mascot-bodies.js";
6
+ import { MAUS_COLOR_NAMES } from "../shared/wire.js";
7
+ const TEAM_LIBRARY_RAW_ROOT = "https://raw.githubusercontent.com/leomieo/limix-bot-teams/main";
8
+ const LIST_TTL_MS = 10 * 60 * 1000;
9
+ const MAX_CATALOG_BYTES = 256_000;
10
+ const MAX_BOT_BYTES = 64_000;
11
+ const CATEGORIES = ["everyday", "work", "writing", "design", "sales", "personal"];
12
+ const ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
13
+ const NOTE_PATH = /^(?:MEMORY\.md|memory\/[a-z0-9][a-z0-9-]{0,63}\.md)$/;
14
+ const TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
15
+ const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
16
+ function text(value, field, max, allowEmpty = false) {
17
+ if (typeof value !== "string" || (!allowEmpty && !value.trim()))
18
+ throw new Error(`${field} is required`);
19
+ const trimmed = value.trim();
20
+ if (trimmed.length > max)
21
+ throw new Error(`${field} is too long`);
22
+ return trimmed;
23
+ }
24
+ function look(value, field) {
25
+ if (!isRecord(value))
26
+ throw new Error(`${field} is invalid`);
27
+ const color = text(value.color, `${field}.color`, 20);
28
+ const mascotBody = text(value.mascotBody, `${field}.mascotBody`, 20);
29
+ if (!MAUS_COLOR_NAMES.includes(color))
30
+ throw new Error(`${field}.color is not a known colour`);
31
+ if (!MASCOT_BODY_IDS.includes(mascotBody))
32
+ throw new Error(`${field}.mascotBody is not a known shape`);
33
+ return { color, mascotBody };
34
+ }
35
+ function category(value, field) {
36
+ const found = CATEGORIES.find((entry) => entry === value);
37
+ if (!found)
38
+ throw new Error(`${field} is not a known category`);
39
+ return found;
40
+ }
41
+ function apps(value, field) {
42
+ if (value === undefined)
43
+ return [];
44
+ if (!Array.isArray(value) || value.length > 10)
45
+ throw new Error(`${field} is invalid`);
46
+ return value.map((app, index) => {
47
+ const slug = text(app, `${field}[${index}]`, 40);
48
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug))
49
+ throw new Error(`${field}[${index}] is invalid`);
50
+ return slug;
51
+ });
52
+ }
53
+ export function parseBotListing(value) {
54
+ if (!isRecord(value) || value.format !== "openmaus.catalog" || value.version !== 1)
55
+ throw new Error("Danh sách mẫu không đúng định dạng");
56
+ if (value.bots === undefined)
57
+ return [];
58
+ if (!Array.isArray(value.bots) || value.bots.length > 200)
59
+ throw new Error("Danh sách bot mẫu không hợp lệ");
60
+ const seen = new Set();
61
+ const out = [];
62
+ value.bots.forEach((raw, index) => {
63
+ const field = `bots[${index}]`;
64
+ try {
65
+ if (!isRecord(raw))
66
+ throw new Error(`${field} is invalid`);
67
+ const id = text(raw.id, `${field}.id`, 64);
68
+ if (!ID.test(id) || seen.has(id))
69
+ throw new Error(`${field}.id is invalid`);
70
+ const routines = typeof raw.routines === "number" && Number.isSafeInteger(raw.routines) && raw.routines >= 0 && raw.routines <= 10 ? raw.routines : 0;
71
+ out.push({
72
+ id, name: text(raw.name, `${field}.name`, 40), title: text(raw.title, `${field}.title`, 80),
73
+ category: category(raw.category, `${field}.category`), description: text(raw.description, `${field}.description`, 300),
74
+ try: text(raw.try, `${field}.try`, 200), look: look(raw.look, `${field}.look`), apps: apps(raw.apps, `${field}.apps`), routines,
75
+ });
76
+ seen.add(id);
77
+ }
78
+ catch {
79
+ // One broken entry hides that entry only; the rest of the list still shows.
80
+ }
81
+ });
82
+ return out;
83
+ }
84
+ export function parseLibraryBot(value, expectedId) {
85
+ if (!isRecord(value) || value.format !== "limix.bot" || value.version !== 1)
86
+ throw new Error("Tệp bot mẫu không đúng định dạng");
87
+ const id = text(value.id, "id", 64);
88
+ if (id !== expectedId)
89
+ throw new Error("Tệp bot mẫu không khớp với danh sách");
90
+ if (!Array.isArray(value.tryAsking) || value.tryAsking.length === 0 || value.tryAsking.length > 6)
91
+ throw new Error("tryAsking is invalid");
92
+ const bot = {
93
+ id, category: category(value.category, "category"), name: text(value.name, "name", 40), title: text(value.title, "title", 80),
94
+ description: text(value.description, "description", 300), soul: text(value.soul, "soul", 8_000), look: look(value.look, "look"),
95
+ tryAsking: value.tryAsking.map((line, index) => text(line, `tryAsking[${index}]`, 200)), apps: apps(value.apps, "apps"),
96
+ };
97
+ if (value.notes !== undefined) {
98
+ if (!isRecord(value.notes) || Object.keys(value.notes).length > 6)
99
+ throw new Error("notes is invalid");
100
+ bot.notes = {};
101
+ for (const [path, note] of Object.entries(value.notes)) {
102
+ if (!NOTE_PATH.test(path))
103
+ throw new Error(`notes path ${path} is not allowed`);
104
+ bot.notes[path] = text(note, `notes.${path}`, 8_000) + "\n";
105
+ }
106
+ }
107
+ if (value.routines !== undefined) {
108
+ if (!Array.isArray(value.routines) || value.routines.length > 5)
109
+ throw new Error("routines is invalid");
110
+ bot.routines = value.routines.map((raw, index) => {
111
+ const field = `routines[${index}]`;
112
+ if (!isRecord(raw) || !isRecord(raw.schedule) || raw.schedule.type !== "daily")
113
+ throw new Error(`${field} is invalid`);
114
+ const time = text(raw.schedule.time, `${field}.schedule.time`, 5);
115
+ if (!TIME.test(time))
116
+ throw new Error(`${field}.schedule.time is invalid`);
117
+ const weekdays = raw.schedule.weekdays;
118
+ if (!Array.isArray(weekdays) || weekdays.length === 0 || weekdays.length > 7 || weekdays.some((day) => !Number.isInteger(day) || day < 0 || day > 6)) {
119
+ throw new Error(`${field}.schedule.weekdays is invalid`);
120
+ }
121
+ // Paused, always: the person turns a routine on after reading it.
122
+ return { name: text(raw.name, `${field}.name`, 80), prompt: text(raw.prompt, `${field}.prompt`, 2_000),
123
+ schedule: { type: "daily", time, weekdays: [...new Set(weekdays)].sort() }, enabled: false, overlap: "skip" };
124
+ });
125
+ }
126
+ return bot;
127
+ }
128
+ async function fetchJson(url, maxBytes) {
129
+ const response = await fetch(url, { headers: { accept: "application/json, text/plain;q=0.9" }, redirect: "error", signal: AbortSignal.timeout(10_000) });
130
+ if (!response.ok)
131
+ throw new Error(`GitHub trả lỗi HTTP ${response.status}`);
132
+ const raw = await response.text();
133
+ if (Buffer.byteLength(raw) > maxBytes)
134
+ throw new Error("Tệp mẫu quá lớn");
135
+ try {
136
+ return JSON.parse(raw);
137
+ }
138
+ catch {
139
+ throw new Error("GitHub không trả về JSON hợp lệ");
140
+ }
141
+ }
142
+ let cached = null;
143
+ /** `fresh`: the "Cập nhật" button. Skips this cache and GitHub's (raw files are cached there for about five minutes). */
144
+ async function listing(fresh = false) {
145
+ if (!fresh && cached && Date.now() - cached.at < LIST_TTL_MS)
146
+ return cached.bots;
147
+ try {
148
+ const bots = parseBotListing(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/catalog.json${fresh ? `?t=${Date.now()}` : ""}`, MAX_CATALOG_BYTES));
149
+ cached = { at: Date.now(), bots };
150
+ return bots;
151
+ }
152
+ catch (error) {
153
+ if (cached)
154
+ return cached.bots; // an older list beats none while GitHub is unreachable
155
+ throw error;
156
+ }
157
+ }
158
+ /** GET /api/limix/bots, POST /api/limix/bots/refresh and GET /api/limix/bots/<id>. Null for any other path. */
159
+ export async function handleLimixBotLibrary(method, path) {
160
+ try {
161
+ if (method === "POST" && path === "/api/limix/bots/refresh")
162
+ return { status: 200, body: { bots: await listing(true) } };
163
+ if (method !== "GET")
164
+ return null;
165
+ if (path === "/api/limix/bots")
166
+ return { status: 200, body: { bots: await listing() } };
167
+ const match = path.match(/^\/api\/limix\/bots\/([a-z0-9][a-z0-9-]{0,63})$/);
168
+ if (!match)
169
+ return null;
170
+ const id = match[1];
171
+ if (!(await listing()).some((bot) => bot.id === id))
172
+ return { status: 404, body: { error: "Không có bot mẫu này" } };
173
+ return { status: 200, body: { bot: parseLibraryBot(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id}.json?t=${Date.now()}`, MAX_BOT_BYTES), id) } };
174
+ }
175
+ catch (error) {
176
+ return { status: 502, body: { error: error instanceof Error ? error.message : String(error) } };
177
+ }
178
+ }
@@ -109,7 +109,8 @@ export function hubSkills() {
109
109
  // A same-named skill the owner imported by hand is not the hub's: never touched by sync.
110
110
  enabled: have?.reviewState === "approved",
111
111
  updateAvailable: Boolean(have && have.checksum !== entry.checksum),
112
- blocked: hubSkillBlocked(entry) ?? (!have && library[entry.id] ? "Thư viện đã có kỹ năng cùng tên không đến từ Hub." : null),
112
+ blocked: !have && library[entry.id] ? "Thư viện đã có kỹ năng cùng tên không đến từ Hub." : null,
113
+ risky: hubSkillBlocked(entry),
113
114
  };
114
115
  });
115
116
  }
@@ -120,7 +121,7 @@ export async function installHubSkill(id, options = {}) {
120
121
  if (!entry)
121
122
  return { error: "Không thấy kỹ năng này trong danh mục Hub. Hãy đồng bộ lại." };
122
123
  const blocked = hubSkillBlocked(entry);
123
- if (blocked)
124
+ if (blocked && !options.allowRisky)
124
125
  return { error: blocked };
125
126
  const installed = installedFromHub().get(id);
126
127
  const existing = readSkillLibraryIndex()[id];
@@ -136,7 +137,7 @@ export async function installHubSkill(id, options = {}) {
136
137
  return { error: `Danh mục thiếu mã kiểm tra cho ${path}` };
137
138
  let response;
138
139
  try {
139
- response = await fetchText(rawUrl(`skills/${id}/${path}`));
140
+ response = await fetchText(rawUrl(`${entry.area === "quarantine" ? "quarantine" : "skills"}/${id}/${path}`));
140
141
  }
141
142
  catch (cause) {
142
143
  return { error: `Không tải được ${path}: ${cause instanceof Error ? cause.message : String(cause)}` };
@@ -180,9 +181,6 @@ export async function installHubSkill(id, options = {}) {
180
181
  export function setHubSkillEnabled(id, enabled) {
181
182
  if (!installedFromHub().has(id))
182
183
  return { error: "Kỹ năng này chưa cài từ Hub" };
183
- const entry = hubCatalog().find((e) => e.id === id);
184
- if (enabled && entry && hubSkillBlocked(entry))
185
- return { error: hubSkillBlocked(entry) };
186
184
  const result = setLibrarySkillReviewState(id, enabled ? "approved" : "disabled");
187
185
  return "error" in result ? { error: result.error } : { ok: true };
188
186
  }
@@ -203,7 +201,7 @@ async function autoInstallEligible() {
203
201
  for (const skill of hubSkills()) {
204
202
  if (budget <= 0)
205
203
  return;
206
- const eligible = !skill.blocked && skill.kind === "instructions-only" && skill.approval_status === "approved" && (skill.grade === "excellent" || skill.grade === "recommended");
204
+ const eligible = !skill.blocked && !skill.risky && skill.kind === "instructions-only" && skill.approval_status === "approved" && (skill.grade === "excellent" || skill.grade === "recommended");
207
205
  if (!eligible || (skill.installed && !skill.updateAvailable))
208
206
  continue;
209
207
  budget -= 1;
@@ -232,7 +230,7 @@ export async function handleLimixHub(method, path, body) {
232
230
  }
233
231
  const id = typeof record.id === "string" ? record.id : "";
234
232
  if (method === "POST" && (path === "/api/limix/hub/install" || path === "/api/limix/hub/update")) {
235
- const result = await installHubSkill(id, { allowTools: record.allowTools === true });
233
+ const result = await installHubSkill(id, { allowTools: record.allowTools === true, allowRisky: record.allowRisky === true });
236
234
  return "error" in result ? { status: 422, body: result } : { status: 200, body: result };
237
235
  }
238
236
  if (method === "POST" && path === "/api/limix/hub/enable") {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { handleLimixBotLibrary } from "../limix-bot-library.js";
2
3
  import { setImportedSkillEnabled } from "../imported-skills.js";
3
4
  import { setNativeSkillEnabled } from "../native-skill-choices.js";
4
5
  import { botSkillTemplateSchema } from "../new-bot-defaults.js";
@@ -14,6 +15,11 @@ export function createBotSkillsRoutes(deps) {
14
15
  const { store, cfg, listBotSkills, readBotSkillFile, stagedSkillListing } = deps;
15
16
  return async ({ req, res, url, path, method, json, readBody }) => {
16
17
  let m = null;
18
+ if (path.startsWith("/api/limix/bots")) {
19
+ const library = await handleLimixBotLibrary(method, path);
20
+ if (library)
21
+ return json(res, library.status, library.body);
22
+ }
17
23
  if (path.startsWith("/api/limix/hub")) {
18
24
  const hub = await handleLimixHub(method, path, method === "POST" ? await readBody(req) : undefined);
19
25
  if (hub)
@@ -159,7 +159,7 @@ export function createTeamRoutes(deps) {
159
159
  }
160
160
  if (method === "GET" && path === "/api/team-library/catalog") {
161
161
  try {
162
- return json(res, 200, await fetchTeamCatalog());
162
+ return json(res, 200, await fetchTeamCatalog(url.searchParams.get("fresh") ? ((input, init) => fetch(`${String(input)}?t=${Date.now()}`, init)) : fetch));
163
163
  }
164
164
  catch (error) {
165
165
  return json(res, 502, { error: error instanceof Error ? error.message : "The team library is unavailable" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limixbot",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Run the Limix Bot server anywhere and pair your devices to it (Leox ৹ Limix AI)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",