diff --git a/.config/fish/functions/prefixed-function.fish b/.config/fish/functions/prefixed-function.fish
new file mode 100644
index 0000000..787526d
--- /dev/null
+++ b/.config/fish/functions/prefixed-function.fish
@@ -0,0 +1,38 @@
+function run
+  eval "$argv[1]-$argv[2]"
+end
+
+function usage
+  set fn $argv[1]
+  set allowed $argv[2..]
+  echo "Usage: $fn <CMD>"
+  echo "       $fn do <CMD>[, <CMD>]"
+  echo "  <CMD>:" (string join ", " $allowed)
+end
+
+function available-cmds
+  string replace --filter --regex "^$argv[1]-" "" (functions --name)
+end
+
+function prefixed-function
+  set fn $argv[1]
+  set allowed (available-cmds $fn)
+
+  if test $argv[2] = "do"
+    set cmds (string replace -a ',' '' $argv[3..])
+  else
+    set cmds $argv[2]
+  end
+
+  for cmd in $cmds
+    if not contains $cmd $allowed
+      echo "Error: Unknown subcommand '$cmd'"
+      usage $fn $allowed
+      return 1
+    end
+  end
+
+  for cmd in $cmds
+    run $fn $cmd
+  end
+end
diff --git a/.config/fish/functions/vitally.fish b/.config/fish/functions/vitally.fish
index fbf7698..aaec404 100644
--- a/.config/fish/functions/vitally.fish
+++ b/.config/fish/functions/vitally.fish
@@ -1,18 +1,3 @@
 function vitally
-  set cmd $argv[1]
-
-  if not contains $cmd start stop session-name init do
-    echo "usage: vitally <CMD>; <CMD>: start, stop, session-name, init"
-    return 1
-  end
-
-  if test $cmd = "do"
-    set cmds (string replace -a ',' '' $argv[2..])
-  else
-    set cmds $cmd
-  end
-
-  for c in $cmds
-    eval "vitally-$c"
-  end
+  prefixed-function vitally $argv[1..]
 end