shadow-claw 1.27.1 → 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.
- package/.well-known/ai-catalog.json +11 -0
- package/.well-known/mcp/index.html +19 -0
- package/.well-known/mcp/server-card.json +52 -0
- package/.well-known/mcp/servers.json +15 -0
- package/.well-known/mcp.json +52 -0
- package/.well-known/oauth-protected-resource +10 -0
- package/.well-known/oauth-protected-resource.json +10 -0
- package/bin/assert-version-bump.mjs +61 -3
- package/bin/assert-version-bump.test.mjs +77 -1
- package/bin/build/build.mjs +99 -34
- package/bin/build/build.test.mjs +32 -0
- package/bin/cli.mjs +290 -237
- package/bin/cli.test.mjs +27 -0
- package/bin/commands/mcp.mjs +132 -2
- package/bin/commands/mcp.test.mjs +166 -0
- package/bin/utils/control-client.mjs +17 -0
- package/bin/utils/control-client.test.mjs +46 -0
- package/bin/version.mjs +208 -0
- package/bin/version.test.mjs +138 -0
- package/dist/electron/main.cjs +195 -185
- package/dist/lib/components/index.js +21 -21
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw/shadow-claw.d.ts +1 -0
- package/dist/lib/components/shadow-claw/utils/setupAppLifecycle.d.ts +23 -0
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{configurePeerJs-oODzltaC.js → configurePeerJs-D8Ph0-A9.js} +2 -2
- package/dist/lib/{configurePeerJs-oODzltaC.js.map → configurePeerJs-D8Ph0-A9.js.map} +1 -1
- package/dist/lib/core/control-plane-client.d.ts +2 -0
- package/dist/lib/core/orchestrator/orchestrator.d.ts +10 -0
- package/dist/lib/core/orchestrator/utils/operations/channel.d.ts +2 -2
- package/dist/lib/core/utils/initControlPlane.d.ts +6 -0
- package/dist/lib/index.js +21 -21
- package/dist/lib/{initControlPlane-C2zURVIS.js → initControlPlane-BIrWmpzc.js} +76 -9
- package/dist/lib/initControlPlane-BIrWmpzc.js.map +1 -0
- package/dist/lib/{markdown-C6EHl9zs.js → markdown-DEUoM1c6.js} +2 -2
- package/dist/lib/{markdown-C6EHl9zs.js.map → markdown-DEUoM1c6.js.map} +1 -1
- package/dist/lib/{orchestrator-KRjx0eba.js → orchestrator-BtVj9bBB.js} +11 -10
- package/dist/lib/{orchestrator-KRjx0eba.js.map → orchestrator-BtVj9bBB.js.map} +1 -1
- package/dist/lib/{orchestrator-CACLa_Ro.js → orchestrator-CFCW-l1O.js} +52 -4
- package/dist/lib/orchestrator-CFCW-l1O.js.map +1 -0
- package/dist/lib/{peerjs-DFWOBcXf.js → peerjs-BoBkx9DJ.js} +23 -3
- package/dist/lib/peerjs-BoBkx9DJ.js.map +1 -0
- package/dist/lib/{push-client-_tiWQsCT.js → push-client-D6hbzDNV.js} +10 -4
- package/dist/lib/push-client-D6hbzDNV.js.map +1 -0
- package/dist/lib/{shadow-claw-6UHdiy4a.js → shadow-claw-Cuf2O5bZ.js} +71 -4
- package/dist/lib/shadow-claw-Cuf2O5bZ.js.map +1 -0
- package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js → shadow-claw-channel-config-BDPKt6Bd.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js.map → shadow-claw-channel-config-BDPKt6Bd.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-1N6Jl-By.js → shadow-claw-channels-BsP5JABM.js} +2 -2
- package/dist/lib/{shadow-claw-channels-1N6Jl-By.js.map → shadow-claw-channels-BsP5JABM.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BMoqQJRK.js → shadow-claw-chat-Cam2lX4k.js} +4 -4
- package/dist/lib/{shadow-claw-chat-BMoqQJRK.js.map → shadow-claw-chat-Cam2lX4k.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-UG0o7pCz.js → shadow-claw-control-plane-DB2qcCFs.js} +20 -10
- package/dist/lib/shadow-claw-control-plane-DB2qcCFs.js.map +1 -0
- package/dist/lib/{shadow-claw-conversations-BwXep3iu.js → shadow-claw-conversations-C9Vf4XGx.js} +2 -2
- package/dist/lib/{shadow-claw-conversations-BwXep3iu.js.map → shadow-claw-conversations-C9Vf4XGx.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js → shadow-claw-file-viewer-B4HYiI0v.js} +3 -3
- package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js.map → shadow-claw-file-viewer-B4HYiI0v.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-DIREEnT2.js → shadow-claw-files-DVROUESO.js} +2 -2
- package/dist/lib/{shadow-claw-files-DIREEnT2.js.map → shadow-claw-files-DVROUESO.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-Bg1SWgsF.js → shadow-claw-git-BqrO7cHJ.js} +3 -3
- package/dist/lib/{shadow-claw-git-Bg1SWgsF.js.map → shadow-claw-git-BqrO7cHJ.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-CazBrVpM.js → shadow-claw-llm-BexUQdYo.js} +2 -2
- package/dist/lib/{shadow-claw-llm-CazBrVpM.js.map → shadow-claw-llm-BexUQdYo.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-Df85tCbO.js → shadow-claw-networking-CfXRGpUD.js} +2 -2
- package/dist/lib/{shadow-claw-networking-Df85tCbO.js.map → shadow-claw-networking-CfXRGpUD.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-v62FryTc.js → shadow-claw-notifications-C9KPMgx_.js} +2 -2
- package/dist/lib/{shadow-claw-notifications-v62FryTc.js.map → shadow-claw-notifications-C9KPMgx_.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-BgPyIEZO.js → shadow-claw-pages-CGzGXmrI.js} +3 -3
- package/dist/lib/{shadow-claw-pages-BgPyIEZO.js.map → shadow-claw-pages-CGzGXmrI.js.map} +1 -1
- package/dist/lib/{shadow-claw-peerjs-xkBXILed.js → shadow-claw-peerjs-B0ZB7yhB.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-xkBXILed.js.map → shadow-claw-peerjs-B0ZB7yhB.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-CN89xRVU.js → shadow-claw-settings-DBfMZdDS.js} +2 -2
- package/dist/lib/{shadow-claw-settings-CN89xRVU.js.map → shadow-claw-settings-DBfMZdDS.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-VnhcObCe.js → shadow-claw-storage-CM-w6mgG.js} +2 -2
- package/dist/lib/{shadow-claw-storage-VnhcObCe.js.map → shadow-claw-storage-CM-w6mgG.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js → shadow-claw-task-server-DV4b1g2-.js} +4 -2
- package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js.map → shadow-claw-task-server-DV4b1g2-.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-i_evIufy.js → shadow-claw-tasks-K-foOHSE.js} +3 -3
- package/dist/lib/{shadow-claw-tasks-i_evIufy.js.map → shadow-claw-tasks-K-foOHSE.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-CwScROlu.js → shadow-claw-terminal-CpAknKyA.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-CwScROlu.js.map → shadow-claw-terminal-CpAknKyA.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-DD3fyXP4.js → shadow-claw-tools-D98XZ0Ep.js} +3 -3
- package/dist/lib/{shadow-claw-tools-DD3fyXP4.js.map → shadow-claw-tools-D98XZ0Ep.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js → shadow-claw-webvm-DLOb8dMI.js} +2 -2
- package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js.map → shadow-claw-webvm-DLOb8dMI.js.map} +1 -1
- package/dist/lib/subsystems/channels/imessage.d.ts +1 -0
- package/dist/lib/subsystems/channels/peerjs.d.ts +1 -0
- package/dist/lib/subsystems/channels/telegram.d.ts +1 -0
- package/dist/lib/subsystems/channels/types.d.ts +2 -0
- package/dist/lib/subsystems/notifications/push-client.d.ts +1 -1
- package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js → syncWebMcpRegistration-NspKefOI.js} +2 -2
- package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js.map → syncWebMcpRegistration-NspKefOI.js.map} +1 -1
- package/dist/public/.well-known/ai-catalog.json +11 -0
- package/dist/public/.well-known/mcp/index.html +19 -0
- package/dist/public/.well-known/mcp/server-card.json +52 -0
- package/dist/public/.well-known/mcp/servers.json +15 -0
- package/dist/public/.well-known/mcp.json +52 -0
- package/dist/public/.well-known/oauth-protected-resource +10 -0
- package/dist/public/.well-known/oauth-protected-resource.json +10 -0
- package/dist/public/404.html +28 -1
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/{configurePeerJs-BqgeBjP7.js → configurePeerJs-BhBokQlC.js} +1 -1
- package/dist/public/docs/example/article/index.html +3 -3
- package/dist/public/docs/publishing/index.html +3 -3
- package/dist/public/docs/skill-creator/index.html +3 -3
- package/dist/public/docs/subsystems/mcp-server.md +1 -0
- package/dist/public/docs/subsystems/notifications.md +10 -0
- package/dist/public/{e2e-bridge-Ceqr5kGc.js → e2e-bridge-DcBoeFvd.js} +1 -1
- package/dist/public/{file-viewer-C-6Jc_tb.js → file-viewer-CKtVC9Ix.js} +1 -1
- package/dist/public/index.css +1 -1
- package/dist/public/index.html +3 -3
- package/dist/public/index.js +1 -1
- package/dist/public/initControlPlane-Bi8ouFEb.js +1 -0
- package/dist/public/llms.txt +36 -1
- package/dist/public/main/index.html +3 -3
- package/dist/public/main/memory/index.html +3 -3
- package/dist/public/{markdown-C3M4OW_A.js → markdown-C-hCVJtc.js} +1 -1
- package/dist/public/orchestrator-8qNGfw8r.js +15 -0
- package/dist/public/{orchestrator-DHjdLtqa.js → orchestrator-BT3Ks5ip.js} +2 -2
- package/dist/public/peerjs-DfhwEiiq.js +1 -0
- package/dist/public/push-client-BVLrE9pK.js +1 -0
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-EAt2ml5Y.js → shadow-claw-DCjxNDCX.js} +3 -3
- package/dist/public/{shadow-claw-channels-XsfuWFF8.js → shadow-claw-channels-CITDrqMl.js} +1 -1
- package/dist/public/{shadow-claw-chat-CbSaOZ4q.js → shadow-claw-chat-BQrSgSKF.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-DHvN3Ukg.js → shadow-claw-control-plane-Cp001OY0.js} +2 -2
- package/dist/public/{shadow-claw-conversations-BWs5VVfs.js → shadow-claw-conversations-EhX4RvIj.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-BWsoW5QS.js → shadow-claw-file-viewer-DiQXIjZD.js} +1 -1
- package/dist/public/{shadow-claw-files-CG7MqWiV.js → shadow-claw-files-lkTnSJsn.js} +1 -1
- package/dist/public/{shadow-claw-git-DBRPZNav.js → shadow-claw-git-BlcA_z8x.js} +2 -2
- package/dist/public/{shadow-claw-llm-DMvLgKXo.js → shadow-claw-llm-CW16X9Rg.js} +1 -1
- package/dist/public/{shadow-claw-networking-Dd8Rw31D.js → shadow-claw-networking-ySsMBY_i.js} +1 -1
- package/dist/public/{shadow-claw-notifications-Dbevj-lq.js → shadow-claw-notifications-CyV2zCQ8.js} +1 -1
- package/dist/public/{shadow-claw-pages-3_lQAv0t.js → shadow-claw-pages-Cx7cF9ah.js} +1 -1
- package/dist/public/{shadow-claw-settings-t8TIlgrM.js → shadow-claw-settings-zm4PHHGi.js} +2 -2
- package/dist/public/{shadow-claw-storage-CfEXoSIE.js → shadow-claw-storage-dy9JAEGd.js} +1 -1
- package/dist/public/{shadow-claw-task-server-DxQSsgRZ.js → shadow-claw-task-server-Bleww48s.js} +2 -2
- package/dist/public/{shadow-claw-tasks-DNpicQwz.js → shadow-claw-tasks-CTI5oaLN.js} +1 -1
- package/dist/public/{shadow-claw-terminal-DpY6zU_r.js → shadow-claw-terminal-mZ3i4HaQ.js} +1 -1
- package/dist/public/{shadow-claw-tools-WSwWohnk.js → shadow-claw-tools-B_nuk8Zl.js} +1 -1
- package/dist/public/{shadow-claw-webvm-CP6oFjg5.js → shadow-claw-webvm-Vl_jH5YL.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-DOn9gLnD.js → syncWebMcpRegistration-n2vAMJ4w.js} +1 -1
- package/dist/server.js +197 -187
- package/package.json +2 -1
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +6 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +19 -9
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +14 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +12 -0
- package/src/components/shadow-claw/shadow-claw.ts +6 -0
- package/src/components/shadow-claw/utils/bindEventListeners.test.ts +57 -0
- package/src/components/shadow-claw/utils/bindEventListeners.ts +14 -0
- package/src/components/shadow-claw/utils/setupAppLifecycle.test.ts +201 -0
- package/src/components/shadow-claw/utils/setupAppLifecycle.ts +101 -0
- package/src/core/control-plane-client.test.ts +82 -1
- package/src/core/control-plane-client.ts +35 -1
- package/src/core/orchestrator/orchestrator.test.ts +49 -0
- package/src/core/orchestrator/orchestrator.ts +43 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +17 -0
- package/src/core/orchestrator/utils/operations/channel.ts +11 -5
- package/src/core/orchestrator/utils/operations/task.test.ts +26 -0
- package/src/core/orchestrator/utils/operations/task.ts +8 -1
- package/src/core/utils/initControlPlane.test.ts +138 -0
- package/src/core/utils/initControlPlane.ts +82 -0
- package/src/core/utils/initializeApp.test.ts +27 -1
- package/src/core/utils/initializeApp.ts +6 -13
- package/src/server/mcp/mcp-server.test.ts +6 -0
- package/src/server/mcp/mcp-server.ts +2 -1
- package/src/server/mcp/tools/built-in-tools.ts +88 -1
- package/src/server/mcp/tools/client-tool-relay.ts +6 -0
- package/src/server/mcp/tools/mcp-tools.test.ts +128 -0
- package/src/server/openapi/openapi-coverage.test.ts +2 -1
- package/src/server/openapi/openapi-spec.ts +2 -1
- package/src/server/openapi/paths/mcp.ts +104 -0
- package/src/server/openapi/paths/push.ts +3 -0
- package/src/server/routes/mcp.test.ts +92 -0
- package/src/server/routes/mcp.ts +138 -2
- package/src/server/utils/packageVersion.test.ts +10 -0
- package/src/server/utils/packageVersion.ts +32 -0
- package/src/service-worker/fetch-proxy-rules.test.ts +73 -0
- package/src/service-worker/fetch-proxy-rules.ts +44 -0
- package/src/service-worker/fetch-proxy.ts +1 -1
- package/src/subsystems/channels/imessage.test.ts +36 -0
- package/src/subsystems/channels/imessage.ts +11 -0
- package/src/subsystems/channels/peerjs.test.ts +56 -0
- package/src/subsystems/channels/peerjs.ts +29 -0
- package/src/subsystems/channels/telegram.test.ts +38 -0
- package/src/subsystems/channels/telegram.ts +13 -0
- package/src/subsystems/channels/types.ts +2 -0
- package/src/subsystems/notifications/push-client.test.ts +70 -0
- package/src/subsystems/notifications/push-client.ts +28 -3
- package/src/subsystems/notifications/push-routes.test.ts +82 -0
- package/src/subsystems/notifications/push-routes.ts +70 -16
- package/src/subsystems/notifications/push-store.test.ts +59 -0
- package/src/subsystems/notifications/push-store.ts +113 -19
- package/src/testing/jest-setup.ts +2 -0
- package/dist/lib/initControlPlane-C2zURVIS.js.map +0 -1
- package/dist/lib/orchestrator-CACLa_Ro.js.map +0 -1
- package/dist/lib/peerjs-DFWOBcXf.js.map +0 -1
- package/dist/lib/push-client-_tiWQsCT.js.map +0 -1
- package/dist/lib/shadow-claw-6UHdiy4a.js.map +0 -1
- package/dist/lib/shadow-claw-control-plane-UG0o7pCz.js.map +0 -1
- package/dist/public/initControlPlane-BN9cWyO0.js +0 -1
- package/dist/public/orchestrator-CMbCTjBf.js +0 -15
- package/dist/public/peerjs-DUCKH-m9.js +0 -1
- package/dist/public/push-client-D0lkwrK0.js +0 -1
package/bin/cli.test.mjs
CHANGED
|
@@ -60,6 +60,12 @@ describe("shadow-claw CLI", () => {
|
|
|
60
60
|
"--help",
|
|
61
61
|
]);
|
|
62
62
|
expect(stdout).toContain("Usage: shadow-claw");
|
|
63
|
+
expect(stdout).toContain("Development:");
|
|
64
|
+
expect(stdout).toContain("Server:");
|
|
65
|
+
expect(stdout).toContain("Control Plane & Client Bridge:");
|
|
66
|
+
expect(stdout).toContain("WebRTC:");
|
|
67
|
+
expect(stdout).toContain("Project:");
|
|
68
|
+
expect(stdout).toContain("Help & Utility:");
|
|
63
69
|
expect(stdout).toContain("build");
|
|
64
70
|
expect(stdout).toContain("dev");
|
|
65
71
|
expect(stdout).toContain("run");
|
|
@@ -75,6 +81,27 @@ describe("shadow-claw CLI", () => {
|
|
|
75
81
|
expect(stdout).toContain("mcp");
|
|
76
82
|
});
|
|
77
83
|
|
|
84
|
+
it("groups subcommand options into logical categories in help", async () => {
|
|
85
|
+
const { stdout: devHelp } = await execFileAsync(process.execPath, [
|
|
86
|
+
cliPath,
|
|
87
|
+
"dev",
|
|
88
|
+
"--help",
|
|
89
|
+
]);
|
|
90
|
+
expect(devHelp).toContain("Server & Network Options:");
|
|
91
|
+
expect(devHelp).toContain("Site & Build Options:");
|
|
92
|
+
expect(devHelp).toContain("Security & Proxy Options:");
|
|
93
|
+
expect(devHelp).toContain("TLS / HTTPS Options:");
|
|
94
|
+
expect(devHelp).toContain("Storage & Database Options:");
|
|
95
|
+
|
|
96
|
+
const { stdout: webrtcHelp } = await execFileAsync(process.execPath, [
|
|
97
|
+
cliPath,
|
|
98
|
+
"webrtc",
|
|
99
|
+
"--help",
|
|
100
|
+
]);
|
|
101
|
+
expect(webrtcHelp).toContain("Identity Options:");
|
|
102
|
+
expect(webrtcHelp).toContain("Signaling & Listener Options");
|
|
103
|
+
});
|
|
104
|
+
|
|
78
105
|
it("outputs server command help with server --help and aliases", async () => {
|
|
79
106
|
const { stdout: outServer } = await execFileAsync(process.execPath, [
|
|
80
107
|
cliPath,
|
package/bin/commands/mcp.mjs
CHANGED
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
import readline from "node:readline";
|
|
10
10
|
import http from "node:http";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
11
14
|
import { CliControlClient } from "../utils/control-client.mjs";
|
|
12
15
|
|
|
16
|
+
const __mcp_dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
function getCliVersion() {
|
|
18
|
+
try {
|
|
19
|
+
const pkgPath = path.resolve(__mcp_dirname, "../../package.json");
|
|
20
|
+
if (fs.existsSync(pkgPath)) {
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
22
|
+
if (pkg.version) return pkg.version;
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
return "1.27.1";
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
export const CLI_BUILTIN_TOOLS = [
|
|
14
29
|
{
|
|
15
30
|
name: "shadowclaw_list_clients",
|
|
@@ -105,6 +120,30 @@ export const CLI_BUILTIN_TOOLS = [
|
|
|
105
120
|
},
|
|
106
121
|
},
|
|
107
122
|
},
|
|
123
|
+
{
|
|
124
|
+
name: "shadowclaw_send_notification",
|
|
125
|
+
description:
|
|
126
|
+
"Broadcast an OS-level push notification to subscribed devices via Web Push (VAPID), or send to a specific registered client. Works even when the client browser tab is closed, asleep, or running in the background.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
title: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Notification title (default: 'ShadowClaw').",
|
|
133
|
+
},
|
|
134
|
+
body: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "The notification body message text.",
|
|
137
|
+
},
|
|
138
|
+
clientId: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description:
|
|
141
|
+
"Target client ID, prefix, or device label of a specific client that has registered in the past. If omitted, broadcasts to all subscribed devices.",
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
required: ["body"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
108
147
|
];
|
|
109
148
|
|
|
110
149
|
export function createCliMcpEngine(options = {}) {
|
|
@@ -115,7 +154,7 @@ export function createCliMcpEngine(options = {}) {
|
|
|
115
154
|
|
|
116
155
|
const serverInfo = {
|
|
117
156
|
name: "shadow-claw",
|
|
118
|
-
version:
|
|
157
|
+
version: options.version || getCliVersion(),
|
|
119
158
|
};
|
|
120
159
|
|
|
121
160
|
const capabilities = {
|
|
@@ -193,6 +232,12 @@ export function createCliMcpEngine(options = {}) {
|
|
|
193
232
|
Array.isArray(res.data.tools)
|
|
194
233
|
) {
|
|
195
234
|
for (const t of res.data.tools) {
|
|
235
|
+
if (
|
|
236
|
+
t.name === "send_notification" ||
|
|
237
|
+
t.name.startsWith("shadowclaw_")
|
|
238
|
+
) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
196
241
|
if (!toolDefMap.has(t.name)) {
|
|
197
242
|
toolDefMap.set(t.name, t);
|
|
198
243
|
toolSupportingClientsMap.set(t.name, []);
|
|
@@ -316,7 +361,9 @@ export function createCliMcpEngine(options = {}) {
|
|
|
316
361
|
combined.set(t.name, t);
|
|
317
362
|
}
|
|
318
363
|
for (const t of relayed) {
|
|
319
|
-
combined.
|
|
364
|
+
if (!combined.has(t.name)) {
|
|
365
|
+
combined.set(t.name, t);
|
|
366
|
+
}
|
|
320
367
|
}
|
|
321
368
|
|
|
322
369
|
const sortedTools = Array.from(combined.values()).sort((a, b) =>
|
|
@@ -541,6 +588,89 @@ export function createCliMcpEngine(options = {}) {
|
|
|
541
588
|
};
|
|
542
589
|
}
|
|
543
590
|
|
|
591
|
+
if (
|
|
592
|
+
toolName === "shadowclaw_send_notification" ||
|
|
593
|
+
toolName === "send_notification"
|
|
594
|
+
) {
|
|
595
|
+
const body = String(args.body || "").trim();
|
|
596
|
+
if (!body) {
|
|
597
|
+
return {
|
|
598
|
+
jsonrpc: "2.0",
|
|
599
|
+
id: reqId,
|
|
600
|
+
result: {
|
|
601
|
+
resultType: "complete",
|
|
602
|
+
isError: true,
|
|
603
|
+
content: [
|
|
604
|
+
{
|
|
605
|
+
type: "text",
|
|
606
|
+
text: "Error: Notification 'body' parameter cannot be empty.",
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
_meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const title = String(args.title || "ShadowClaw").trim();
|
|
615
|
+
const targetClientId =
|
|
616
|
+
typeof args.clientId === "string" && args.clientId.trim()
|
|
617
|
+
? args.clientId.trim()
|
|
618
|
+
: undefined;
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const res = await client.broadcastNotification({
|
|
622
|
+
title,
|
|
623
|
+
body,
|
|
624
|
+
...(targetClientId ? { clientId: targetClientId } : {}),
|
|
625
|
+
});
|
|
626
|
+
const sentCount = res?.sent ?? 0;
|
|
627
|
+
const failedCount = res?.failed ?? 0;
|
|
628
|
+
let msg;
|
|
629
|
+
|
|
630
|
+
if (res?.notFound) {
|
|
631
|
+
msg = `Warning: No push subscriptions found for client '${targetClientId}'. Ensure push notifications were registered by this client in ShadowClaw Settings.`;
|
|
632
|
+
} else if (res?.noSubscribers) {
|
|
633
|
+
msg =
|
|
634
|
+
"Warning: Push notification broadcast completed, but no devices are currently subscribed to push notifications. Ensure push notifications are enabled in ShadowClaw Settings on the client first.";
|
|
635
|
+
} else if (targetClientId) {
|
|
636
|
+
msg = `Push notification sent to client '${targetClientId}': ${sentCount} recipient(s) delivered, ${failedCount} failed.`;
|
|
637
|
+
} else {
|
|
638
|
+
msg = `Push notification broadcast sent: ${sentCount} recipient(s) delivered, ${failedCount} failed.`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
jsonrpc: "2.0",
|
|
643
|
+
id: reqId,
|
|
644
|
+
result: {
|
|
645
|
+
resultType: "complete",
|
|
646
|
+
isError: false,
|
|
647
|
+
content: [{ type: "text", text: msg }],
|
|
648
|
+
_meta: {
|
|
649
|
+
...res,
|
|
650
|
+
...(targetClientId ? { targetClientId } : {}),
|
|
651
|
+
"io.modelcontextprotocol/serverInfo": serverInfo,
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
} catch (err) {
|
|
656
|
+
return {
|
|
657
|
+
jsonrpc: "2.0",
|
|
658
|
+
id: reqId,
|
|
659
|
+
result: {
|
|
660
|
+
resultType: "complete",
|
|
661
|
+
isError: true,
|
|
662
|
+
content: [
|
|
663
|
+
{
|
|
664
|
+
type: "text",
|
|
665
|
+
text: `Failed to send push notification: ${err.message}`,
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
_meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
|
|
669
|
+
},
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
544
674
|
// Relayed interactive ask_user tool with MRTR response fulfillment if provided
|
|
545
675
|
if (
|
|
546
676
|
toolName === "ask_user" &&
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
1
4
|
import {
|
|
2
5
|
describe,
|
|
3
6
|
it,
|
|
@@ -8,6 +11,11 @@ import {
|
|
|
8
11
|
} from "@jest/globals";
|
|
9
12
|
import { createCliMcpEngine } from "./mcp.mjs";
|
|
10
13
|
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const pkg = JSON.parse(
|
|
16
|
+
fs.readFileSync(path.resolve(__dirname, "../../package.json"), "utf8"),
|
|
17
|
+
);
|
|
18
|
+
|
|
11
19
|
describe("CLI MCP Engine (createCliMcpEngine)", () => {
|
|
12
20
|
let errorSpy;
|
|
13
21
|
|
|
@@ -41,6 +49,7 @@ describe("CLI MCP Engine (createCliMcpEngine)", () => {
|
|
|
41
49
|
expect(res.id).toBe(1);
|
|
42
50
|
expect(res.result.protocolVersion).toBe("2026-07-28");
|
|
43
51
|
expect(res.result.serverInfo.name).toBe("shadow-claw");
|
|
52
|
+
expect(res.result.serverInfo.version).toBe(pkg.version);
|
|
44
53
|
});
|
|
45
54
|
|
|
46
55
|
it("processes tools/list including built-in and client tools", async () => {
|
|
@@ -82,7 +91,164 @@ describe("CLI MCP Engine (createCliMcpEngine)", () => {
|
|
|
82
91
|
const toolNames = res.result.tools.map((t) => t.name);
|
|
83
92
|
expect(toolNames).toContain("shadowclaw_list_clients");
|
|
84
93
|
expect(toolNames).toContain("shadowclaw_send_message");
|
|
94
|
+
expect(toolNames).toContain("shadowclaw_send_notification");
|
|
85
95
|
expect(toolNames).toContain("read_file");
|
|
96
|
+
|
|
97
|
+
const notifTool = res.result.tools.find(
|
|
98
|
+
(t) => t.name === "shadowclaw_send_notification",
|
|
99
|
+
);
|
|
100
|
+
expect(notifTool.inputSchema.properties).toHaveProperty("clientId");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("processes tools/call for shadowclaw_send_notification directly via broadcastNotification", async () => {
|
|
104
|
+
const mockClient = {
|
|
105
|
+
listClients: jest.fn().mockResolvedValue([]),
|
|
106
|
+
broadcastNotification: jest.fn().mockResolvedValue({
|
|
107
|
+
sent: 2,
|
|
108
|
+
failed: 0,
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
113
|
+
const res = await engine.handleMessage({
|
|
114
|
+
jsonrpc: "2.0",
|
|
115
|
+
id: 30,
|
|
116
|
+
method: "tools/call",
|
|
117
|
+
params: {
|
|
118
|
+
name: "shadowclaw_send_notification",
|
|
119
|
+
arguments: { title: "Custom Alert", body: "Server push message" },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(res).toBeDefined();
|
|
124
|
+
expect(res.result.resultType).toBe("complete");
|
|
125
|
+
expect(res.result.isError).toBe(false);
|
|
126
|
+
expect(res.result.content[0].text).toContain(
|
|
127
|
+
"2 recipient(s) delivered, 0 failed",
|
|
128
|
+
);
|
|
129
|
+
expect(mockClient.broadcastNotification).toHaveBeenCalledWith({
|
|
130
|
+
title: "Custom Alert",
|
|
131
|
+
body: "Server push message",
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("processes tools/call for send_notification alias directly via broadcastNotification", async () => {
|
|
136
|
+
const mockClient = {
|
|
137
|
+
listClients: jest.fn().mockResolvedValue([]),
|
|
138
|
+
broadcastNotification: jest.fn().mockResolvedValue({
|
|
139
|
+
sent: 1,
|
|
140
|
+
failed: 0,
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
145
|
+
const res = await engine.handleMessage({
|
|
146
|
+
jsonrpc: "2.0",
|
|
147
|
+
id: 31,
|
|
148
|
+
method: "tools/call",
|
|
149
|
+
params: {
|
|
150
|
+
name: "send_notification",
|
|
151
|
+
arguments: { body: "Fallback push" },
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(res).toBeDefined();
|
|
156
|
+
expect(res.result.resultType).toBe("complete");
|
|
157
|
+
expect(res.result.isError).toBe(false);
|
|
158
|
+
expect(mockClient.broadcastNotification).toHaveBeenCalledWith({
|
|
159
|
+
title: "ShadowClaw",
|
|
160
|
+
body: "Fallback push",
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("handles empty body validation for shadowclaw_send_notification", async () => {
|
|
165
|
+
const mockClient = {
|
|
166
|
+
listClients: jest.fn().mockResolvedValue([]),
|
|
167
|
+
broadcastNotification: jest.fn(),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
171
|
+
const res = await engine.handleMessage({
|
|
172
|
+
jsonrpc: "2.0",
|
|
173
|
+
id: 32,
|
|
174
|
+
method: "tools/call",
|
|
175
|
+
params: {
|
|
176
|
+
name: "shadowclaw_send_notification",
|
|
177
|
+
arguments: { body: "" },
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(res).toBeDefined();
|
|
182
|
+
expect(res.result.isError).toBe(true);
|
|
183
|
+
expect(res.result.content[0].text).toContain("cannot be empty");
|
|
184
|
+
expect(mockClient.broadcastNotification).not.toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("targets a specific client in shadowclaw_send_notification", async () => {
|
|
188
|
+
const mockClient = {
|
|
189
|
+
listClients: jest.fn().mockResolvedValue([]),
|
|
190
|
+
broadcastNotification: jest.fn().mockResolvedValue({
|
|
191
|
+
sent: 1,
|
|
192
|
+
failed: 0,
|
|
193
|
+
}),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
197
|
+
const res = await engine.handleMessage({
|
|
198
|
+
jsonrpc: "2.0",
|
|
199
|
+
id: 33,
|
|
200
|
+
method: "tools/call",
|
|
201
|
+
params: {
|
|
202
|
+
name: "shadowclaw_send_notification",
|
|
203
|
+
arguments: {
|
|
204
|
+
title: "Direct Alert",
|
|
205
|
+
body: "Direct push message",
|
|
206
|
+
clientId: "client-target-99",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(res).toBeDefined();
|
|
212
|
+
expect(res.result.isError).toBe(false);
|
|
213
|
+
expect(res.result.content[0].text).toContain(
|
|
214
|
+
"Push notification sent to client 'client-target-99': 1 recipient(s) delivered, 0 failed.",
|
|
215
|
+
);
|
|
216
|
+
expect(mockClient.broadcastNotification).toHaveBeenCalledWith({
|
|
217
|
+
title: "Direct Alert",
|
|
218
|
+
body: "Direct push message",
|
|
219
|
+
clientId: "client-target-99",
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("handles notFound warning when targeted client has no push subscriptions", async () => {
|
|
224
|
+
const mockClient = {
|
|
225
|
+
listClients: jest.fn().mockResolvedValue([]),
|
|
226
|
+
broadcastNotification: jest.fn().mockResolvedValue({
|
|
227
|
+
sent: 0,
|
|
228
|
+
failed: 0,
|
|
229
|
+
notFound: true,
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
234
|
+
const res = await engine.handleMessage({
|
|
235
|
+
jsonrpc: "2.0",
|
|
236
|
+
id: 34,
|
|
237
|
+
method: "tools/call",
|
|
238
|
+
params: {
|
|
239
|
+
name: "shadowclaw_send_notification",
|
|
240
|
+
arguments: {
|
|
241
|
+
body: "Alert",
|
|
242
|
+
clientId: "missing-client-id",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(res).toBeDefined();
|
|
248
|
+
expect(res.result.isError).toBe(false);
|
|
249
|
+
expect(res.result.content[0].text).toContain(
|
|
250
|
+
"No push subscriptions found for client 'missing-client-id'",
|
|
251
|
+
);
|
|
86
252
|
});
|
|
87
253
|
|
|
88
254
|
it("processes tools/call for shadowclaw_send_message", async () => {
|
|
@@ -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
|
});
|
package/bin/version.mjs
ADDED
|
@@ -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
|
+
}
|