shadow-claw 1.27.0 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/.well-known/ai-catalog.json +11 -0
  2. package/.well-known/mcp/index.html +19 -0
  3. package/.well-known/mcp/server-card.json +52 -0
  4. package/.well-known/mcp/servers.json +15 -0
  5. package/.well-known/mcp.json +52 -0
  6. package/.well-known/oauth-protected-resource +10 -0
  7. package/.well-known/oauth-protected-resource.json +10 -0
  8. package/bin/assert-version-bump.mjs +61 -3
  9. package/bin/assert-version-bump.test.mjs +77 -1
  10. package/bin/build/build.mjs +116 -49
  11. package/bin/build/build.test.mjs +103 -0
  12. package/bin/cli.mjs +299 -237
  13. package/bin/cli.test.mjs +27 -0
  14. package/bin/commands/mcp.mjs +132 -2
  15. package/bin/commands/mcp.test.mjs +166 -0
  16. package/bin/commands/skills-index.mjs +139 -20
  17. package/bin/commands/skills-index.test.mjs +82 -0
  18. package/bin/utils/control-client.mjs +17 -0
  19. package/bin/utils/control-client.test.mjs +46 -0
  20. package/bin/version.mjs +208 -0
  21. package/bin/version.test.mjs +138 -0
  22. package/dist/electron/main.cjs +195 -185
  23. package/dist/lib/components/index.js +21 -21
  24. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  25. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  26. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  27. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  28. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  29. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  30. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  31. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  32. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  33. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  34. package/dist/lib/components/shadow-claw/shadow-claw.d.ts +1 -0
  35. package/dist/lib/components/shadow-claw/utils/setupAppLifecycle.d.ts +23 -0
  36. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  37. package/dist/lib/components/shadow-claw-channels.js +1 -1
  38. package/dist/lib/components/shadow-claw-chat.js +1 -1
  39. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  40. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  41. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  42. package/dist/lib/components/shadow-claw-files.js +1 -1
  43. package/dist/lib/components/shadow-claw-git.js +1 -1
  44. package/dist/lib/components/shadow-claw-llm.js +1 -1
  45. package/dist/lib/components/shadow-claw-networking.js +1 -1
  46. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  47. package/dist/lib/components/shadow-claw-pages.js +1 -1
  48. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  49. package/dist/lib/components/shadow-claw-settings.js +1 -1
  50. package/dist/lib/components/shadow-claw-storage.js +1 -1
  51. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  52. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  53. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  54. package/dist/lib/components/shadow-claw-tools.js +1 -1
  55. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  56. package/dist/lib/components/shadow-claw.js +1 -1
  57. package/dist/lib/{configurePeerJs-oODzltaC.js → configurePeerJs-D8Ph0-A9.js} +2 -2
  58. package/dist/lib/{configurePeerJs-oODzltaC.js.map → configurePeerJs-D8Ph0-A9.js.map} +1 -1
  59. package/dist/lib/core/control-plane-client.d.ts +2 -0
  60. package/dist/lib/core/orchestrator/orchestrator.d.ts +10 -0
  61. package/dist/lib/core/orchestrator/utils/operations/channel.d.ts +2 -2
  62. package/dist/lib/core/utils/initControlPlane.d.ts +6 -0
  63. package/dist/lib/index.js +21 -21
  64. package/dist/lib/{initControlPlane-C2zURVIS.js → initControlPlane-BIrWmpzc.js} +76 -9
  65. package/dist/lib/initControlPlane-BIrWmpzc.js.map +1 -0
  66. package/dist/lib/{markdown-C6EHl9zs.js → markdown-DEUoM1c6.js} +2 -2
  67. package/dist/lib/{markdown-C6EHl9zs.js.map → markdown-DEUoM1c6.js.map} +1 -1
  68. package/dist/lib/{orchestrator-KRjx0eba.js → orchestrator-BtVj9bBB.js} +11 -10
  69. package/dist/lib/{orchestrator-KRjx0eba.js.map → orchestrator-BtVj9bBB.js.map} +1 -1
  70. package/dist/lib/{orchestrator-CACLa_Ro.js → orchestrator-CFCW-l1O.js} +52 -4
  71. package/dist/lib/orchestrator-CFCW-l1O.js.map +1 -0
  72. package/dist/lib/{peerjs-DFWOBcXf.js → peerjs-BoBkx9DJ.js} +23 -3
  73. package/dist/lib/peerjs-BoBkx9DJ.js.map +1 -0
  74. package/dist/lib/{push-client-_tiWQsCT.js → push-client-D6hbzDNV.js} +10 -4
  75. package/dist/lib/push-client-D6hbzDNV.js.map +1 -0
  76. package/dist/lib/{shadow-claw-6UHdiy4a.js → shadow-claw-Cuf2O5bZ.js} +71 -4
  77. package/dist/lib/shadow-claw-Cuf2O5bZ.js.map +1 -0
  78. package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js → shadow-claw-channel-config-BDPKt6Bd.js} +3 -3
  79. package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js.map → shadow-claw-channel-config-BDPKt6Bd.js.map} +1 -1
  80. package/dist/lib/{shadow-claw-channels-1N6Jl-By.js → shadow-claw-channels-BsP5JABM.js} +2 -2
  81. package/dist/lib/{shadow-claw-channels-1N6Jl-By.js.map → shadow-claw-channels-BsP5JABM.js.map} +1 -1
  82. package/dist/lib/{shadow-claw-chat-BMoqQJRK.js → shadow-claw-chat-Cam2lX4k.js} +4 -4
  83. package/dist/lib/{shadow-claw-chat-BMoqQJRK.js.map → shadow-claw-chat-Cam2lX4k.js.map} +1 -1
  84. package/dist/lib/{shadow-claw-control-plane-UG0o7pCz.js → shadow-claw-control-plane-DB2qcCFs.js} +20 -10
  85. package/dist/lib/shadow-claw-control-plane-DB2qcCFs.js.map +1 -0
  86. package/dist/lib/{shadow-claw-conversations-BwXep3iu.js → shadow-claw-conversations-C9Vf4XGx.js} +2 -2
  87. package/dist/lib/{shadow-claw-conversations-BwXep3iu.js.map → shadow-claw-conversations-C9Vf4XGx.js.map} +1 -1
  88. package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js → shadow-claw-file-viewer-B4HYiI0v.js} +3 -3
  89. package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js.map → shadow-claw-file-viewer-B4HYiI0v.js.map} +1 -1
  90. package/dist/lib/{shadow-claw-files-DIREEnT2.js → shadow-claw-files-DVROUESO.js} +2 -2
  91. package/dist/lib/{shadow-claw-files-DIREEnT2.js.map → shadow-claw-files-DVROUESO.js.map} +1 -1
  92. package/dist/lib/{shadow-claw-git-Bg1SWgsF.js → shadow-claw-git-BqrO7cHJ.js} +3 -3
  93. package/dist/lib/{shadow-claw-git-Bg1SWgsF.js.map → shadow-claw-git-BqrO7cHJ.js.map} +1 -1
  94. package/dist/lib/{shadow-claw-llm-CazBrVpM.js → shadow-claw-llm-BexUQdYo.js} +2 -2
  95. package/dist/lib/{shadow-claw-llm-CazBrVpM.js.map → shadow-claw-llm-BexUQdYo.js.map} +1 -1
  96. package/dist/lib/{shadow-claw-networking-Df85tCbO.js → shadow-claw-networking-CfXRGpUD.js} +2 -2
  97. package/dist/lib/{shadow-claw-networking-Df85tCbO.js.map → shadow-claw-networking-CfXRGpUD.js.map} +1 -1
  98. package/dist/lib/{shadow-claw-notifications-v62FryTc.js → shadow-claw-notifications-C9KPMgx_.js} +2 -2
  99. package/dist/lib/{shadow-claw-notifications-v62FryTc.js.map → shadow-claw-notifications-C9KPMgx_.js.map} +1 -1
  100. package/dist/lib/{shadow-claw-pages-BgPyIEZO.js → shadow-claw-pages-CGzGXmrI.js} +3 -3
  101. package/dist/lib/{shadow-claw-pages-BgPyIEZO.js.map → shadow-claw-pages-CGzGXmrI.js.map} +1 -1
  102. package/dist/lib/{shadow-claw-peerjs-xkBXILed.js → shadow-claw-peerjs-B0ZB7yhB.js} +3 -3
  103. package/dist/lib/{shadow-claw-peerjs-xkBXILed.js.map → shadow-claw-peerjs-B0ZB7yhB.js.map} +1 -1
  104. package/dist/lib/{shadow-claw-settings-CN89xRVU.js → shadow-claw-settings-DBfMZdDS.js} +2 -2
  105. package/dist/lib/{shadow-claw-settings-CN89xRVU.js.map → shadow-claw-settings-DBfMZdDS.js.map} +1 -1
  106. package/dist/lib/{shadow-claw-storage-VnhcObCe.js → shadow-claw-storage-CM-w6mgG.js} +2 -2
  107. package/dist/lib/{shadow-claw-storage-VnhcObCe.js.map → shadow-claw-storage-CM-w6mgG.js.map} +1 -1
  108. package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js → shadow-claw-task-server-DV4b1g2-.js} +4 -2
  109. package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js.map → shadow-claw-task-server-DV4b1g2-.js.map} +1 -1
  110. package/dist/lib/{shadow-claw-tasks-i_evIufy.js → shadow-claw-tasks-K-foOHSE.js} +3 -3
  111. package/dist/lib/{shadow-claw-tasks-i_evIufy.js.map → shadow-claw-tasks-K-foOHSE.js.map} +1 -1
  112. package/dist/lib/{shadow-claw-terminal-CwScROlu.js → shadow-claw-terminal-CpAknKyA.js} +2 -2
  113. package/dist/lib/{shadow-claw-terminal-CwScROlu.js.map → shadow-claw-terminal-CpAknKyA.js.map} +1 -1
  114. package/dist/lib/{shadow-claw-tools-DD3fyXP4.js → shadow-claw-tools-D98XZ0Ep.js} +3 -3
  115. package/dist/lib/{shadow-claw-tools-DD3fyXP4.js.map → shadow-claw-tools-D98XZ0Ep.js.map} +1 -1
  116. package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js → shadow-claw-webvm-DLOb8dMI.js} +2 -2
  117. package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js.map → shadow-claw-webvm-DLOb8dMI.js.map} +1 -1
  118. package/dist/lib/subsystems/channels/imessage.d.ts +1 -0
  119. package/dist/lib/subsystems/channels/peerjs.d.ts +1 -0
  120. package/dist/lib/subsystems/channels/telegram.d.ts +1 -0
  121. package/dist/lib/subsystems/channels/types.d.ts +2 -0
  122. package/dist/lib/subsystems/notifications/push-client.d.ts +1 -1
  123. package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js → syncWebMcpRegistration-NspKefOI.js} +2 -2
  124. package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js.map → syncWebMcpRegistration-NspKefOI.js.map} +1 -1
  125. package/dist/public/.well-known/ai-catalog.json +11 -0
  126. package/dist/public/.well-known/mcp/index.html +19 -0
  127. package/dist/public/.well-known/mcp/server-card.json +52 -0
  128. package/dist/public/.well-known/mcp/servers.json +15 -0
  129. package/dist/public/.well-known/mcp.json +52 -0
  130. package/dist/public/.well-known/oauth-protected-resource +10 -0
  131. package/dist/public/.well-known/oauth-protected-resource.json +10 -0
  132. package/dist/public/404.html +28 -1
  133. package/dist/public/AGENTS.md +1 -1
  134. package/dist/public/agent.worker.js +1 -1
  135. package/dist/public/{configurePeerJs-BqgeBjP7.js → configurePeerJs-BhBokQlC.js} +1 -1
  136. package/dist/public/docs/example/article/index.html +3 -3
  137. package/dist/public/docs/publishing/index.html +3 -3
  138. package/dist/public/docs/skill-creator/index.html +3 -3
  139. package/dist/public/docs/subsystems/cli.md +11 -8
  140. package/dist/public/docs/subsystems/mcp-server.md +1 -0
  141. package/dist/public/docs/subsystems/notifications.md +10 -0
  142. package/dist/public/{e2e-bridge-Ceqr5kGc.js → e2e-bridge-DcBoeFvd.js} +1 -1
  143. package/dist/public/{file-viewer-C-6Jc_tb.js → file-viewer-CKtVC9Ix.js} +1 -1
  144. package/dist/public/index.css +1 -1
  145. package/dist/public/index.html +3 -3
  146. package/dist/public/index.js +1 -1
  147. package/dist/public/initControlPlane-Bi8ouFEb.js +1 -0
  148. package/dist/public/llms.txt +36 -1
  149. package/dist/public/main/index.html +3 -3
  150. package/dist/public/main/memory/index.html +3 -3
  151. package/dist/public/{markdown-C3M4OW_A.js → markdown-C-hCVJtc.js} +1 -1
  152. package/dist/public/orchestrator-8qNGfw8r.js +15 -0
  153. package/dist/public/{orchestrator-DHjdLtqa.js → orchestrator-BT3Ks5ip.js} +2 -2
  154. package/dist/public/peerjs-DfhwEiiq.js +1 -0
  155. package/dist/public/push-client-BVLrE9pK.js +1 -0
  156. package/dist/public/service-worker/fetch-proxy.js +1 -1
  157. package/dist/public/service-worker.js +1 -1
  158. package/dist/public/{shadow-claw-EAt2ml5Y.js → shadow-claw-DCjxNDCX.js} +3 -3
  159. package/dist/public/{shadow-claw-channels-XsfuWFF8.js → shadow-claw-channels-CITDrqMl.js} +1 -1
  160. package/dist/public/{shadow-claw-chat-CbSaOZ4q.js → shadow-claw-chat-BQrSgSKF.js} +1 -1
  161. package/dist/public/{shadow-claw-control-plane-DHvN3Ukg.js → shadow-claw-control-plane-Cp001OY0.js} +2 -2
  162. package/dist/public/{shadow-claw-conversations-BWs5VVfs.js → shadow-claw-conversations-EhX4RvIj.js} +1 -1
  163. package/dist/public/{shadow-claw-file-viewer-BWsoW5QS.js → shadow-claw-file-viewer-DiQXIjZD.js} +1 -1
  164. package/dist/public/{shadow-claw-files-CG7MqWiV.js → shadow-claw-files-lkTnSJsn.js} +1 -1
  165. package/dist/public/{shadow-claw-git-DBRPZNav.js → shadow-claw-git-BlcA_z8x.js} +2 -2
  166. package/dist/public/{shadow-claw-llm-DMvLgKXo.js → shadow-claw-llm-CW16X9Rg.js} +1 -1
  167. package/dist/public/{shadow-claw-networking-Dd8Rw31D.js → shadow-claw-networking-ySsMBY_i.js} +1 -1
  168. package/dist/public/{shadow-claw-notifications-Dbevj-lq.js → shadow-claw-notifications-CyV2zCQ8.js} +1 -1
  169. package/dist/public/{shadow-claw-pages-3_lQAv0t.js → shadow-claw-pages-Cx7cF9ah.js} +1 -1
  170. package/dist/public/{shadow-claw-settings-t8TIlgrM.js → shadow-claw-settings-zm4PHHGi.js} +2 -2
  171. package/dist/public/{shadow-claw-storage-CfEXoSIE.js → shadow-claw-storage-dy9JAEGd.js} +1 -1
  172. package/dist/public/{shadow-claw-task-server-DxQSsgRZ.js → shadow-claw-task-server-Bleww48s.js} +2 -2
  173. package/dist/public/{shadow-claw-tasks-DNpicQwz.js → shadow-claw-tasks-CTI5oaLN.js} +1 -1
  174. package/dist/public/{shadow-claw-terminal-DpY6zU_r.js → shadow-claw-terminal-mZ3i4HaQ.js} +1 -1
  175. package/dist/public/{shadow-claw-tools-WSwWohnk.js → shadow-claw-tools-B_nuk8Zl.js} +1 -1
  176. package/dist/public/{shadow-claw-webvm-CP6oFjg5.js → shadow-claw-webvm-Vl_jH5YL.js} +1 -1
  177. package/dist/public/{syncWebMcpRegistration-DOn9gLnD.js → syncWebMcpRegistration-n2vAMJ4w.js} +1 -1
  178. package/dist/server.js +197 -187
  179. package/package.json +2 -1
  180. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +6 -0
  181. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +19 -9
  182. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +14 -0
  183. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +12 -0
  184. package/src/components/shadow-claw/shadow-claw.ts +6 -0
  185. package/src/components/shadow-claw/utils/bindEventListeners.test.ts +57 -0
  186. package/src/components/shadow-claw/utils/bindEventListeners.ts +14 -0
  187. package/src/components/shadow-claw/utils/setupAppLifecycle.test.ts +201 -0
  188. package/src/components/shadow-claw/utils/setupAppLifecycle.ts +101 -0
  189. package/src/core/control-plane-client.test.ts +82 -1
  190. package/src/core/control-plane-client.ts +35 -1
  191. package/src/core/orchestrator/orchestrator.test.ts +49 -0
  192. package/src/core/orchestrator/orchestrator.ts +43 -0
  193. package/src/core/orchestrator/utils/operations/channel.test.ts +17 -0
  194. package/src/core/orchestrator/utils/operations/channel.ts +11 -5
  195. package/src/core/orchestrator/utils/operations/task.test.ts +26 -0
  196. package/src/core/orchestrator/utils/operations/task.ts +8 -1
  197. package/src/core/utils/initControlPlane.test.ts +138 -0
  198. package/src/core/utils/initControlPlane.ts +82 -0
  199. package/src/core/utils/initializeApp.test.ts +27 -1
  200. package/src/core/utils/initializeApp.ts +6 -13
  201. package/src/server/mcp/mcp-server.test.ts +6 -0
  202. package/src/server/mcp/mcp-server.ts +2 -1
  203. package/src/server/mcp/tools/built-in-tools.ts +88 -1
  204. package/src/server/mcp/tools/client-tool-relay.ts +6 -0
  205. package/src/server/mcp/tools/mcp-tools.test.ts +128 -0
  206. package/src/server/openapi/openapi-coverage.test.ts +2 -1
  207. package/src/server/openapi/openapi-spec.ts +2 -1
  208. package/src/server/openapi/paths/mcp.ts +104 -0
  209. package/src/server/openapi/paths/push.ts +3 -0
  210. package/src/server/routes/mcp.test.ts +92 -0
  211. package/src/server/routes/mcp.ts +138 -2
  212. package/src/server/utils/packageVersion.test.ts +10 -0
  213. package/src/server/utils/packageVersion.ts +32 -0
  214. package/src/service-worker/fetch-proxy-rules.test.ts +73 -0
  215. package/src/service-worker/fetch-proxy-rules.ts +44 -0
  216. package/src/service-worker/fetch-proxy.ts +1 -1
  217. package/src/subsystems/channels/imessage.test.ts +36 -0
  218. package/src/subsystems/channels/imessage.ts +11 -0
  219. package/src/subsystems/channels/peerjs.test.ts +56 -0
  220. package/src/subsystems/channels/peerjs.ts +29 -0
  221. package/src/subsystems/channels/telegram.test.ts +38 -0
  222. package/src/subsystems/channels/telegram.ts +13 -0
  223. package/src/subsystems/channels/types.ts +2 -0
  224. package/src/subsystems/notifications/push-client.test.ts +70 -0
  225. package/src/subsystems/notifications/push-client.ts +28 -3
  226. package/src/subsystems/notifications/push-routes.test.ts +82 -0
  227. package/src/subsystems/notifications/push-routes.ts +70 -16
  228. package/src/subsystems/notifications/push-store.test.ts +59 -0
  229. package/src/subsystems/notifications/push-store.ts +113 -19
  230. package/src/testing/jest-setup.ts +15 -0
  231. package/dist/lib/initControlPlane-C2zURVIS.js.map +0 -1
  232. package/dist/lib/orchestrator-CACLa_Ro.js.map +0 -1
  233. package/dist/lib/peerjs-DFWOBcXf.js.map +0 -1
  234. package/dist/lib/push-client-_tiWQsCT.js.map +0 -1
  235. package/dist/lib/shadow-claw-6UHdiy4a.js.map +0 -1
  236. package/dist/lib/shadow-claw-control-plane-UG0o7pCz.js.map +0 -1
  237. package/dist/public/initControlPlane-BN9cWyO0.js +0 -1
  238. package/dist/public/orchestrator-CMbCTjBf.js +0 -15
  239. package/dist/public/peerjs-DUCKH-m9.js +0 -1
  240. package/dist/public/push-client-D0lkwrK0.js +0 -1
