diff --git a/CalamityCommands/Command.hs b/CalamityCommands/Command.hs
--- a/CalamityCommands/Command.hs
+++ b/CalamityCommands/Command.hs
@@ -6,6 +6,7 @@
 import CalamityCommands.Check
 import CalamityCommands.Error
 import CalamityCommands.Group
+import CalamityCommands.ParameterInfo
 
 import Control.Lens hiding (Context, (<.>))
 
@@ -29,9 +30,8 @@
     hidden :: Bool
   , -- | A list of checks that must pass for this command to be invoked
     checks :: [Check m c]
-  , -- | A list of the parameters the command takes, only used for constructing
-    -- help messages.
-    params :: [S.Text]
+  , -- | A list of parameter metadata
+    params :: [ParameterInfo]
   , -- | A function producing the \'help\' for the command.
     help :: c -> L.Text
   , -- | A function that parses the context for the command, producing the input
@@ -44,7 +44,7 @@
 
 data CommandS = CommandS
   { names :: NonEmpty S.Text
-  , params :: [S.Text]
+  , params :: [ParameterInfo]
   , parent :: Maybe S.Text
   , checks :: [S.Text]
   , hidden :: Bool
diff --git a/CalamityCommands/CommandUtils.hs b/CalamityCommands/CommandUtils.hs
--- a/CalamityCommands/CommandUtils.hs
+++ b/CalamityCommands/CommandUtils.hs
@@ -20,6 +20,7 @@
 import CalamityCommands.Group
 import CalamityCommands.Internal.RunIntoM
 import CalamityCommands.Internal.Utils
+import CalamityCommands.ParameterInfo
 import CalamityCommands.Parser
 
 import Control.Lens hiding (Context, (<.>))
@@ -30,8 +31,8 @@
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe
-import Data.Text as S
-import Data.Text.Lazy as L
+import qualified Data.Text as S
+import qualified Data.Text.Lazy as L
 
 import qualified Polysemy as P
 import qualified Polysemy.Error as P
@@ -45,7 +46,14 @@
 
 -- | Format a command's parameters
 commandParams :: Command m c a -> L.Text
-commandParams Command{params} = L.fromStrict $ S.intercalate ", " params
+commandParams Command{params} =
+  let formatted =
+        map
+          ( \(ParameterInfo (fromMaybe "" -> name) type_ _) ->
+              "`" <> name <> ":" <> S.pack (show type_) <> "`"
+          )
+          params
+   in L.fromStrict $ S.intercalate ", " formatted
 
 {- | Given the properties of a 'Command' with the @parser@ and @callback@ in the
  'P.Sem' monad, build a command by transforming the Polysemy actions into @m@
@@ -62,8 +70,8 @@
   Bool ->
   -- | The checks for the command
   [Check m c] ->
-  -- | The names of the command's parameters
-  [S.Text] ->
+  -- | The command's parameter metadata
+  [ParameterInfo] ->
   -- | The help generator for this command
   (c -> L.Text) ->
   -- | The parser for this command
@@ -92,7 +100,7 @@
 -}
 buildCommand ::
   forall ps c m a r.
-  (Monad m, P.Member (P.Final m) r, TypedCommandC ps a r, CommandContext m c a) =>
+  (Monad m, P.Member (P.Final m) r, TypedCommandC ps c a r, CommandContext m c a) =>
   -- | The name (and aliases) of the command
   NonEmpty S.Text ->
   -- | The parent group of the command
@@ -108,7 +116,7 @@
   P.Sem r (Command m c a)
 buildCommand names parent hidden checks help command =
   let (parser, cb) = buildTypedCommand @ps command
-   in buildCommand' names parent hidden checks (paramNames @ps @r) help parser cb
+   in buildCommand' names parent hidden checks (parameterInfos @ps @r) help parser cb
 
 {- | Given the name of the command the parser is for and a parser function in
  the 'P.Sem' monad, build a parser by transforming the Polysemy action into an
@@ -130,7 +138,7 @@
 buildCallback ::
   (Monad m, P.Member (P.Final m) r) => ((c, p) -> P.Sem (P.Fail ': r) a) -> P.Sem r ((c, p) -> m (Either L.Text a))
 buildCallback cb = do
-  cb' <- bindSemToM ( \x -> P.runFail (cb x) <&> mapLeft L.pack)
+  cb' <- bindSemToM (\x -> P.runFail (cb x) <&> mapLeft L.pack)
   let cb'' = fromMaybe (Left "failed internally") <.> cb'
   pure cb''
 
@@ -152,16 +160,16 @@
 type CommandSemType r a = P.Sem (P.Fail ': r) a
 
 -- | Some constraints used for making parameter typed commands work
-type TypedCommandC ps a r =
+type TypedCommandC ps c a r =
   ( ApplyTupRes (ParserResult (ListToTup ps)) (CommandSemType r a) ~ CommandForParsers ps r a
-  , ParameterParser (ListToTup ps) r
+  , ParameterParser (ListToTup ps) c r
   , ApplyTup (ParserResult (ListToTup ps)) (CommandSemType r a)
-  , ParamNamesForParsers ps r
+  , ParameterInfoForParsers ps r
   )
 
 buildTypedCommand ::
   forall (ps :: [Type]) c m a p r.
-  (TypedCommandC ps a r, p ~ ParserResult (ListToTup ps), CommandContext m c a) =>
+  (TypedCommandC ps c a r, p ~ ParserResult (ListToTup ps), CommandContext m c a) =>
   (c -> CommandForParsers ps r a) ->
   ( c -> P.Sem r (Either CommandError p)
   , (c, p) -> P.Sem (P.Fail ': r) a
@@ -171,14 +179,14 @@
       consumer (ctx, r) = applyTup (cmd ctx) r
    in (parser, consumer)
 
-class ParamNamesForParsers (ps :: [Type]) r where
-  paramNames :: [S.Text]
+class ParameterInfoForParsers (ps :: [Type]) r where
+  parameterInfos :: [ParameterInfo]
 
-instance ParamNamesForParsers '[] r where
-  paramNames = []
+instance ParameterInfoForParsers '[] r where
+  parameterInfos = []
 
-instance (ParameterParser x r, ParamNamesForParsers xs r) => ParamNamesForParsers (x : xs) r where
-  paramNames = parserName @x @r : paramNames @xs @r
+instance (ParameterParser x c r, ParameterInfoForParsers xs r) => ParameterInfoForParsers (x : xs) r where
+  parameterInfos = parameterInfo @x @c @r : parameterInfos @xs @r
 
 class ApplyTup a b where
   type ApplyTupRes a b
@@ -197,12 +205,12 @@
 
 buildTypedCommandParser ::
   forall (ps :: [Type]) c r.
-  ParameterParser (ListToTup ps) r =>
+  ParameterParser (ListToTup ps) c r =>
   c ->
   L.Text ->
   P.Sem r (Either CommandError (ParserResult (ListToTup ps)))
 buildTypedCommandParser ctx t =
-  runCommandParser ctx t (parse @(ListToTup ps) @r) <&> \case
+  runCommandParser ctx t (parse @(ListToTup ps) @c @r) <&> \case
     Right r -> Right r
     Left (n, e) -> Left $ ParseError n e
 
@@ -210,7 +218,7 @@
   ListToTup '[] = ()
   ListToTup (x ': xs) = (x, ListToTup xs)
 
-{- | Transform a type level list of types implementing the 'Parser' typeclass into
+{- | Transform a type level list of types implementing the 'ParameterParser' typeclass into
  the type a command callback matching those parameters should be.
 
  As an example:
diff --git a/CalamityCommands/Dsl.hs b/CalamityCommands/Dsl.hs
--- a/CalamityCommands/Dsl.hs
+++ b/CalamityCommands/Dsl.hs
@@ -30,6 +30,7 @@
 import CalamityCommands.Error
 import CalamityCommands.Group hiding (help)
 import CalamityCommands.Handler
+import CalamityCommands.ParameterInfo
 import CalamityCommands.Internal.LocalWriter
 
 import qualified Data.HashMap.Lazy as LH
@@ -103,8 +104,8 @@
     (Monad m, P.Member (P.Final m) r) =>
     -- | The name of the command
     S.Text ->
-    -- | The names of the command's parameters
-    [S.Text] ->
+    -- | The command's parameter metadata
+    [ParameterInfo] ->
     -- | The parser for this command
     (c -> P.Sem r (Either CommandError p)) ->
     -- | The callback for this command
@@ -126,8 +127,8 @@
     S.Text ->
     -- | The aliases for the command
     [S.Text] ->
-    -- | The names of the command's parameters
-    [S.Text] ->
+    -- | The command's parameter metadata
+    [ParameterInfo] ->
     -- | The parser for this command
     (c -> P.Sem r (Either CommandError p)) ->
     -- | The callback for this command
@@ -167,7 +168,7 @@
     forall ps c a m r.
     ( Monad m
     , P.Member (P.Final m) r
-    , TypedCommandC ps a r
+    , TypedCommandC ps c a r
     , CommandContext m c a
     ) =>
     -- | The name of the command
@@ -197,7 +198,7 @@
     forall ps c a m r.
     ( Monad m
     , P.Member (P.Final m) r
-    , TypedCommandC ps a r
+    , TypedCommandC ps c a r
     , CommandContext m c a
     ) =>
     -- | The name of the command
diff --git a/CalamityCommands/Help.hs b/CalamityCommands/Help.hs
--- a/CalamityCommands/Help.hs
+++ b/CalamityCommands/Help.hs
@@ -12,6 +12,7 @@
 import CalamityCommands.Dsl
 import CalamityCommands.Group
 import CalamityCommands.Handler
+import CalamityCommands.ParameterInfo
 import CalamityCommands.Internal.LocalWriter
 
 import Control.Applicative
@@ -32,16 +33,22 @@
   = Command' (Command m c a)
   | Group' (Group m c a) [S.Text]
 
+parameterTypeHelp :: [ParameterInfo] -> L.Text
+parameterTypeHelp pinfo =
+  let dedup = LH.toList . LH.fromList $ map (\(ParameterInfo _ t d) -> (t, d)) pinfo
+      typeDescs = L.unlines ["- " <> L.pack (show t) <> ": " <> L.fromStrict d | (t, d) <- dedup]
+  in "Types:\n" <> typeDescs <> "\n"
+
 helpCommandHelp :: c -> L.Text
 helpCommandHelp _ = "Show help for a command or group."
 
 helpForCommand :: CommandContext m c a => c -> Command m c a -> L.Text
-helpForCommand ctx cmd@Command{names, checks, help} =
-  "```\nUsage: " <> prefix' <> path' <> " " <> params' <> "\n"
+helpForCommand ctx cmd@Command{names, checks, help, params} =
+  "Usage: " <> prefix' <> path' <> " " <> params' <> "\n"
     <> aliasesFmt
     <> checksFmt
+    <> parameterTypeHelp params
     <> help ctx
-    <> "\n```"
  where
   prefix' = ctxPrefix ctx
   path' = L.unwords . map L.fromStrict $ commandPath cmd
@@ -81,14 +88,13 @@
 
 helpForGroup :: CommandContext m c a => c -> Group m c a -> L.Text
 helpForGroup ctx grp =
-  "```\nGroup: " <> path' <> "\n"
+  "Group: " <> path' <> "\n"
     <> aliasesFmt
     <> checksFmt
     <> (grp ^. #help) ctx
     <> "\n"
     <> groupsMsg
     <> commandsMsg
-    <> "\n```"
  where
   path' = L.fromStrict . S.unwords $ groupPath grp
   groups = onlyVisibleG . onlyOriginals . LH.elems $ grp ^. #children
@@ -114,7 +120,7 @@
       else "Checks: " <> L.unwords checks' <> "\n\n"
 
 rootHelp :: CommandHandler m c a -> L.Text
-rootHelp handler = "```\n" <> groupsMsg <> commandsMsg <> "\n```"
+rootHelp handler = groupsMsg <> commandsMsg
  where
   groups = onlyVisibleG . onlyOriginals . LH.elems $ handler ^. #groups
   commands = onlyVisibleC . onlyOriginals . LH.elems $ handler ^. #commands
@@ -155,7 +161,7 @@
   CommandHandler m c a ->
   Maybe (Group m c a) ->
   [Check m c] ->
-  (L.Text -> P.Sem (P.Fail ': r) a) ->
+  (c -> L.Text -> P.Sem (P.Fail ': r) a) ->
   P.Sem r (Command m c a)
 helpCommand' handler parent checks render =
   buildCommand @'[[S.Text]]
@@ -164,7 +170,7 @@
     False
     checks
     helpCommandHelp
-    (\ctx path -> render $ renderHelp handler ctx path)
+    (\ctx path -> render ctx $ renderHelp handler ctx path)
 
 {- | Create and register the default help command for all the commands registered
  in the commands DSL this is used in. The @render@ parameter is used so you can
@@ -217,7 +223,7 @@
 helpCommand ::
   forall c m a r.
   (Monad m, P.Member (P.Final m) r, CommandContext m c a) =>
-  (L.Text -> P.Sem (P.Fail ': r) a) ->
+  (c -> L.Text -> P.Sem (P.Fail ': r) a) ->
   P.Sem (DSLState m c a r) (Command m c a)
 helpCommand render = do
   handler <- P.ask @(CommandHandler m c a)
diff --git a/CalamityCommands/ParameterInfo.hs b/CalamityCommands/ParameterInfo.hs
new file mode 100644
--- /dev/null
+++ b/CalamityCommands/ParameterInfo.hs
@@ -0,0 +1,19 @@
+-- | Parameter info for a command
+module CalamityCommands.ParameterInfo (
+    ParameterInfo (..),
+) where
+
+import qualified Data.Text as S
+import Data.Typeable
+
+import GHC.Generics (Generic)
+import TextShow
+import qualified TextShow.Generic as TSG
+
+data ParameterInfo = ParameterInfo
+    { name :: Maybe S.Text
+    , type_ :: TypeRep
+    , typeDescription :: S.Text
+    }
+    deriving (Show, Generic)
+    deriving (TextShow) via TSG.FromGeneric ParameterInfo
diff --git a/CalamityCommands/Parser.hs b/CalamityCommands/Parser.hs
--- a/CalamityCommands/Parser.hs
+++ b/CalamityCommands/Parser.hs
@@ -4,12 +4,17 @@
   Named,
   KleeneStarConcat,
   KleenePlusConcat,
+
+  -- * Parameter parsing utilities
   ParserEffs,
   runCommandParser,
   ParserState (..),
   parseMP,
+  SpannedError (..),
 ) where
 
+import CalamityCommands.ParameterInfo
+
 import Control.Lens hiding (Context)
 import Control.Monad
 
@@ -17,6 +22,7 @@
 import Data.Generics.Labels ()
 import Data.Kind
 import Data.List.NonEmpty (NonEmpty (..))
+import Data.Maybe (fromMaybe)
 import Data.Semigroup
 import qualified Data.Text as S
 import qualified Data.Text.Lazy as L
@@ -38,9 +44,6 @@
 data SpannedError = SpannedError L.Text !Int !Int
   deriving (Show, Eq, Ord)
 
-showTypeOf :: forall a. Typeable a => String
-showTypeOf = show . typeRep $ Proxy @a
-
 {- | The current state of the parser, used so that the entire remaining input is
  available.
 
@@ -56,32 +59,35 @@
   }
   deriving (Show, Generic)
 
+-- |
 type ParserEffs c r =
   ( P.State ParserState
-      ': P.Error (S.Text, L.Text) -- the current parser state
-        ': P.Reader c -- (failing parser name, error reason)
+      ': P.Error (S.Text, L.Text) -- (failing parser name, error reason)
+        ': P.Reader c -- the current parser state
           ': r -- context
   )
-type ParserCtxE c r = P.Reader c ': r
 
 -- | Run a command parser, @ctx@ is the context, @t@ is the text input
 runCommandParser :: c -> L.Text -> P.Sem (ParserEffs c r) a -> P.Sem r (Either (S.Text, L.Text) a)
 runCommandParser ctx t = P.runReader ctx . P.runError . P.evalState (ParserState 0 t)
 
--- | A typeclass for things that can be parsed as parameters to commands.
---
--- Any type that is an instance of ParamerParser can be used in the type level
--- parameter @ps@ of 'CalamityCommands.Dsl.command',
--- 'CalamityCommands.CommandUtils.buildCommand', etc.
-class Typeable a => ParameterParser (a :: Type) r where
+{- | A typeclass for things that can be parsed as parameters to commands.
+
+ Any type that is an instance of ParamerParser can be used in the type level
+ parameter @ps@ of 'CalamityCommands.Dsl.command',
+ 'CalamityCommands.CommandUtils.buildCommand', etc.
+-}
+class Typeable a => ParameterParser (a :: Type) c r where
   type ParserResult a
 
   type ParserResult a = a
 
-  parserName :: S.Text
-  default parserName :: S.Text
-  parserName = ":" <> S.pack (showTypeOf @a)
+  parameterInfo :: ParameterInfo
+  default parameterInfo :: ParameterInfo
+  parameterInfo = ParameterInfo Nothing (typeRep $ Proxy @a) (parameterDescription @a @c @r)
 
+  parameterDescription :: S.Text
+
   parse :: P.Sem (ParserEffs c r) (ParserResult a)
 
 {- | A named parameter, used to attach the name @s@ to a type in the command's
@@ -89,20 +95,35 @@
 -}
 data Named (s :: Symbol) (a :: Type)
 
-instance (KnownSymbol s, ParameterParser a r) => ParameterParser (Named s a) r where
+instance (KnownSymbol s, ParameterParser a c r) => ParameterParser (Named s a) c r where
   type ParserResult (Named s a) = ParserResult a
 
-  parserName = (S.pack . symbolVal $ Proxy @s) <> parserName @a @r
+  parameterInfo =
+    let ParameterInfo _ type_ typeDescription = parameterInfo @a @c @r
+     in ParameterInfo (Just . S.pack . symbolVal $ Proxy @s) type_ typeDescription
 
-  parse = mapE (_1 .~ parserName @(Named s a) @r) $ parse @a @r
+  parameterDescription = parameterDescription @a @c @r
 
+  parse = mapE (_1 .~ parserName @(Named s a) @c @r) $ parse @a @c @r
+
+parserName :: forall a c r. ParameterParser a c r => S.Text
+parserName =
+  let ParameterInfo (fromMaybe "" -> name) type_ _ = parameterInfo @a @c @r
+   in name <> ":" <> S.pack (show type_)
+
 mapE :: P.Member (P.Error e) r => (e -> e) -> P.Sem r a -> P.Sem r a
 mapE f m = P.catch m (P.throw . f)
 
--- | Parse a paremeter using a MegaParsec parser.
---
--- On failure this constructs a nice-looking megaparsec error for the failed parameter.
-parseMP :: S.Text -> ParsecT SpannedError L.Text (P.Sem (ParserCtxE c r)) a -> P.Sem (ParserEffs c r) a
+{- | Parse a paremeter using a MegaParsec parser.
+
+ On failure this constructs a nice-looking megaparsec error for the failed parameter.
+-}
+parseMP ::
+  -- | The name of the parser
+  S.Text ->
+  -- | The megaparsec parser
+  ParsecT SpannedError L.Text (P.Sem (P.Reader c ': r)) a ->
+  P.Sem (ParserEffs c r) a
 parseMP n m = do
   s <- P.get
   res <- P.raise . P.raise $ runParserT (skipN (s ^. #off) *> trackOffsets (space *> m)) "" (s ^. #msg)
@@ -112,43 +133,52 @@
       pure a
     Left s -> P.throw (n, L.pack $ errorBundlePretty s)
 
-instance ParameterParser L.Text r where
+instance ParameterParser L.Text c r where
   parse = parseMP (parserName @L.Text) item
+  parameterDescription = "word or quoted string"
 
-instance ParameterParser S.Text r where
+instance ParameterParser S.Text c r where
   parse = parseMP (parserName @S.Text) (L.toStrict <$> item)
+  parameterDescription = "word or quoted string"
 
-instance ParameterParser Integer r where
+instance ParameterParser Integer c r where
   parse = parseMP (parserName @Integer) (signed mempty decimal)
+  parameterDescription = "number"
 
-instance ParameterParser Natural r where
+instance ParameterParser Natural c r where
   parse = parseMP (parserName @Natural) decimal
+  parameterDescription = "number"
 
-instance ParameterParser Int r where
+instance ParameterParser Int c r where
   parse = parseMP (parserName @Int) (signed mempty decimal)
+  parameterDescription = "number"
 
-instance ParameterParser Word r where
+instance ParameterParser Word c r where
   parse = parseMP (parserName @Word) decimal
+  parameterDescription = "number"
 
-instance ParameterParser Float r where
+instance ParameterParser Float c r where
   parse = parseMP (parserName @Float) (signed mempty (try float <|> decimal))
+  parameterDescription = "number"
 
-instance ParameterParser a r => ParameterParser (Maybe a) r where
+instance ParameterParser a c r => ParameterParser (Maybe a) c r where
   type ParserResult (Maybe a) = Maybe (ParserResult a)
 
   parse = P.catch (Just <$> parse @a) (const $ pure Nothing)
+  parameterDescription = "optional " <> parameterDescription @a @c @r
 
-instance (ParameterParser a r, ParameterParser b r) => ParameterParser (Either a b) r where
+instance (ParameterParser a c r, ParameterParser b c r) => ParameterParser (Either a b) c r where
   type ParserResult (Either a b) = Either (ParserResult a) (ParserResult b)
 
   parse = do
-    l <- parse @(Maybe a) @r
+    l <- parse @(Maybe a) @c @r
     case l of
       Just l' -> pure (Left l')
       Nothing ->
-        Right <$> parse @b @r
+        Right <$> parse @b @c @r
+  parameterDescription = "either '" <> parameterDescription @a @c @r <> "' or '" <> parameterDescription @b @c @r <> "'"
 
-instance ParameterParser a r => ParameterParser [a] r where
+instance ParameterParser a c r => ParameterParser [a] c r where
   type ParserResult [a] = [ParserResult a]
 
   parse = go []
@@ -159,7 +189,9 @@
         Just a -> go $ l <> [a]
         Nothing -> pure l
 
-instance (ParameterParser a r, Typeable a) => ParameterParser (NonEmpty a) r where
+  parameterDescription = "zero or more '" <> parameterDescription @a @c @r <> "'"
+
+instance (ParameterParser a c r, Typeable a) => ParameterParser (NonEmpty a) c r where
   type ParserResult (NonEmpty a) = NonEmpty (ParserResult a)
 
   parse = do
@@ -167,28 +199,33 @@
     as <- parse @[a]
     pure $ a :| as
 
+  parameterDescription = "one or more '" <> parameterDescription @a @c @r <> "'"
+
 {- | A parser that consumes zero or more of @a@ then concatenates them together.
 
  @'KleeneStarConcat' 'L.Text'@ therefore consumes all remaining input.
 -}
 data KleeneStarConcat (a :: Type)
 
-instance (Monoid (ParserResult a), ParameterParser a r) => ParameterParser (KleeneStarConcat a) r where
+instance (Monoid (ParserResult a), ParameterParser a c r) => ParameterParser (KleeneStarConcat a) c r where
   type ParserResult (KleeneStarConcat a) = ParserResult a
 
   parse = mconcat <$> parse @[a]
+  parameterDescription = "zero or more '" <> parameterDescription @a @c @r <> "'"
 
-instance {-# OVERLAPS #-} ParameterParser (KleeneStarConcat L.Text) r where
+instance {-# OVERLAPS #-} ParameterParser (KleeneStarConcat L.Text) c r where
   type ParserResult (KleeneStarConcat L.Text) = ParserResult L.Text
 
   -- consume rest on text just takes everything remaining
   parse = parseMP (parserName @(KleeneStarConcat L.Text)) manySingle
+  parameterDescription = "the remaining input"
 
-instance {-# OVERLAPS #-} ParameterParser (KleeneStarConcat S.Text) r where
+instance {-# OVERLAPS #-} ParameterParser (KleeneStarConcat S.Text) c r where
   type ParserResult (KleeneStarConcat S.Text) = ParserResult S.Text
 
   -- consume rest on text just takes everything remaining
   parse = parseMP (parserName @(KleeneStarConcat S.Text)) (L.toStrict <$> manySingle)
+  parameterDescription = "the remaining input"
 
 {- | A parser that consumes one or more of @a@ then concatenates them together.
 
@@ -196,33 +233,38 @@
 -}
 data KleenePlusConcat (a :: Type)
 
-instance (Semigroup (ParserResult a), ParameterParser a r) => ParameterParser (KleenePlusConcat a) r where
+instance (Semigroup (ParserResult a), ParameterParser a c r) => ParameterParser (KleenePlusConcat a) c r where
   type ParserResult (KleenePlusConcat a) = ParserResult a
 
   parse = sconcat <$> parse @(NonEmpty a)
+  parameterDescription = "one or more '" <> parameterDescription @a @c @r <> "'"
 
-instance {-# OVERLAPS #-} ParameterParser (KleenePlusConcat L.Text) r where
+instance {-# OVERLAPS #-} ParameterParser (KleenePlusConcat L.Text) c r where
   type ParserResult (KleenePlusConcat L.Text) = ParserResult L.Text
 
   -- consume rest on text just takes everything remaining
   parse = parseMP (parserName @(KleenePlusConcat L.Text)) someSingle
+  parameterDescription = "the remaining input"
 
-instance {-# OVERLAPS #-} ParameterParser (KleenePlusConcat S.Text) r where
+instance {-# OVERLAPS #-} ParameterParser (KleenePlusConcat S.Text) c r where
   type ParserResult (KleenePlusConcat S.Text) = ParserResult S.Text
 
   -- consume rest on text just takes everything remaining
   parse = parseMP (parserName @(KleenePlusConcat S.Text)) (L.toStrict <$> someSingle)
+  parameterDescription = "the remaining input"
 
-instance (ParameterParser a r, ParameterParser b r) => ParameterParser (a, b) r where
+instance (ParameterParser a c r, ParameterParser b c r) => ParameterParser (a, b) c r where
   type ParserResult (a, b) = (ParserResult a, ParserResult b)
 
   parse = do
     a <- parse @a
     b <- parse @b
     pure (a, b)
+  parameterDescription = "'" <> parameterDescription @a @c @r <> "' then '" <> parameterDescription @b @c @r <> "'"
 
-instance ParameterParser () r where
+instance ParameterParser () c r where
   parse = parseMP (parserName @()) space
+  parameterDescription = "whitespace"
 
 instance ShowErrorComponent SpannedError where
   showErrorComponent (SpannedError t _ _) = L.unpack t
diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,23 @@
 # Changelog for Calamity Commands
 
+## 0.1.2.0
+
++ Changed the command parameter machinery to hold more info about parameters.
++ Added a 'type cheatsheet' thing to command help in the default help command.
++ The `render` parameter of `helpCommand` and `helpCommand'` is now passed the
+  context.
++ `ParameterParser` now has an extra type variable `c` to parameterise the
+  context type.
+
+## 0.1.1.0
+
++ The minimum version of `base` has been upped to `4.13` as the library fails to
+  build on ghc-8.6
++ The minimum version of `polysemy` has been upped to 1.5
++ The upper bound of `lens` has been bumped to 6
+
 ## 0.1.0.0
+
+Initial release of the library
 
 ## Unreleased changes
diff --git a/calamity-commands.cabal b/calamity-commands.cabal
--- a/calamity-commands.cabal
+++ b/calamity-commands.cabal
@@ -1,13 +1,13 @@
 cabal-version: 1.18
 
--- This file has been generated from package.yaml by hpack version 0.33.0.
+-- This file has been generated from package.yaml by hpack version 0.34.4.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: d17b097b7481b77c55ba13052f69950fec4cd0809ca7f49be4f52b1a6fc2ad28
+-- hash: 962098e7be30e39345aaa5a511635824c3654c1f199212a575271f3d7e6f5c60
 
 name:           calamity-commands
-version:        0.1.1.0
+version:        0.1.2.0
 synopsis:       A library for declaring, parsing, and invoking text-input based commands
 description:    Please see the README on GitHub at <https://github.com/simmsb/calamity#readme>
 category:       Utils
@@ -18,7 +18,8 @@
 copyright:      2020 Ben Simms
 license:        MIT
 license-file:   LICENSE
-tested-with:    GHC == 8.10.4
+tested-with:
+    GHC == 8.10.4
 build-type:     Simple
 extra-source-files:
     README.md
@@ -46,14 +47,64 @@
       CalamityCommands.Internal.LocalWriter
       CalamityCommands.Internal.RunIntoM
       CalamityCommands.Internal.Utils
+      CalamityCommands.ParameterInfo
       CalamityCommands.ParsePrefix
       CalamityCommands.Parser
       CalamityCommands.Utils
   other-modules:
       Paths_calamity_commands
   hs-source-dirs:
-      ./.
-  default-extensions: StrictData AllowAmbiguousTypes BlockArguments NoMonomorphismRestriction BangPatterns BinaryLiterals UndecidableInstances ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DoAndIfThenElse EmptyDataDecls ExistentialQuantification FlexibleContexts FlexibleInstances FunctionalDependencies GADTs DerivingVia DerivingStrategies GeneralizedNewtypeDeriving StandaloneDeriving DeriveAnyClass InstanceSigs KindSignatures LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns OverloadedStrings OverloadedLabels PartialTypeSignatures PatternGuards PolyKinds RankNTypes RecordWildCards ScopedTypeVariables TupleSections TypeFamilies TypeSynonymInstances ViewPatterns DuplicateRecordFields TypeOperators TypeApplications RoleAnnotations
+      ./
+  default-extensions:
+      StrictData
+      AllowAmbiguousTypes
+      BlockArguments
+      NoMonomorphismRestriction
+      BangPatterns
+      BinaryLiterals
+      UndecidableInstances
+      ConstraintKinds
+      DataKinds
+      DefaultSignatures
+      DeriveDataTypeable
+      DeriveFoldable
+      DeriveFunctor
+      DeriveGeneric
+      DeriveTraversable
+      DoAndIfThenElse
+      EmptyDataDecls
+      ExistentialQuantification
+      FlexibleContexts
+      FlexibleInstances
+      FunctionalDependencies
+      GADTs
+      DerivingVia
+      DerivingStrategies
+      GeneralizedNewtypeDeriving
+      StandaloneDeriving
+      DeriveAnyClass
+      InstanceSigs
+      KindSignatures
+      LambdaCase
+      MultiParamTypeClasses
+      MultiWayIf
+      NamedFieldPuns
+      OverloadedStrings
+      OverloadedLabels
+      PartialTypeSignatures
+      PatternGuards
+      PolyKinds
+      RankNTypes
+      RecordWildCards
+      ScopedTypeVariables
+      TupleSections
+      TypeFamilies
+      TypeSynonymInstances
+      ViewPatterns
+      DuplicateRecordFields
+      TypeOperators
+      TypeApplications
+      RoleAnnotations
   ghc-options: -fplugin=Polysemy.Plugin -funbox-strict-fields -Wall -fno-warn-name-shadowing
   build-depends:
       base >=4.13 && <5
@@ -61,8 +112,8 @@
     , lens >=4.18 && <6
     , megaparsec >=8 && <10
     , polysemy >=1.5 && <2
-    , polysemy-plugin >=0.3 && <0.4
+    , polysemy-plugin ==0.3.*
     , text >=1.2 && <2
     , text-show >=3.8 && <4
-    , unordered-containers >=0.2 && <0.3
+    , unordered-containers ==0.2.*
   default-language: Haskell2010
