diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## [2.5.0](https://github.com/haskell-github-trust/effectful-cli/compare/2.4.2...2.5.0) (2026-11-05)
+
+### Features
+
+* show whole command tree on help ([db5ad51](https://github.com/haskell-github-trust/effectful-cli/commit/db5ad51e5352c765bc73b292e8b2f004ebf44c9b))
+
 ## [2.4.2](https://github.com/haskell-github-trust/effectful-cli/compare/2.4.1...2.4.2) (2026-11-05)
 
 ### Bug Fixes
diff --git a/effectful-cli.cabal b/effectful-cli.cabal
--- a/effectful-cli.cabal
+++ b/effectful-cli.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           effectful-cli
-version:        0.2.4.2
+version:        0.2.5.0
 synopsis:       CLI helpers for the effectful ecosystem
 description:    Please see the README at https://github.com/haskell-github-trust/effectful-cli
 category:       CLI
diff --git a/src/Data/CLI/Impl.hs b/src/Data/CLI/Impl.hs
--- a/src/Data/CLI/Impl.hs
+++ b/src/Data/CLI/Impl.hs
@@ -14,6 +14,8 @@
   mkCommands,
   usage,
   usageWith,
+  usageTreeWith,
+  commandTree,
   HelpStyle (..),
   defaultHelpStyle,
   plainHelpStyle,
@@ -26,22 +28,31 @@
 import Data.CLI.Class (Command (..))
 import Data.Char (toUpper)
 import Data.List (sortOn)
+import Data.Maybe (fromMaybe)
 import Data.UUID (UUID)
 import Data.UUID qualified as UUID
 import Data.Void (Void)
 import Options.Applicative
+import Options.Applicative.Common (mapParser)
+import Options.Applicative.Help.Chunk (unChunk)
 import Options.Applicative.Help.Pretty (
   AnsiStyle,
   Color (..),
   Doc,
+  align,
   annotate,
   bold,
   color,
+  fill,
+  indent,
+  line,
   pretty,
   unAnnotate,
   underlined,
+  vsep,
   (<+>),
  )
+import Options.Applicative.Types (OptReader (..), Option (..))
 import System.Environment (getArgs, lookupEnv)
 import System.IO (hIsTerminalDevice, stderr, stdout)
 import Text.Megaparsec (Parsec, eof, errorBundlePretty, runParser)
@@ -273,3 +284,42 @@
               )
           )
     )
+
+-- | 'usageWith' with the full 'commandTree' as footer.
+--
+-- @since 0.2.5.0
+usageTreeWith
+  :: HelpStyle
+  -- ^ help style
+  -> String
+  -- ^ program name
+  -> String
+  -- ^ version
+  -> String
+  -- ^ description
+  -> Parser a
+  -- ^ arguments parser
+  -> ParserInfo a
+usageTreeWith s name version hdr p =
+  (usageWith s name version hdr p)
+    { infoFooter = pure (vsep ["Command tree:", indent 2 (commandTree p)])
+    }
+
+-- | Every command of a parser, hidden ones included, with nested subcommands indented under their
+-- parent.
+--
+-- @since 0.2.5.0
+commandTree :: Parser a -> Doc
+commandTree = vsep . commands
+ where
+  commands :: Parser x -> [Doc]
+  commands = concat . mapParser (const entries)
+  entries :: Option x -> [Doc]
+  entries o = case optMain o of
+    CmdReader _ cs -> uncurry entry <$> cs
+    _ -> []
+  entry :: String -> ParserInfo x -> Doc
+  entry n i =
+    fill 12 (pretty n) <+> align (fromMaybe mempty (unChunk (infoProgDesc i))) <> case commands (infoParser i) of
+      [] -> mempty
+      sub -> line <> indent 2 (vsep sub)
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,7 +1,14 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-import Data.CLI.Impl (fromParsec, plainHelpStyle, reversibleSwitch, usage, usageWith)
+import Data.CLI.Impl (
+  commandTree,
+  fromParsec,
+  plainHelpStyle,
+  reversibleSwitch,
+  usage,
+  usageWith,
+ )
 import Data.Error.CLI (CLIError (..), displayCLIError)
 import Data.List (isInfixOf)
 import Effectful (Eff, IOE, liftIO, runEff)
@@ -97,3 +104,12 @@
           , "prog - desc - version 1.0" `isInfixOf` render (usageWith plainHelpStyle)
           )
             `shouldBe` (True, False, True)
+
+  describe "commandTree"
+    $ it "renders nested and hidden commands"
+    $ let leaf n = command n (info (pure ()) (progDesc (n <> " desc")))
+          p =
+            subparser (leaf "a")
+              <|> subparser (hidden <> command "b" (info (subparser (leaf "c")) (progDesc "b desc")))
+       in show (commandTree p)
+            `shouldBe` "a            a desc\nb            b desc\n  c            c desc"
