What I use in my day-to-day work is basically something like this. I'd normally type the right command name in and go about that approach. As Steven pointed out, once the direction is set, most users are happy with it. (Only works in 2D):
(defun c:lx nil (CommandDirection (list "_.LINE" pause) ".Y"))
(defun c:ly nil (CommandDirection (list "_.LINE" pause) ".X"))
(defun c:cx (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_.COPY" ss "" "_M" pause) ".Y")))
(defun c:cy (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_.COPY" ss "" "_M" pause) ".X")))
(defun c:mx (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_MOVE" ss "" pause) ".Y")))
(defun c:my (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_MOVE" ss "" pause) ".X")))
(defun c:sx (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_STRETCH" ss "" pause) ".Y")))
(defun c:sy (/ ss) (if (setq ss (ssget "_:L")) (CommandDirection (list "_STRETCH" ss "" pause) ".X")))
;; Command Direction - Jonathan Handojo
;; Creates a command that constraints point prompts to X, Y or Z axis.
;; cmd - a list of strings to pass into the AutoCAD command function.
;; => The next prompt following the string list supplied must be a point selection.
;; dir - a string of either ".X", ".Y" or ".Z"
(defun CommandDirection (cmd dir)
(apply 'command cmd)
(while (not (zerop (getvar "cmdactive")))
(command dir "_non" "@0,0,0" pause)
)
(princ)
)