True has a value of 1 in Python. Bit-inverting (~) a binary 1 (...0001) gives you ...1110. Since negative integers are represented by two's compliment, that's a value of -2.
Logical and returns its left operand if it's false, the right operand otherwise. (True is never false, obviously.)
Bitwise &, on the other hand, works on the individual bits. ...0001 & ...1110 have no 1 bits in the same position so they're all zeros in the result.
I was just surprised that a numpy array with dtype=bool acts differently with literal bool
Each Python type can implement an operator's methods with special method names. (Like .__invert__() for ~). But and, or, and not don't have these hooks, so often &, |, and ~ are used instead. For ints the implementation is bitwise, but other types can interpret operators however they want.
Note that bool is a subclass of int in Python, so it has the same operators as int. But you were operating on a numpy.ndarray, not on its individual components, so Python uses the ndarray implementation of the operators, which are not the same as bool when dtype=bool.