@@ -271,4 +271,21 @@ export class CliControlClient {
271
271
  });
272
272
  return data;
273
273
  }
274
+
275
+ async broadcastNotification({ title = "ShadowClaw", body, clientId } = {}) {
276
+ const data = await this._request({
277
+ path: "/push/broadcast",
278
+ method: "POST",
279
+ body: {
280
+ title,
281
+ body,
282
+ ...(clientId ? { clientId } : {}),
283
+ },
284
+ });
285
+ return data;
286
+ }
287
+
288
+ async sendNotification(options) {
289
+ return this.broadcastNotification(options);
290
+ }
274
291
  }
@@ -35,6 +35,10 @@ describe("CliControlClient", () => {
35
35
  token,
36
36
  });
37
37
 
38
+ app.post("/push/broadcast", (req, res) => {
39
+ res.json({ sent: 1, failed: 0, received: req.body });
40
+ });
41
+
38
42
  await new Promise((resolve) => {
39
43
  server.listen(0, "127.0.0.1", () => {
40
44
  port = server.address().port;
@@ -90,4 +94,46 @@ describe("CliControlClient", () => {
90
94
  expect(client2.protocol).toBe("https");
91
95
  delete process.env.SHADOWCLAW_HTTPS;
92
96
  });
97
+
98
+ it("broadcasts push notifications via broadcastNotification", async () => {
99
+ const client = new CliControlClient({
100
+ host: "127.0.0.1",
101
+ port,
102
+ token,
103
+ });
104
+
105
+ const result = await client.broadcastNotification({
106
+ title: "Test Title",
107
+ body: "Test Body",
108
+ });
109
+
110
+ expect(result).toBeDefined();
111
+ expect(result.sent).toBe(1);
112
+ expect(result.received).toEqual({
113
+ title: "Test Title",
114
+ body: "Test Body",
115
+ });
116
+ });
117
+
118
+ it("sends targeted push notification with clientId via sendNotification", async () => {
119
+ const client = new CliControlClient({
120
+ host: "127.0.0.1",
121
+ port,
122
+ token,
123
+ });
124
+
125
+ const result = await client.sendNotification({
126
+ title: "Targeted Alert",
127
+ body: "Hello specific client",
128
+ clientId: "client-target-42",
129
+ });
130
+
131
+ expect(result).toBeDefined();
132
+ expect(result.sent).toBe(1);
133
+ expect(result.received).toEqual({
134
+ title: "Targeted Alert",
135
+ body: "Hello specific client",
136
+ clientId: "client-target-42",
137
+ });
138
+ });
93
139
  });
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from "node:child_process";
4
+ import { readFile, writeFile, stat } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const rootDir = path.resolve(__dirname, "..");
11
+
12
+ export const DISALLOWED_KEYWORDS = new Set([
13
+ "major",
14
+ "minor",
15
+ "patch",
16
+ "premajor",
17
+ "preminor",
18
+ "prepatch",
19
+ "prerelease",
20
+ "from-git",
21
+ ]);
22
+
23
+ export const USAGE_NOTICE = `
24
+ Usage: npm run version [<newversion>]
25
+
26
+ Note: Only explicit semantic version strings (e.g. 1.28.0) are supported.
27
+ Keyword commands (major, minor, patch, premajor, preminor, prepatch, prerelease, from-git) are not supported.
28
+
29
+ Standard npm version syntax for reference:
30
+ npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]
31
+ `.trim();
32
+
33
+ export async function pathExists(p) {
34
+ try {
35
+ await stat(p);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ export function parseSemverString(v) {
43
+ const clean = String(v || "")
44
+ .trim()
45
+ .replace(/^v/, "");
46
+ const match = clean.match(
47
+ /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/,
48
+ );
49
+ if (!match) return null;
50
+ return clean;
51
+ }
52
+
53
+ export function validateVersionArg(arg) {
54
+ if (!arg) return null;
55
+ const lower = arg.toLowerCase().trim();
56
+ if (lower === "--help" || lower === "-h") {
57
+ return { isHelp: true };
58
+ }
59
+ if (DISALLOWED_KEYWORDS.has(lower)) {
60
+ return {
61
+ error: `Error: Keyword command "${arg}" is not supported by npm run version.\n\n${USAGE_NOTICE}`,
62
+ };
63
+ }
64
+ const clean = parseSemverString(arg);
65
+ if (!clean) {
66
+ return {
67
+ error: `Error: Invalid version string "${arg}". Expected a valid semantic version (e.g. 1.28.0).\n\n${USAGE_NOTICE}`,
68
+ };
69
+ }
70
+ return { version: clean };
71
+ }
72
+
73
+ export async function getVersionMap(targetRoot = rootDir) {
74
+ const pkg = JSON.parse(
75
+ await readFile(path.join(targetRoot, "package.json"), "utf8"),
76
+ );
77
+ let npmVer = "";
78
+ try {
79
+ npmVer = execSync("npm -v", { encoding: "utf8" }).trim();
80
+ } catch {}
81
+
82
+ return {
83
+ [pkg.name]: pkg.version,
84
+ ...(npmVer ? { npm: npmVer } : {}),
85
+ ...process.versions,
86
+ };
87
+ }
88
+
89
+ export async function syncWellKnownVersions(newVersion, targetRoot = rootDir) {
90
+ const filesToSync = [
91
+ path.join(targetRoot, ".well-known/mcp.json"),
92
+ path.join(targetRoot, ".well-known/mcp/server-card.json"),
93
+ path.join(targetRoot, "dist/public/.well-known/mcp.json"),
94
+ path.join(targetRoot, "dist/public/.well-known/mcp/server-card.json"),
95
+ ];
96
+
97
+ const updatedFiles = [];
98
+ for (const filePath of filesToSync) {
99
+ if (await pathExists(filePath)) {
100
+ try {
101
+ const content = await readFile(filePath, "utf8");
102
+ const json = JSON.parse(content);
103
+ if (json.version !== newVersion) {
104
+ json.version = newVersion;
105
+ await writeFile(
106
+ filePath,
107
+ JSON.stringify(json, null, 2) + "\n",
108
+ "utf8",
109
+ );
110
+ updatedFiles.push(path.relative(targetRoot, filePath));
111
+ }
112
+ } catch (err) {
113
+ console.warn(`Warning: Could not update version in ${filePath}:`, err);
114
+ }
115
+ }
116
+ }
117
+ return updatedFiles;
118
+ }
119
+
120
+ export async function runVersion(args = process.argv.slice(2), options = {}) {
121
+ const isLifecycle =
122
+ options.isLifecycle ??
123
+ (process.env.npm_command === "version" &&
124
+ process.env.npm_lifecycle_event === "version");
125
+ const targetRoot = options.rootDir || rootDir;
126
+ const execOptions = options.execOptions || {
127
+ cwd: targetRoot,
128
+ stdio: options.stdio || "inherit",
129
+ };
130
+
131
+ // 1. If running under npm's lifecycle hook (npm version <newver> triggered this script)
132
+ if (isLifecycle) {
133
+ const pkg = JSON.parse(
134
+ await readFile(path.join(targetRoot, "package.json"), "utf8"),
135
+ );
136
+ const newVersion = pkg.version;
137
+ const updated = await syncWellKnownVersions(newVersion, targetRoot);
138
+ if (
139
+ updated.length > 0 &&
140
+ (await pathExists(path.join(targetRoot, ".git")))
141
+ ) {
142
+ try {
143
+ execSync(
144
+ `git add .well-known/mcp.json .well-known/mcp/server-card.json`,
145
+ { cwd: targetRoot, stdio: "pipe" },
146
+ );
147
+ } catch {}
148
+ }
149
+ return { version: newVersion, updated };
150
+ }
151
+
152
+ // 2. Direct CLI invocation
153
+ const targetArg = args.find((a) => !a.startsWith("--")) || args[0];
154
+
155
+ // No arguments passed -> list versions mimicking `npm version`
156
+ if (!targetArg) {
157
+ const versionMap = await getVersionMap(targetRoot);
158
+ if (execOptions.stdio === "inherit") {
159
+ console.log(versionMap);
160
+ }
161
+ return { action: "list", versions: versionMap };
162
+ }
163
+
164
+ const validation = validateVersionArg(targetArg);
165
+ if (validation.isHelp) {
166
+ if (execOptions.stdio === "inherit") {
167
+ console.log(USAGE_NOTICE);
168
+ }
169
+ return { action: "help", message: USAGE_NOTICE };
170
+ }
171
+
172
+ if (validation.error) {
173
+ throw new Error(validation.error);
174
+ }
175
+
176
+ const cleanVersion = validation.version;
177
+
178
+ // Run npm version --no-git-tag-version --allow-same-version --ignore-scripts
179
+ execSync(
180
+ `npm version ${cleanVersion} --no-git-tag-version --allow-same-version --ignore-scripts`,
181
+ execOptions,
182
+ );
183
+
184
+ // Sync .well-known JSON discovery files
185
+ const updated = await syncWellKnownVersions(cleanVersion, targetRoot);
186
+
187
+ if (execOptions.stdio === "inherit") {
188
+ console.log(`v${cleanVersion}`);
189
+ if (updated.length > 0) {
190
+ console.log(
191
+ `Updated .well-known discovery versions: ${updated.join(", ")}`,
192
+ );
193
+ }
194
+ }
195
+
196
+ return { version: cleanVersion, updated };
197
+ }
198
+
199
+ const isMainModule =
200
+ process.argv[1] &&
201
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
202
+
203
+ if (isMainModule) {
204
+ runVersion().catch((err) => {
205
+ console.error(`\n${err.message}\n`);
206
+ process.exit(1);
207
+ });
208
+ }
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import {
6
+ DISALLOWED_KEYWORDS,
7
+ USAGE_NOTICE,
8
+ getVersionMap,
9
+ parseSemverString,
10
+ runVersion,
11
+ syncWellKnownVersions,
12
+ validateVersionArg,
13
+ } from "./version.mjs";
14
+
15
+ describe("bin/version.mjs", () => {
16
+ describe("parseSemverString", () => {
17
+ it("parses valid semver versions with or without leading v", () => {
18
+ expect(parseSemverString("1.28.0")).toBe("1.28.0");
19
+ expect(parseSemverString("v1.28.0")).toBe("1.28.0");
20
+ expect(parseSemverString("2.0.0-rc.1")).toBe("2.0.0-rc.1");
21
+ expect(parseSemverString("v0.1.0+build.123")).toBe("0.1.0+build.123");
22
+ });
23
+
24
+ it("returns null for non-semver strings", () => {
25
+ expect(parseSemverString("minor")).toBeNull();
26
+ expect(parseSemverString("latest")).toBeNull();
27
+ expect(parseSemverString("1.2")).toBeNull();
28
+ expect(parseSemverString("")).toBeNull();
29
+ });
30
+ });
31
+
32
+ describe("validateVersionArg", () => {
33
+ it("returns isHelp for help flags", () => {
34
+ expect(validateVersionArg("--help")).toEqual({ isHelp: true });
35
+ expect(validateVersionArg("-h")).toEqual({ isHelp: true });
36
+ });
37
+
38
+ it("rejects all disallowed npm version keyword commands", () => {
39
+ for (const keyword of DISALLOWED_KEYWORDS) {
40
+ const result = validateVersionArg(keyword);
41
+ expect(result.error).toBeDefined();
42
+ expect(result.error).toContain(keyword);
43
+ expect(result.error).toContain(USAGE_NOTICE);
44
+ }
45
+ });
46
+
47
+ it("rejects invalid semver strings", () => {
48
+ const result = validateVersionArg("invalid-version");
49
+ expect(result.error).toBeDefined();
50
+ expect(result.error).toContain("Invalid version string");
51
+ expect(result.error).toContain(USAGE_NOTICE);
52
+ });
53
+
54
+ it("accepts valid semver string", () => {
55
+ expect(validateVersionArg("1.28.0")).toEqual({ version: "1.28.0" });
56
+ expect(validateVersionArg("v1.28.0")).toEqual({ version: "1.28.0" });
57
+ });
58
+ });
59
+
60
+ describe("getVersionMap", () => {
61
+ it("returns version mapping object containing package name and node versions", async () => {
62
+ const map = await getVersionMap();
63
+ expect(map["shadow-claw"]).toBeDefined();
64
+ expect(map.node).toBeDefined();
65
+ });
66
+ });
67
+
68
+ describe("syncWellKnownVersions", () => {
69
+ let tempDir;
70
+
71
+ beforeEach(async () => {
72
+ tempDir = await mkdtemp(path.join(os.tmpdir(), "sync-well-known-"));
73
+ await mkdir(path.join(tempDir, ".well-known/mcp"), { recursive: true });
74
+ await writeFile(
75
+ path.join(tempDir, ".well-known/mcp.json"),
76
+ JSON.stringify({ name: "shadow-claw", version: "1.27.1" }, null, 2),
77
+ "utf8",
78
+ );
79
+ await writeFile(
80
+ path.join(tempDir, ".well-known/mcp/server-card.json"),
81
+ JSON.stringify({ name: "shadow-claw", version: "1.27.1" }, null, 2),
82
+ "utf8",
83
+ );
84
+ });
85
+
86
+ afterEach(async () => {
87
+ await rm(tempDir, { recursive: true, force: true });
88
+ });
89
+
90
+ it("updates version in .well-known/mcp.json and .well-known/mcp/server-card.json", async () => {
91
+ const updated = await syncWellKnownVersions("1.28.0", tempDir);
92
+ expect(updated).toEqual(
93
+ expect.arrayContaining([
94
+ ".well-known/mcp.json",
95
+ ".well-known/mcp/server-card.json",
96
+ ]),
97
+ );
98
+
99
+ const mcpJson = JSON.parse(
100
+ await readFile(path.join(tempDir, ".well-known/mcp.json"), "utf8"),
101
+ );
102
+ const serverCard = JSON.parse(
103
+ await readFile(
104
+ path.join(tempDir, ".well-known/mcp/server-card.json"),
105
+ "utf8",
106
+ ),
107
+ );
108
+ expect(mcpJson.version).toBe("1.28.0");
109
+ expect(serverCard.version).toBe("1.28.0");
110
+ });
111
+
112
+ it("skips updating files that already have the matching version", async () => {
113
+ await syncWellKnownVersions("1.28.0", tempDir);
114
+ const secondUpdate = await syncWellKnownVersions("1.28.0", tempDir);
115
+ expect(secondUpdate).toEqual([]);
116
+ });
117
+ });
118
+
119
+ describe("runVersion CLI execution", () => {
120
+ it("returns list action when no arguments are provided", async () => {
121
+ const result = await runVersion([], { stdio: "pipe" });
122
+ expect(result.action).toBe("list");
123
+ expect(result.versions["shadow-claw"]).toBeDefined();
124
+ });
125
+
126
+ it("returns help action when --help flag is provided", async () => {
127
+ const result = await runVersion(["--help"], { stdio: "pipe" });
128
+ expect(result.action).toBe("help");
129
+ expect(result.message).toContain(USAGE_NOTICE);
130
+ });
131
+
132
+ it("throws error when disallowed keyword is provided", async () => {
133
+ await expect(runVersion(["minor"], { stdio: "pipe" })).rejects.toThrow(
134
+ 'Keyword command "minor" is not supported',
135
+ );
136
+ });
137
+ });
138
+ });