limixbot 0.3.23 → 0.3.25

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 (140) hide show
  1. package/dist/assets/{ActivityPanel-BkpnvFMV.js → ActivityPanel-CgPkGKZZ.js} +1 -1
  2. package/dist/assets/{App-5fWd2qWI.js → App-lurtH-qI.js} +90 -90
  3. package/dist/assets/{AppEventEditor-4U6g9rYH.js → AppEventEditor-G_71EWui.js} +1 -1
  4. package/dist/assets/BotSettingsDialog-BUvuLu53.js +16 -0
  5. package/dist/assets/{BrowserPanel-DKxFFmVF.js → BrowserPanel-DrKZNyYB.js} +1 -1
  6. package/dist/assets/{BrowserProfilesManager-DbsOGGoV.js → BrowserProfilesManager-CtiUbbdv.js} +1 -1
  7. package/dist/assets/{CloudAddDialog-I2EDWXA8.js → CloudAddDialog-D4BX2927.js} +1 -1
  8. package/dist/assets/{ComputerFilesPane-DA4Txmh9.js → ComputerFilesPane-RnmlJhlD.js} +1 -1
  9. package/dist/assets/{ComputerPanel-B3EgKF7M.js → ComputerPanel-D8upQSmd.js} +2 -2
  10. package/dist/assets/{DataPanel-DALLlVzz.js → DataPanel-MZcku4SL.js} +1 -1
  11. package/dist/assets/{DesktopViewer-BTFnhvej.js → DesktopViewer-p7N7cqle.js} +1 -1
  12. package/dist/assets/{FilesView-DfvDhNdO.js → FilesView-CDbtoPxh.js} +1 -1
  13. package/dist/assets/{GrokMoveDialog-CVgxiDZG.js → GrokMoveDialog-BebZb43R.js} +1 -1
  14. package/dist/assets/{InspectorPanel-B8d5f_Z7.js → InspectorPanel-BMkRIuTz.js} +1 -1
  15. package/dist/assets/{KeyboardShortcutsModal-C4Sy1Y5Q.js → KeyboardShortcutsModal-3XyVcNTZ.js} +1 -1
  16. package/dist/assets/{LiveDesktop-BdLw7_qb.js → LiveDesktop-BkRR5lCn.js} +1 -1
  17. package/dist/assets/{LocalVmWorkspace-Dd8v6HSb.js → LocalVmWorkspace-9l9iWtAy.js} +1 -1
  18. package/dist/assets/{MacLocalControl-DBItoJQX.js → MacLocalControl-B45d0l7H.js} +1 -1
  19. package/dist/assets/{MarkdownFilePreview-tXLtgk1m.js → MarkdownFilePreview-ChR-f9oD.js} +1 -1
  20. package/dist/assets/{MenuMotion-DnbwlBMa.js → MenuMotion-BBaxdyL_.js} +1 -1
  21. package/dist/assets/NewBotDialog-C_uQRhxy.js +1 -0
  22. package/dist/assets/{PhoneAppDialog-D4klvpz5.js → PhoneAppDialog-CDO1xUXD.js} +1 -1
  23. package/dist/assets/{PlacesRail-BYBRFGDz.js → PlacesRail-iu-rjh5s.js} +1 -1
  24. package/dist/assets/{PluginsPanel-DUOw0nhP.js → PluginsPanel-DlQBRYQB.js} +1 -1
  25. package/dist/assets/{ProIntroduction-B1eqbu20.js → ProIntroduction-RbtTSiR6.js} +1 -1
  26. package/dist/assets/{RemoteAgentSettingsPanel-B46lWStL.js → RemoteAgentSettingsPanel-C8r-80J4.js} +1 -1
  27. package/dist/assets/{RoomPanel-DBKyhryu.js → RoomPanel-BIUTnm5N.js} +1 -1
  28. package/dist/assets/{RoutinesPage-CCSHgNRs.js → RoutinesPage-DROK-336.js} +2 -2
  29. package/dist/assets/{SettingsModal-CwXiblca.js → SettingsModal--yYGUYK2.js} +3 -3
  30. package/dist/assets/{ShareTeamDialog-DuSs1saG.js → ShareTeamDialog-CVnQoRVQ.js} +1 -1
  31. package/dist/assets/{SignedInSites-BdxFX6Wt.js → SignedInSites-B-iBIlfL.js} +1 -1
  32. package/dist/assets/TeamLibraryPanel-CwVaJVC_.js +1 -0
  33. package/dist/assets/{TeamMapPage-PW3Ots-y.js → TeamMapPage-kXk9hCNu.js} +1 -1
  34. package/dist/assets/{TriggersPage-D5jH7bR3.js → TriggersPage-prZBUP3d.js} +1 -1
  35. package/dist/assets/{VisibilitySection-yE3ZIXKk.js → VisibilitySection-DJjvTyLx.js} +1 -1
  36. package/dist/assets/{WebhookTrigger-doGyrUZA.js → WebhookTrigger-BAFikH71.js} +1 -1
  37. package/dist/assets/{WorksOnGrid-DWvjM4aV.js → WorksOnGrid-DJmfLtP8.js} +1 -1
  38. package/dist/assets/{abnfDiagram-YUKMZFIV-Bs9i-eLj.js → abnfDiagram-YUKMZFIV-DUI6uevQ.js} +1 -1
  39. package/dist/assets/{activity-DIhOTAmd.js → activity-DVLFekjx.js} +1 -1
  40. package/dist/assets/{architectureDiagram-47P4ROYG-CR-7WHVu.js → architectureDiagram-47P4ROYG-CMELW5_4.js} +1 -1
  41. package/dist/assets/{blockDiagram-E7TT5TSB-Cx9qUsrY.js → blockDiagram-E7TT5TSB-C3xD0z1e.js} +1 -1
  42. package/dist/assets/{c4Diagram-CRA5TL53-CtuUaTlE.js → c4Diagram-CRA5TL53-Cm9X0GEV.js} +1 -1
  43. package/dist/assets/{calendar-days-DyyECwZf.js → calendar-days-CDvwJ_d4.js} +1 -1
  44. package/dist/assets/channel-CsOUN1ZX.js +1 -0
  45. package/dist/assets/{chunk-2Q5K7J3B-DIvFRpG0.js → chunk-2Q5K7J3B-LWcbjcjL.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-Cxp3RKyR.js → chunk-5VM5RSS4-B3rNoWeE.js} +1 -1
  47. package/dist/assets/{chunk-7TKQ45FW-DSOW_9p2.js → chunk-7TKQ45FW-FL9R0rHe.js} +1 -1
  48. package/dist/assets/{chunk-AW2ZBBNX-zV216OhN.js → chunk-AW2ZBBNX-JRef_4rd.js} +1 -1
  49. package/dist/assets/{chunk-FVRAUYC3-BOtXkSrZ.js → chunk-FVRAUYC3-C__loQFR.js} +2 -2
  50. package/dist/assets/{chunk-HTAEGDNF-DiV1eG2w.js → chunk-HTAEGDNF-Bx74_xmN.js} +1 -1
  51. package/dist/assets/{chunk-JWPE2WC7-BZJyqcw-.js → chunk-JWPE2WC7-B20vr3uH.js} +1 -1
  52. package/dist/assets/{chunk-KQW6MTUR-DabkPo_d.js → chunk-KQW6MTUR-DfO9J2pt.js} +1 -1
  53. package/dist/assets/{chunk-XXDRQBXY-CcZGTVYy.js → chunk-XXDRQBXY-BAPGqnqj.js} +1 -1
  54. package/dist/assets/{classDiagram-v2-K4WV4PDN-DfOdVcjL.js → classDiagram-v2-K4WV4PDN-C3VR3yN8.js} +1 -1
  55. package/dist/assets/{coins-s4HyWHyQ.js → coins-CueFc0mY.js} +1 -1
  56. package/dist/assets/{core-D7M03RiE.js → core-Bw6kaGwe.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-CqrD5E-Q.js → cose-bilkent-JH36ORCC-19p3m04s.js} +1 -1
  58. package/dist/assets/{cynefin-EF2NZ3EQ-fcIOWtrM.js → cynefin-EF2NZ3EQ-Tv0tce7B.js} +1 -1
  59. package/dist/assets/{cynefinDiagram-3GCD6N5R-Cx2XYJ57.js → cynefinDiagram-3GCD6N5R-Iin0JwlW.js} +1 -1
  60. package/dist/assets/{dagre-W4DXFKR2-D6f_32GP.js → dagre-W4DXFKR2-00TRmSmB.js} +1 -1
  61. package/dist/assets/{diagram-2UJZ2QOL-IxXxA3zr.js → diagram-2UJZ2QOL-DslMBjKt.js} +1 -1
  62. package/dist/assets/{diagram-OVF4WLC6-DzRS9og-.js → diagram-OVF4WLC6-C3o3f1eL.js} +1 -1
  63. package/dist/assets/{diagram-PFPMY2P6-CA4cF9TP.js → diagram-PFPMY2P6-RRy9F9wH.js} +1 -1
  64. package/dist/assets/{diagram-UMYRVEAY-B5YdB3mN.js → diagram-UMYRVEAY-CWfRKtkp.js} +1 -1
  65. package/dist/assets/{diagram-YEKJPTXX-D8jMUZQ7.js → diagram-YEKJPTXX-SlJKvYay.js} +1 -1
  66. package/dist/assets/{diagram-ZIFT7M5P-D9FKtuee.js → diagram-ZIFT7M5P-DqzI8_eN.js} +1 -1
  67. package/dist/assets/{ebnfDiagram-VR2GEFS7-BipEfLA3.js → ebnfDiagram-VR2GEFS7-CDsrBNMu.js} +1 -1
  68. package/dist/assets/{elk-IJKZMXRS-ByA43ZYx.js → elk-IJKZMXRS-iikFqO-X.js} +1 -1
  69. package/dist/assets/{erDiagram-O2IAPWRE-hlDRgJN-.js → erDiagram-O2IAPWRE-DzFCc0Gp.js} +1 -1
  70. package/dist/assets/{file-up-DXcOpz8w.js → file-up-BzW-KJx-.js} +1 -1
  71. package/dist/assets/{flowDiagram-OXPTDLAJ-BuCuOW1m.js → flowDiagram-OXPTDLAJ-U5nLRus2.js} +1 -1
  72. package/dist/assets/{ganttDiagram-R7TSEDQI-Bk16Ilgi.js → ganttDiagram-R7TSEDQI-h7xqrZ23.js} +1 -1
  73. package/dist/assets/{gitGraphDiagram-XJZIOB7I-CN3BXWBF.js → gitGraphDiagram-XJZIOB7I-Bo9ZX2g5.js} +1 -1
  74. package/dist/assets/{import-name-wz0BNJ-d.js → import-name-BN_-MYip.js} +1 -1
  75. package/dist/assets/{index-BJo4IICg.js → index-BEjJ5c6H.js} +2 -2
  76. package/dist/assets/{index-BR_1_bou.js → index-DkDFNBNq.js} +1 -1
  77. package/dist/assets/{infoDiagram-5W2HQ5XZ-DSUAydoj.js → infoDiagram-5W2HQ5XZ-CAZmFoiz.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-K3B6WC7H-C1_ZiQE2.js → ishikawaDiagram-K3B6WC7H-T1M7DtvG.js} +1 -1
  79. package/dist/assets/{journeyDiagram-COXZFDF6-CAGOqBTO.js → journeyDiagram-COXZFDF6-DzO0Gzq9.js} +1 -1
  80. package/dist/assets/{kanban-definition-P3RFRI5V-Dj3_9GgV.js → kanban-definition-P3RFRI5V-C0fM1UHu.js} +1 -1
  81. package/dist/assets/{mcp-servers-qzidl6IU.js → mcp-servers-C7oRQpN5.js} +1 -1
  82. package/dist/assets/{mermaid.core-CcbkAG9m.js → mermaid.core-mKZTdBWz.js} +4 -4
  83. package/dist/assets/{mindmap-definition-LZFPQGKD-C_gFeuC9.js → mindmap-definition-LZFPQGKD-DEgoC28p.js} +1 -1
  84. package/dist/assets/{minimize-DixQTKye.js → minimize-D-CvaOV-.js} +1 -1
  85. package/dist/assets/{monitor-IwDx0ohO.js → monitor-DRcE92Rj.js} +1 -1
  86. package/dist/assets/{new-bot-COptZjiW.js → new-bot-COj1uEQa.js} +1 -1
  87. package/dist/assets/{org-library-97Cfj4XD.js → org-library-BIb28h24.js} +1 -1
  88. package/dist/assets/{package-format-MbGfBQ0g.js → package-format-hON5OXLZ.js} +1 -1
  89. package/dist/assets/{pegDiagram-BGZESJAR-CYa91QJf.js → pegDiagram-BGZESJAR-Vatn56u_.js} +1 -1
  90. package/dist/assets/{pieDiagram-CAPJLFHJ-H4g7rmhD.js → pieDiagram-CAPJLFHJ-DniOgFo3.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-GDMTTRAM-CkHV6SSw.js → quadrantDiagram-GDMTTRAM-BqhAQt5w.js} +1 -1
  92. package/dist/assets/{rail-SzaGJCSZ.js → rail-BWcrFQ2_.js} +1 -1
  93. package/dist/assets/{railroadDiagram-SM67HX2B-Kx3ccbrH.js → railroadDiagram-SM67HX2B-CxHs3JOs.js} +1 -1
  94. package/dist/assets/{remote-desktop-panel-CVuN83Ny.js → remote-desktop-panel-9XdtvJrd.js} +1 -1
  95. package/dist/assets/{requirementDiagram-X7JNWC4A-BcNRqLgf.js → requirementDiagram-X7JNWC4A-ChjhMFon.js} +1 -1
  96. package/dist/assets/{sankeyDiagram-UM26HJRW-CQP982o-.js → sankeyDiagram-UM26HJRW-B67bYF8E.js} +1 -1
  97. package/dist/assets/{sequenceDiagram-ZO4K6R2Y-BXkOAwlC.js → sequenceDiagram-ZO4K6R2Y-CTy7B-eu.js} +1 -1
  98. package/dist/assets/{sizeCapture-INFHLROL-BtuN-EAh.js → sizeCapture-INFHLROL-uy6qiY-2.js} +1 -1
  99. package/dist/assets/{stateDiagram-v2-TBUQTH76-CmlsJGhT.js → stateDiagram-v2-TBUQTH76-BvYYPNqL.js} +1 -1
  100. package/dist/assets/{styles-ElM5u1od.js → styles-DlOxUga6.js} +1 -1
  101. package/dist/assets/{swimlanes-N4OXWK64-GH4PBeGD.js → swimlanes-N4OXWK64-CMSAKu-2.js} +1 -1
  102. package/dist/assets/swimlanesDiagram-K3J5GTZL-BaaZdhur.js +8 -0
  103. package/dist/assets/{team-share-B2cRBtNk.js → team-share-BbQqJ5O6.js} +1 -1
  104. package/dist/assets/{timeline-definition-YOQAKGHF-j8Q7uetA.js → timeline-definition-YOQAKGHF-BOK1V5EC.js} +1 -1
  105. package/dist/assets/{usecaseDiagram-VIAY4XPW-Cz4ej3SQ.js → usecaseDiagram-VIAY4XPW-DEywfSfC.js} +1 -1
  106. package/dist/assets/{vennDiagram-BLWOH2XV-BZ3IAGpt.js → vennDiagram-BLWOH2XV-DN1iWw97.js} +1 -1
  107. package/dist/assets/{vi-CVDyZGky.js → vi-C-E3a4Tv.js} +2 -2
  108. package/dist/assets/{wardleyDiagram-YMQ3BMBF-Bc32JfGA.js → wardleyDiagram-YMQ3BMBF-BAIfK1We.js} +1 -1
  109. package/dist/assets/{xychartDiagram-TAQBALBS-s5XbB-l2.js → xychartDiagram-TAQBALBS-BLBD8FaQ.js} +1 -1
  110. package/dist/index.html +2 -2
  111. package/dist/rail.html +4 -4
  112. package/dist-server/index.js +1787 -1759
  113. package/dist-server/openmausbot.js +1 -1
  114. package/dist-server/package.json +1 -1
  115. package/dist-server/pair-cli.js +1 -1
  116. package/dist-server/server/imported-skills.js +7 -2
  117. package/dist-server/server/index.js +8 -6
  118. package/dist-server/server/initial-greeting.js +4 -0
  119. package/dist-server/server/limix-bot-library.js +184 -0
  120. package/dist-server/server/limix-skill-hub.js +2 -2
  121. package/dist-server/server/limix-skill-store.js +179 -0
  122. package/dist-server/server/routes/bot-skills.js +21 -4
  123. package/dist-server/server/routes/teams.js +1 -1
  124. package/package.json +1 -1
  125. package/skills/windows-computer-automation/SKILL.md +53 -0
  126. package/skills/windows-computer-automation/assets/HERIONIX-LEADER-PROMPT.md +25 -0
  127. package/skills/windows-computer-automation/assets/control-policy.example.json +25 -0
  128. package/skills/windows-computer-automation/assets/mcp-servers.example.json +19 -0
  129. package/skills/windows-computer-automation/manifest.json +27 -0
  130. package/skills/windows-computer-automation/references/herionix-integration.md +72 -0
  131. package/skills/windows-computer-automation/references/routing.md +26 -0
  132. package/skills/windows-computer-automation/references/scenarios.md +44 -0
  133. package/skills/windows-computer-automation/references/security.md +24 -0
  134. package/skills/windows-computer-automation/scripts/policy_engine.py +235 -0
  135. package/skills/windows-computer-automation/scripts/test_policy_engine.py +107 -0
  136. package/dist/assets/BotSettingsDialog-DBAjMChc.js +0 -16
  137. package/dist/assets/NewBotDialog-sfd-FzAz.js +0 -1
  138. package/dist/assets/TeamLibraryPanel-Djaf5q_z.js +0 -1
  139. package/dist/assets/channel-DXuqtdcQ.js +0 -1
  140. package/dist/assets/swimlanesDiagram-K3J5GTZL-VqQY3wNh.js +0 -8
