> but I haven't yet come up with a good system of combinators that can represent I/O and foreign functions properly
I do not believe that you should support either.
I/O could simply be the program arguments & the final reduction of the expression. The input being your mouse position and the output being a window shouldn't be the responsibility of your program.
Foreign functions are IMO a bad idea, especially if the goal is for the program to outlive you, you lose the guarantee if these functions depend on the environment. Why couldn't you copy/paste the code into your own project?
[0] - https://esolangs.org/wiki/Binary_lambda_calculus
[1] - https://esolangs.org/wiki/Dependently_Typed_Binary_Lambda_Ca...
Basically combinators for the pure parts and functions that get optimised much less enthusiastically for IO or FFI.