Well to distinguish covectors you need to define something like a manifold or just any canonical mapping between the tangent and cotangent space and then when you define your first few tensors you define them in the scope of that mapping.
There is some generalization to gauge fields where a bijective canonical mapping may not be possible in general, but I never studied that extensively.
If you had reason to do so, it would be easy to bolt something onto the front of @tensor etc. which understood say A^[i]_[j] * B^[j,k] . Something similar would not be so hard to do to np.einsum either, just messing with the string... it could take exactly your "email Latex notation". In fact I'm a bit surprised that I can't find someone who's done this, in a few minutes' googling. I did find that opt-einsum has a notation in which the indices at least follow the array:
https://optimized-einsum.readthedocs.io/en/latest/input_form...
You have honestly piqued my curiosity towards Julia. I will need to find an opportunity to dig in.