@@ -4193,7 +4193,7 @@ string(),skillManifestEntrySchema),stagedSkillWriteSchema=external_exports.objec
4193
4193
  /^[a-f0-9]{64}$/).optional(),warnings:external_exports.array(external_exports.string()),skippedFiles:external_exports.array(external_exports.string()),createdAt:external_exports.string(),baseSha256:external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),baseAppliedStageId:external_exports.string().optional()}).superRefine((entry,ctx)=>{entry.action==="update"&&(!entry.baseSha256||!entry.baseAppliedStageId)&&ctx.addIssue({code:"custom",message:"updated skills require their reviewed base revi\
4194
4194
  sion"})}),stagedStoreSchema=external_exports.object({writes:external_exports.record(external_exports.string(),stagedSkillWriteSchema)})}});import{dirname as dirname29,isAbsolute as isAbsolute14,join as join64,resolve as resolve22}from"node:path";var IMPORTED_SKILLS_DIR,INDEX_PATH,sha256Hex,entrySchema,indexSchema,choicesSchema2,init_imported_skills=__esm({"server/imported-skills.ts"(){"use strict";init_zod();init_skill_fit();init_skill_md();init_atomic();init_config3();init_skill_storage();init_skills();IMPORTED_SKILLS_DIR=join64(DATA_DIR,"imported-skills"),INDEX_PATH=join64(DATA_DIR,"skill-state","imported-skills.json"),sha256Hex=
4195
4195
  external_exports.string().regex(/^[a-f0-9]{64}$/),entrySchema=external_exports.object({description:external_exports.string(),source:external_exports.enum(["claudeCode","codex"]),root:external_exports.string(),origin:external_exports.string(),label:external_exports.string(),sha256:sha256Hex,filesSha256:sha256Hex,importedAt:external_exports.string(),warnings:external_exports.array(external_exports.string()),license:external_exports.string().optional(),compatibility:external_exports.string().optional()}),
4196
- indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4196
+ indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional(),picked:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4197
4197
  `).length}: ${message5}`)},spaces=()=>{for(;text13[i2]===" "||text13[i2]===" ";)i2++},comment=()=>{if(text13[i2]==="#")for(;i2<text13.length&&text13[i2]!==`
4198
4198
  `;)i2++},newline=()=>{if(text13[i2]===`
4199
4199
  `)i2+=1;else if(text13[i2]==="\r"&&text13[i2+1]===`
@@ -1 +1 @@
1
- {"type":"module","version":"0.3.23"}
1
+ {"type":"module","version":"0.3.25"}
@@ -4192,7 +4192,7 @@ string(),skillManifestEntrySchema),stagedSkillWriteSchema=external_exports.objec
4192
4192
  /^[a-f0-9]{64}$/).optional(),warnings:external_exports.array(external_exports.string()),skippedFiles:external_exports.array(external_exports.string()),createdAt:external_exports.string(),baseSha256:external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),baseAppliedStageId:external_exports.string().optional()}).superRefine((entry,ctx)=>{entry.action==="update"&&(!entry.baseSha256||!entry.baseAppliedStageId)&&ctx.addIssue({code:"custom",message:"updated skills require their reviewed base revi\
4193
4193
  sion"})}),stagedStoreSchema=external_exports.object({writes:external_exports.record(external_exports.string(),stagedSkillWriteSchema)})}});import{dirname as dirname29,isAbsolute as isAbsolute14,join as join64,resolve as resolve22}from"node:path";var IMPORTED_SKILLS_DIR,INDEX_PATH,sha256Hex,entrySchema,indexSchema,choicesSchema2,init_imported_skills=__esm({"server/imported-skills.ts"(){"use strict";init_zod();init_skill_fit();init_skill_md();init_atomic();init_config3();init_skill_storage();init_skills();IMPORTED_SKILLS_DIR=join64(DATA_DIR,"imported-skills"),INDEX_PATH=join64(DATA_DIR,"skill-state","imported-skills.json"),sha256Hex=
4194
4194
  external_exports.string().regex(/^[a-f0-9]{64}$/),entrySchema=external_exports.object({description:external_exports.string(),source:external_exports.enum(["claudeCode","codex"]),root:external_exports.string(),origin:external_exports.string(),label:external_exports.string(),sha256:sha256Hex,filesSha256:sha256Hex,importedAt:external_exports.string(),warnings:external_exports.array(external_exports.string()),license:external_exports.string().optional(),compatibility:external_exports.string().optional()}),
4195
- indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4195
+ indexSchema=external_exports.record(external_exports.string(),entrySchema),choicesSchema2=external_exports.object({off:external_exports.array(external_exports.string()),on:external_exports.record(external_exports.string(),sha256Hex).default({}),listed:external_exports.array(external_exports.string()).optional(),picked:external_exports.array(external_exports.string()).optional()})}});function parseToml(source){let text13=source.charCodeAt(0)===65279?source.slice(1):source,i2=0,root=table(),current=root,fail3=message5=>{throw new TomlError(`line ${text13.slice(0,i2).split(`
4196
4196
  `).length}: ${message5}`)},spaces=()=>{for(;text13[i2]===" "||text13[i2]===" ";)i2++},comment=()=>{if(text13[i2]==="#")for(;i2<text13.length&&text13[i2]!==`
4197
4197
  `;)i2++},newline=()=>{if(text13[i2]===`
4198
4198
  `)i2+=1;else if(text13[i2]==="\r"&&text13[i2+1]===`
@@ -47,6 +47,8 @@ const choicesSchema = z.object({
47
47
  off: z.array(z.string()),
48
48
  on: z.record(z.string(), sha256Hex).default({}),
49
49
  listed: z.array(z.string()).optional(),
50
+ /** Limix Bot: skills from this computer are off for a bot until a person adds one from its store. */
51
+ picked: z.array(z.string()).optional(),
50
52
  });
51
53
  /** Every imported skill by name; empty when there are none or the index cannot be trusted. */
52
54
  export function readImportedSkills() {
@@ -279,6 +281,8 @@ export function storeImportedSkills(inputs) {
279
281
  function enabledFor(name, entry, choices) {
280
282
  if (choices.off.includes(name))
281
283
  return false;
284
+ if (!(choices.picked ?? []).includes(name))
285
+ return false;
282
286
  return entry.warnings.length === 0 || choices.on[name] === entry.filesSha256;
283
287
  }
284
288
  const comparable = (path) => {
@@ -361,9 +365,10 @@ export function setImportedSkillEnabled(botId, name, enabled) {
361
365
  const off = choices.off.filter((other) => other !== name);
362
366
  const { [name]: _read, ...on } = choices.on;
363
367
  const listed = choices.listed?.filter((other) => other !== name);
368
+ const picked = (choices.picked ?? []).filter((other) => other !== name);
364
369
  const next = enabled
365
- ? { off, on: entry.warnings.length ? { ...on, [name]: entry.filesSha256 } : on, ...(listed ? { listed: [...listed, name].sort() } : {}) }
366
- : { off: [...off, name].sort(), on, ...(listed ? { listed } : {}) };
370
+ ? { off, on: entry.warnings.length ? { ...on, [name]: entry.filesSha256 } : on, ...(listed ? { listed: [...listed, name].sort() } : {}), picked: [...picked, name].sort() }
371
+ : { off: [...off, name].sort(), on, ...(listed ? { listed } : {}), picked };
367
372
  mkdirSync(dirname(choicesPath(botId)), { recursive: true, mode: 0o700 });
368
373
  writeFileAtomic(choicesPath(botId), `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
369
374
  return importedSkillListings(botId, new Set()).find((skill) => skill.name === name);
@@ -442,7 +442,7 @@ import { AGENTS_NODE_FLAG, SPAWNED_PROXIES } from "./proxy-paths.js";
442
442
  import { agentsProxyLaunch, agentsToolBridgeEnv, applyAgentsBridge, bridgedToolTitle, turnEngine } from "./agents-bridge-switch.js";
443
443
  import { agentsServerEnv, botDrivenConversation, COMPUTER_OFF_REFUSAL, computerPickRefusal, directTurnKind, roomSkillsRefusal } from "./agents-turn.js";
444
444
  import { harnessMcpTokenEnv } from "./harness-mcp-token.js";
445
- import { loadBundledSkills, loadUserSkills, mergeSkills, renderSkillInstructions, selectBundledSkills, skillsLibraryEnabled, readSkillLibraryIndex, } from "./skill-library.js";
445
+ import { loadBundledSkills, loadUserSkills, mergeSkills, renderSkillInstructions, skillsLibraryEnabled, readSkillLibraryIndex, } from "./skill-library.js";
446
446
  import { runSkillsLibraryBootSweep } from "./skills-library-migration.js";
447
447
  import { installedPlaybookInstructions } from "./installed-playbooks.js";
448
448
  import { picture as sharedPicture } from "./package-export.js";
@@ -512,6 +512,7 @@ import { createBotOrderRoutes } from "./routes/bot-order.js";
512
512
  import { createBotLifecycleRoutes } from "./routes/bot-lifecycle.js";
513
513
  import { createBotSkillsRoutes } from "./routes/bot-skills.js";
514
514
  import { startHubAutoSync } from "./limix-skill-hub.js";
515
+ import { limixBundledFor, limixSelectBundled, limixSkillDefaultsMigration } from "./limix-skill-store.js";
515
516
  import { createSkillShareRoutes } from "./routes/skill-share.js";
516
517
  import { createTeamContextRoutes } from "./routes/team-context.js";
517
518
  import { createAppSuggestions } from "./host/app-suggestions.js";
@@ -661,7 +662,7 @@ const CLOUD_ENV = { ...process.env, ...CLOUD_SECRETS };
661
662
  const CLOUD_HOME = cloudHomeConfiguration(CLOUD_ENV);
662
663
  const CLOUD_TEAM = CLOUD_HOME?.team ?? null, CLOUD_PERSONAL = CLOUD_HOME !== null && CLOUD_TEAM === null, TEAM_LABEL = CLOUD_TEAM ? { label: CLOUD_TEAM.name } : {};
663
664
  /** 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;
665
+ const appLanguage = () => cfg.language ?? CLOUD_TEAM?.language ?? (process.env.OMB_UI_LANGUAGE?.trim() || undefined);
665
666
  const personLanguages = createPersonLanguages({ dataDir: DATA_DIR, configured: appLanguage, privateTo: (threadId) => teamHost.privateTo(threadId) });
666
667
  const threadLanguage = personLanguages.forThread;
667
668
  setConversationLanguage(threadLanguage);
@@ -1436,6 +1437,7 @@ function resyncOpenCodeProviderKeys() {
1436
1437
  const firstEngineRead = holdUntilFirstEngineRead();
1437
1438
  await registry.load(providerConfigs(), decorateHostedProvider);
1438
1439
  const bundledSkills = loadBundledSkills();
1440
+ limixSkillDefaultsMigration();
1439
1441
  const availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join(DATA_DIR, "skills")));
1440
1442
  /** Every turn the engine can mount it, whatever its words: they pick only the skill's instructions. A Cloud home or a hosted workspace has no USB port. */
1441
1443
  const usbPhoneOffered = (instance, skills) => instance.adapter.capabilities.phoneMcp === true &&
@@ -8084,8 +8086,8 @@ async function beginTurn(botId, text, opts) {
8084
8086
  handoffs.begin(threadId, dispatchClaimId, dispatchContext.handoff);
8085
8087
  const integrations = {};
8086
8088
  let browser = null;
8087
- const skills = availableSkills(), phoneOffered = usbPhoneOffered(instance, skills) && !proactive.fence.confines(threadId) && !botApiThread(threadId);
8088
- const selectedSkills = selectBundledSkills(providerText, [
8089
+ const skills = limixBundledFor(bot.id, availableSkills()), phoneOffered = usbPhoneOffered(instance, skills) && !proactive.fence.confines(threadId) && !botApiThread(threadId);
8090
+ const selectedSkills = limixSelectBundled(providerText, [
8089
8091
  ...(phoneOffered ? ["phoneMcp"] : []),
8090
8092
  ...(skillAuthoring ? ["skillAuthoring"] : []),
8091
8093
  ], skills);
@@ -10048,8 +10050,8 @@ setupRetry = 0) {
10048
10050
  : undefined, bot.id);
10049
10051
  const roomContext = roomWindow.text;
10050
10052
  const turnImages = usesNativeImageInput ? [...burstImages, ...resolvedLatestImages.images, ...await videoFramesWithin(burstImages.length + resolvedLatestImages.images.length, resolvedLatestImages.videos)] : [];
10051
- const skills = availableSkills(), phoneOffered = usbPhoneOffered(instance, skills);
10052
- const selectedSkills = mergeSkills(selectBundledSkills(roomContext, phoneOffered ? ["phoneMcp"] : [], skills), selectBundledSkills(latestUser?.text ?? "", skillAuthoring ? ["skillAuthoring"] : [], skills));
10053
+ const skills = limixBundledFor(bot.id, availableSkills()), phoneOffered = usbPhoneOffered(instance, skills);
10054
+ const selectedSkills = mergeSkills(limixSelectBundled(roomContext, phoneOffered ? ["phoneMcp"] : [], skills), limixSelectBundled(latestUser?.text ?? "", skillAuthoring ? ["skillAuthoring"] : [], skills));
10053
10055
  if (phoneOffered)
10054
10056
  integrations.phone = phoneIntegration(bot.id, threadId, internalGeneration);
10055
10057
  try {
@@ -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,184 @@
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
+ if (value.skills !== undefined) {
127
+ if (!Array.isArray(value.skills) || value.skills.length > 20 || value.skills.some((name) => typeof name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)) {
128
+ throw new Error("skills is invalid");
129
+ }
130
+ bot.skills = [...new Set(value.skills)];
131
+ }
132
+ return bot;
133
+ }
134
+ async function fetchJson(url, maxBytes) {
135
+ const response = await fetch(url, { headers: { accept: "application/json, text/plain;q=0.9" }, redirect: "error", signal: AbortSignal.timeout(10_000) });
136
+ if (!response.ok)
137
+ throw new Error(`GitHub trả lỗi HTTP ${response.status}`);
138
+ const raw = await response.text();
139
+ if (Buffer.byteLength(raw) > maxBytes)
140
+ throw new Error("Tệp mẫu quá lớn");
141
+ try {
142
+ return JSON.parse(raw);
143
+ }
144
+ catch {
145
+ throw new Error("GitHub không trả về JSON hợp lệ");
146
+ }
147
+ }
148
+ let cached = null;
149
+ /** `fresh`: the "Cập nhật" button. Skips this cache and GitHub's (raw files are cached there for about five minutes). */
150
+ async function listing(fresh = false) {
151
+ if (!fresh && cached && Date.now() - cached.at < LIST_TTL_MS)
152
+ return cached.bots;
153
+ try {
154
+ const bots = parseBotListing(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/catalog.json${fresh ? `?t=${Date.now()}` : ""}`, MAX_CATALOG_BYTES));
155
+ cached = { at: Date.now(), bots };
156
+ return bots;
157
+ }
158
+ catch (error) {
159
+ if (cached)
160
+ return cached.bots; // an older list beats none while GitHub is unreachable
161
+ throw error;
162
+ }
163
+ }
164
+ /** GET /api/limix/bots, POST /api/limix/bots/refresh and GET /api/limix/bots/<id>. Null for any other path. */
165
+ export async function handleLimixBotLibrary(method, path) {
166
+ try {
167
+ if (method === "POST" && path === "/api/limix/bots/refresh")
168
+ return { status: 200, body: { bots: await listing(true) } };
169
+ if (method !== "GET")
170
+ return null;
171
+ if (path === "/api/limix/bots")
172
+ return { status: 200, body: { bots: await listing() } };
173
+ const match = path.match(/^\/api\/limix\/bots\/([a-z0-9][a-z0-9-]{0,63})$/);
174
+ if (!match)
175
+ return null;
176
+ const id = match[1];
177
+ if (!(await listing()).some((bot) => bot.id === id))
178
+ return { status: 404, body: { error: "Không có bot mẫu này" } };
179
+ return { status: 200, body: { bot: parseLibraryBot(await fetchJson(`${TEAM_LIBRARY_RAW_ROOT}/bots/${id}.json?t=${Date.now()}`, MAX_BOT_BYTES), id) } };
180
+ }
181
+ catch (error) {
182
+ return { status: 502, body: { error: error instanceof Error ? error.message : String(error) } };
183
+ }
184
+ }
@@ -1,6 +1,6 @@
1
1
  // Limix Skill Hub client: keeps a cached copy of the hub catalog, installs a skill only after every file matches the
2
2
  // SHA-256 the catalog pins, and never installs scripts, binaries or MCP files unless the owner allows tools for that
3
- // one skill. Installed skills land DISABLED; the owner turns them on. Auto Sync (catalog refresh) is on by default,
3
+ // one skill. Installed skills wait in the store ("Kho Kĩ Năng (Skills)"), switched on but on no bot until one is added to it. Auto Sync (catalog refresh) is on by default,
4
4
  // Auto Install is off by default.
5
5
  import { createHash } from "node:crypto";
6
6
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
@@ -171,7 +171,7 @@ export async function installHubSkill(id, options = {}) {
171
171
  ...(entry.tags.length ? { tags: entry.tags } : {}),
172
172
  warnings: entry.notes,
173
173
  skippedFiles: skipped,
174
- reviewState: "disabled",
174
+ reviewState: "approved",
175
175
  ...(files.length ? { files } : {}),
176
176
  });
177
177
  if ("error" in result)
@@ -0,0 +1,179 @@
1
+ // Limix Bot: skills are chosen per bot. A new bot starts with only Limix's core skills (learning new skills, reading
2
+ // links with Agent Reach, operating Windows, the phone helpers) plus the skills it learns itself; everything else waits
3
+ // in the store ("Kho Kĩ Năng (Skills)") until a person adds it to one bot. Settings → Kĩ Năng (Skills) only downloads and
4
+ // installs skills into the store; it never turns a skill on for every bot.
5
+ //
6
+ // Three kinds of skill can be added to a bot from the store:
7
+ // bundled — shipped in skills/<id> (ui-ux-pro-max, data-dashboards…): the bot's picks live in
8
+ // <data>/skill-state/<bot>/limix-bundled.json; core ones are always on and cannot be removed.
9
+ // library — installed from the Limix Skill Hub or pasted in Settings: the bot's assignedSkills.
10
+ // imported — brought in from this computer's Claude Code or Codex: the bot's own switch (opt-in in Limix).
11
+ // `/name` calls any skill the bot has, bundled ones included (limixSelectBundled).
12
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { isSkillName } from "../shared/skill-md.js";
15
+ import { writeFileAtomic } from "./atomic.js";
16
+ import { DATA_DIR } from "./config.js";
17
+ import { importedSkillListings, setImportedSkillEnabled } from "./imported-skills.js";
18
+ import { listLibrarySkills, loadBundledSkills, mergeSkills, readSkillLibraryIndex, selectBundledSkills, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, unassignLibrarySkill, } from "./skill-library.js";
19
+ /** Always on for every bot. "Learning new skills" itself is the skill-authoring feature, on for every bot already. */
20
+ export const LIMIX_CORE_SKILLS = new Set([
21
+ "create-verification-skill", "agent-reach", "windows-computer-automation", "phone-connection", "phone-harness",
22
+ ]);
23
+ const BOT_ID = /^[\w-]+$/;
24
+ const picksPath = (botId) => join(DATA_DIR, "skill-state", botId, "limix-bundled.json");
25
+ export function bundledPicks(botId) {
26
+ if (!BOT_ID.test(botId))
27
+ return new Set();
28
+ try {
29
+ const parsed = JSON.parse(readFileSync(picksPath(botId), "utf8"));
30
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string" && isSkillName(name)) : []);
31
+ }
32
+ catch {
33
+ return new Set();
34
+ }
35
+ }
36
+ function setBundledPick(botId, id, on) {
37
+ if (!BOT_ID.test(botId) || !isSkillName(id))
38
+ return;
39
+ const picks = bundledPicks(botId);
40
+ if (picks.has(id) === on)
41
+ return;
42
+ if (on)
43
+ picks.add(id);
44
+ else
45
+ picks.delete(id);
46
+ mkdirSync(join(DATA_DIR, "skill-state", botId), { recursive: true, mode: 0o700 });
47
+ writeFileAtomic(picksPath(botId), `${JSON.stringify([...picks].sort(), null, 2)}\n`, { mode: 0o600 });
48
+ }
49
+ /** The shipped skills one bot has: the core ones and the ones a person added to it. */
50
+ export function limixBundledFor(botId, skills) {
51
+ const picks = bundledPicks(botId);
52
+ return skills.filter(({ manifest }) => LIMIX_CORE_SKILLS.has(manifest.id) || picks.has(manifest.id));
53
+ }
54
+ const slashCalled = (text, id) => new RegExp(`(^|\\s)/${id.replace(/[-]/g, "\\-")}(?=$|[\\s.,:;!?])`, "m").test(text);
55
+ /** selectBundledSkills, plus every skill of `skills` the text calls by `/id`. */
56
+ export function limixSelectBundled(text, capabilities, skills) {
57
+ const available = new Set(capabilities);
58
+ const called = skills.filter(({ manifest }) => manifest.requiredCapabilities.every((capability) => available.has(capability)) && slashCalled(text, manifest.id));
59
+ return mergeSkills(selectBundledSkills(text, available, skills), called);
60
+ }
61
+ /** The bot's shipped skills as rows of its Skills page. */
62
+ export function limixBundledListings(botId) {
63
+ return limixBundledFor(botId, loadBundledSkills()).map(({ manifest }) => ({
64
+ name: manifest.id, description: manifest.description, enabled: true, editable: false,
65
+ source: LIMIX_CORE_SKILLS.has(manifest.id) ? "Kĩ năng gốc của Limix Bot" : "Kho Kĩ Năng (Skills) · có sẵn",
66
+ sha256: "", importedAt: "", warnings: [], skippedFiles: [], origin: "bundled", core: LIMIX_CORE_SKILLS.has(manifest.id),
67
+ }));
68
+ }
69
+ /** A bot's switch for one of its shipped skills: off takes an added one away; a core one stays. Null: not shipped. */
70
+ export function limixBundledSwitch(botId, name, enabled) {
71
+ if (!loadBundledSkills().some(({ manifest }) => manifest.id === name))
72
+ return null;
73
+ if (LIMIX_CORE_SKILLS.has(name)) {
74
+ return enabled ? { status: 200, body: { skill: limixBundledListings(botId).find((skill) => skill.name === name) } }
75
+ : { status: 409, body: { error: "Kĩ năng gốc của Limix Bot luôn bật cho mọi bot." } };
76
+ }
77
+ setBundledPick(botId, name, enabled);
78
+ return { status: 200, body: { skill: { ...limixBundledListings(botId).find((skill) => skill.name === name), name, enabled } } };
79
+ }
80
+ function storeItems(bots, botId) {
81
+ const bot = bots.bot(botId);
82
+ const assigned = new Set(bot.assignedSkills ?? []);
83
+ const picks = bundledPicks(botId);
84
+ const items = new Map();
85
+ for (const { manifest } of loadBundledSkills()) {
86
+ const core = LIMIX_CORE_SKILLS.has(manifest.id);
87
+ items.set(manifest.id, { name: manifest.id, description: manifest.description, kind: "bundled", added: core || picks.has(manifest.id), core, ready: true, warnings: 0, source: "Có sẵn trong Limix Bot" });
88
+ }
89
+ for (const skill of listLibrarySkills()) {
90
+ if (items.has(skill.name))
91
+ continue;
92
+ const intact = !skill.warnings.some((warning) => warning.includes("enablement is blocked"));
93
+ items.set(skill.name, {
94
+ name: skill.name, description: skill.description, kind: "library", added: assigned.has(skill.name) && skill.enabled, core: false,
95
+ ready: intact, warnings: skill.warnings.length, source: skill.source.startsWith("limix-hub:") ? "Thư viện kĩ năng Limix" : "Đã cài trong Cài đặt",
96
+ });
97
+ }
98
+ for (const skill of importedSkillListings(botId, new Set())) {
99
+ if (items.has(skill.name))
100
+ continue;
101
+ items.set(skill.name, { name: skill.name, description: skill.description, kind: "imported", added: skill.enabled, core: false, ready: true, warnings: skill.warnings.length, source: skill.source || "Từ máy tính này" });
102
+ }
103
+ return [...items.values()].sort((a, b) => Number(b.core) - Number(a.core) || a.name.localeCompare(b.name));
104
+ }
105
+ /** Add one skill to one bot, or take it away. `kind` may be left out: the store finds the name. */
106
+ export function limixStoreSet(bots, botId, name, add, kind) {
107
+ const bot = bots.bot(botId);
108
+ if (!bot)
109
+ return { error: "Không tìm thấy bot." };
110
+ if (!isSkillName(name))
111
+ return { error: "Tên kĩ năng không hợp lệ." };
112
+ const item = storeItems(bots, botId).find((entry) => entry.name === name && (!kind || entry.kind === kind));
113
+ if (!item)
114
+ return { error: `Kho chưa có kĩ năng "${name}".` };
115
+ if (item.kind === "bundled") {
116
+ if (item.core)
117
+ return add ? { ok: true } : { error: "Kĩ năng gốc của Limix Bot luôn bật cho mọi bot." };
118
+ setBundledPick(botId, name, add);
119
+ return { ok: true };
120
+ }
121
+ if (item.kind === "imported") {
122
+ const result = setImportedSkillEnabled(botId, name, add);
123
+ return "error" in result ? { error: result.error } : { ok: true };
124
+ }
125
+ const assigned = bot.assignedSkills ?? [];
126
+ if (add) {
127
+ // Adding from the store is the person's choice for this bot, so a skill installed but not yet switched on is switched on.
128
+ const listing = listLibrarySkills().find((skill) => skill.name === name);
129
+ if (listing && !listing.enabled) {
130
+ const approved = setLibrarySkillReviewState(name, "approved");
131
+ if ("error" in approved)
132
+ return { error: approved.error };
133
+ }
134
+ setLibrarySkillOptOut(botId, name, false);
135
+ if (!assigned.includes(name))
136
+ bots.patchBot(botId, { assignedSkills: [...assigned, name] });
137
+ return { ok: true };
138
+ }
139
+ const result = unassignLibrarySkill(botId, name, assigned);
140
+ if ("unassigned" in result && result.assignedSkills)
141
+ bots.patchBot(botId, { assignedSkills: result.assignedSkills });
142
+ return { ok: true };
143
+ }
144
+ /** GET /api/limix/skill-store/<bot> lists the store for that bot; POST {name, add, kind?} adds or takes away one skill. */
145
+ export function handleLimixSkillStore(bots, method, path, body) {
146
+ const m = path.match(/^\/api\/limix\/skill-store\/([\w-]+)$/);
147
+ if (!m)
148
+ return null;
149
+ const botId = m[1];
150
+ if (!bots.bot(botId))
151
+ return { status: 404, body: { error: "Không tìm thấy bot." } };
152
+ if (method === "GET")
153
+ return { status: 200, body: { skills: storeItems(bots, botId) } };
154
+ if (method === "POST") {
155
+ const raw = (body && typeof body === "object" ? body : {});
156
+ const kind = raw.kind === "bundled" || raw.kind === "library" || raw.kind === "imported" ? raw.kind : undefined;
157
+ if (typeof raw.name !== "string" || typeof raw.add !== "boolean")
158
+ return { status: 400, body: { error: "Cần tên kĩ năng và add." } };
159
+ const result = limixStoreSet(bots, botId, raw.name, raw.add, kind);
160
+ return "error" in result ? { status: 422, body: result } : { status: 200, body: { skills: storeItems(bots, botId) } };
161
+ }
162
+ return { status: 405, body: { error: "method not allowed" } };
163
+ }
164
+ /** Once: skills that earlier versions turned on for every bot go back to the store. */
165
+ export function limixSkillDefaultsMigration() {
166
+ const marker = join(DATA_DIR, "skill-state", "limix-skill-defaults-v1.json");
167
+ if (existsSync(marker))
168
+ return;
169
+ try {
170
+ for (const [name, entry] of Object.entries(readSkillLibraryIndex()))
171
+ if (entry.everyBot)
172
+ setLibrarySkillEveryBot(name, false);
173
+ mkdirSync(join(DATA_DIR, "skill-state"), { recursive: true, mode: 0o700 });
174
+ writeFileAtomic(marker, `${JSON.stringify({ at: new Date().toISOString() })}\n`, { mode: 0o600 });
175
+ }
176
+ catch {
177
+ // Retried at the next start.
178
+ }
179
+ }
@@ -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";
@@ -6,6 +7,7 @@ import { fetchSkillFromSource } from "../skill-fetch.js";
6
7
  import { readLocalSkills } from "../limix-skill-folder.js";
7
8
  import { loadBundledSkills } from "../skill-library.js";
8
9
  import { handleLimixHub } from "../limix-skill-hub.js";
10
+ import { handleLimixSkillStore, LIMIX_CORE_SKILLS, limixBundledListings, limixBundledSwitch } from "../limix-skill-store.js";
9
11
  import { skillImportSummary } from "../skill-folder.js";
10
12
  import { installLibrarySkill, librarySkillOptOuts, listLibrarySkills, readLibrarySkillFile, readSkillLibraryIndex, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, skillsLibraryEnabled, unassignLibrarySkill } from "../skill-library.js";
11
13
  import { installSkill, listBotSkillsWithOrigin, listStagedSkillWrites, parseSkillMd, removeSkill, scanSkillText, setSkillEnabled, } from "../skills.js";
@@ -14,13 +16,23 @@ export function createBotSkillsRoutes(deps) {
14
16
  const { store, cfg, listBotSkills, readBotSkillFile, stagedSkillListing } = deps;
15
17
  return async ({ req, res, url, path, method, json, readBody }) => {
16
18
  let m = null;
19
+ if (path.startsWith("/api/limix/bots")) {
20
+ const library = await handleLimixBotLibrary(method, path);
21
+ if (library)
22
+ return json(res, library.status, library.body);
23
+ }
17
24
  if (path.startsWith("/api/limix/hub")) {
18
25
  const hub = await handleLimixHub(method, path, method === "POST" ? await readBody(req) : undefined);
19
26
  if (hub)
20
27
  return json(res, hub.status, hub.body);
21
28
  }
29
+ if (path.startsWith("/api/limix/skill-store/")) {
30
+ const skillStore = handleLimixSkillStore(store, method, path, method === "POST" ? await readBody(req) : undefined);
31
+ if (skillStore)
32
+ return json(res, skillStore.status, skillStore.body);
33
+ }
22
34
  if (method === "GET" && path === "/api/limix/bundled-skills") {
23
- const skills = loadBundledSkills().map(({ manifest }) => ({ id: manifest.id, name: manifest.name, description: manifest.description, defaultEnabled: manifest.defaultEnabled, triggerTerms: manifest.triggerTerms }));
35
+ const skills = loadBundledSkills().map(({ manifest }) => ({ id: manifest.id, name: manifest.name, description: manifest.description, defaultEnabled: manifest.defaultEnabled, triggerTerms: manifest.triggerTerms, core: LIMIX_CORE_SKILLS.has(manifest.id) }));
24
36
  return json(res, 200, { skills });
25
37
  }
26
38
  // Imports land DISABLED: a person enables one after reading SKILL.md and its scan warnings.
@@ -57,14 +69,15 @@ export function createBotSkillsRoutes(deps) {
57
69
  const { skills, library } = listBotSkillsWithOrigin(m[1], store.bot(m[1]).assignedSkills);
58
70
  const names = new Set(skills.map((skill) => skill.name));
59
71
  return json(res, 200, {
60
- skills: [...skills, ...listBotSkills(bot, threadId).filter((skill) => !names.has(skill.name))],
72
+ skills: [...skills, ...listBotSkills(bot, threadId).filter((skill) => !names.has(skill.name)),
73
+ ...limixBundledListings(bot.id).filter((skill) => !names.has(skill.name) && !listBotSkills(bot, threadId).some((other) => other.name === skill.name))],
61
74
  library,
62
75
  assignedSkills: store.bot(m[1]).assignedSkills,
63
76
  staged: listStagedSkillWrites(m[1]).map(stagedSkillListing),
64
77
  });
65
78
  }
66
79
  return json(res, 200, {
67
- skills: listBotSkills(bot, threadId),
80
+ skills: [...listBotSkills(bot, threadId), ...limixBundledListings(bot.id)],
68
81
  staged: listStagedSkillWrites(m[1]).map(stagedSkillListing),
69
82
  });
70
83
  }
@@ -115,8 +128,12 @@ export function createBotSkillsRoutes(deps) {
115
128
  return json(res, 200, { skill });
116
129
  }
117
130
  const result = setSkillEnabled(m[1], m[2], parsed.data.enabled);
118
- if ("error" in result)
131
+ if ("error" in result) {
132
+ const shipped = limixBundledSwitch(m[1], m[2], parsed.data.enabled);
133
+ if (shipped)
134
+ return json(res, shipped.status, shipped.body);
119
135
  return json(res, 404, { error: result.error });
136
+ }
120
137
  return json(res, 200, { skill: result });
121
138
  }
122
139
  if (m && method === "DELETE") {
@@ -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.23",
3
+ "version": "0.3.25",
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",