diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.4.0.1...main)
+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.4.2.0...main)
+
+## [v1.4.2.0](https://github.com/freckle/stackctl/compare/v1.4.0.1...v1.4.2.0)
+
+- Add `stackctl-ls` for listing stacks and their deployed status
+- Add `--auto-sso` option for automating `aws sso login` when required
 
 ## [v1.4.0.1](https://github.com/freckle/stackctl/compare/v1.4.0.0...v1.4.0.1)
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -72,8 +72,8 @@
 - `man 1 stackctl`, or
 - `man 1 stackctl <command>`
 
-The man pages are also available [in-repository](./doc), but contain
-documentation as of `main`, and not your installed version.
+The man pages are also available [online](https://freckle.github.io/stackctl/),
+but contain documentation as of `main`, and not your installed version.
 
 ## Relationship to CloudGenesis
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -14,4 +14,5 @@
     <> subcommand Commands.capture
     <> subcommand Commands.changes
     <> subcommand Commands.deploy
+    <> subcommand Commands.list
     <> subcommand Commands.version
diff --git a/src/Stackctl/AutoSSO.hs b/src/Stackctl/AutoSSO.hs
new file mode 100644
--- /dev/null
+++ b/src/Stackctl/AutoSSO.hs
@@ -0,0 +1,81 @@
+module Stackctl.AutoSSO
+  ( AutoSSOOption
+  , defaultAutoSSOOption
+  , HasAutoSSOOption(..)
+  , autoSSOOption
+  , envAutoSSOOption
+  , handleAutoSSO
+  ) where
+
+import Stackctl.Prelude
+
+import Amazonka.SSO (_UnauthorizedException)
+import Amazonka.Types (Error, ErrorMessage(..), serviceMessage)
+import Data.Semigroup (Last(..))
+import qualified Env
+import Options.Applicative
+import Stackctl.Prompt
+import System.Process.Typed
+
+data AutoSSOOption
+  = AutoSSOAlways
+  | AutoSSOAsk
+  | AutoSSONever
+  deriving Semigroup via Last AutoSSOOption
+
+defaultAutoSSOOption :: AutoSSOOption
+defaultAutoSSOOption = AutoSSOAsk
+
+readAutoSSO :: String -> Either String AutoSSOOption
+readAutoSSO = \case
+  "always" -> Right AutoSSOAlways
+  "ask" -> Right AutoSSOAsk
+  "never" -> Right AutoSSONever
+  x ->
+    Left $ "Invalid choice for auto-sso: " <> x <> ", must be always|ask|never"
+
+class HasAutoSSOOption env where
+  autoSSOOptionL :: Lens' env AutoSSOOption
+
+autoSSOOption :: Parser AutoSSOOption
+autoSSOOption = option (eitherReader readAutoSSO)
+  $ mconcat [long "auto-sso", help autoSSOHelp, metavar "WHEN"]
+
+envAutoSSOOption :: Env.Parser Env.Error AutoSSOOption
+envAutoSSOOption = Env.var (first Env.UnreadError . readAutoSSO) "AUTO_SSO"
+  $ Env.help autoSSOHelp
+
+autoSSOHelp :: IsString a => a
+autoSSOHelp = "Automatically run aws-sso-login if necessary?"
+
+handleAutoSSO
+  :: ( MonadUnliftIO m
+     , MonadReader env m
+     , MonadLogger m
+     , HasLogger env
+     , HasAutoSSOOption options
+     )
+  => options
+  -> m a
+  -> m a
+handleAutoSSO options f = do
+  catchJust (preview (_UnauthorizedException @Error)) f $ \ex -> do
+    case options ^. autoSSOOptionL of
+      AutoSSOAlways -> do
+        logWarn $ ssoErrorMessage ex
+        logInfo "Running `aws sso login' automatically"
+      AutoSSOAsk -> do
+        logWarn $ ssoErrorMessage ex
+        promptOrExit "Run `aws sso login'"
+      AutoSSONever -> do
+        logError $ ssoErrorMessage ex
+        exitFailure
+
+    runProcess_ $ proc "aws" ["sso", "login"]
+    f
+ where
+  ssoErrorMessage ex =
+    "AWS SSO authorization error"
+      :# [ "message" .= fmap fromErrorMessage (ex ^. serviceMessage)
+         , "hint" .= ("Run `aws sso login' and try again" :: Text)
+         ]
diff --git a/src/Stackctl/CLI.hs b/src/Stackctl/CLI.hs
--- a/src/Stackctl/CLI.hs
+++ b/src/Stackctl/CLI.hs
@@ -10,6 +10,7 @@
 import qualified Blammo.Logging.LogSettings.Env as LoggingEnv
 import Control.Monad.Catch (MonadCatch)
 import Control.Monad.Trans.Resource (ResourceT, runResourceT)
+import Stackctl.AutoSSO
 import Stackctl.AWS
 import Stackctl.AWS.Scope
 import Stackctl.ColorOption
@@ -53,6 +54,9 @@
 instance HasVerboseOption options => HasVerboseOption (App options) where
   verboseOptionL = optionsL . verboseOptionL
 
+instance HasAutoSSOOption options => HasAutoSSOOption (App options) where
+  autoSSOOptionL = optionsL . autoSSOOptionL
+
 newtype AppT app m a = AppT
   { unAppT :: ReaderT app (LoggingT (ResourceT m)) a
   }
@@ -75,6 +79,7 @@
      , MonadUnliftIO m
      , HasColorOption options
      , HasVerboseOption options
+     , HasAutoSSOOption options
      )
   => options
   -> AppT (App options) m a
@@ -92,7 +97,7 @@
     envLogSettings
 
   app <- runResourceT $ runLoggerLoggingT logger $ do
-    aws <- awsEnvDiscover
+    aws <- runReaderT (handleAutoSSO options awsEnvDiscover) logger
 
     App logger
       <$> loadConfigOrExit
diff --git a/src/Stackctl/Commands.hs b/src/Stackctl/Commands.hs
--- a/src/Stackctl/Commands.hs
+++ b/src/Stackctl/Commands.hs
@@ -1,13 +1,10 @@
 module Stackctl.Commands
-  ( cat
-  , capture
-  , changes
-  , deploy
-  , version
+  ( module Stackctl.Commands
   ) where
 
 import Stackctl.Prelude
 
+import Stackctl.AutoSSO
 import Stackctl.ColorOption
 import Stackctl.DirectoryOption
 import Stackctl.FilterOption
@@ -15,6 +12,7 @@
 import Stackctl.Spec.Cat
 import Stackctl.Spec.Changes
 import Stackctl.Spec.Deploy
+import Stackctl.Spec.List
 import Stackctl.Subcommand
 import Stackctl.VerboseOption
 import Stackctl.Version
@@ -24,6 +22,7 @@
      , HasVerboseOption options
      , HasDirectoryOption options
      , HasFilterOption options
+     , HasAutoSSOOption options
      )
   => Subcommand options CatOptions
 cat = Subcommand
@@ -37,6 +36,7 @@
   :: ( HasColorOption options
      , HasVerboseOption options
      , HasDirectoryOption options
+     , HasAutoSSOOption options
      )
   => Subcommand options CaptureOptions
 capture = Subcommand
@@ -51,6 +51,7 @@
      , HasVerboseOption options
      , HasDirectoryOption options
      , HasFilterOption options
+     , HasAutoSSOOption options
      )
   => Subcommand options ChangesOptions
 changes = Subcommand
@@ -65,6 +66,7 @@
      , HasVerboseOption options
      , HasDirectoryOption options
      , HasFilterOption options
+     , HasAutoSSOOption options
      )
   => Subcommand options DeployOptions
 deploy = Subcommand
@@ -72,6 +74,21 @@
   , description = "Deploy specifications"
   , parse = parseDeployOptions
   , run = runAppSubcommand runDeploy
+  }
+
+list
+  :: ( HasColorOption options
+     , HasVerboseOption options
+     , HasDirectoryOption options
+     , HasFilterOption options
+     , HasAutoSSOOption options
+     )
+  => Subcommand options ListOptions
+list = Subcommand
+  { name = "ls"
+  , description = "List specifications"
+  , parse = parseListOptions
+  , run = runAppSubcommand runList
   }
 
 version :: Subcommand options ()
diff --git a/src/Stackctl/Options.hs b/src/Stackctl/Options.hs
--- a/src/Stackctl/Options.hs
+++ b/src/Stackctl/Options.hs
@@ -9,6 +9,7 @@
 import Data.Semigroup.Generic
 import qualified Env
 import Options.Applicative
+import Stackctl.AutoSSO
 import Stackctl.ColorOption
 import Stackctl.DirectoryOption
 import Stackctl.FilterOption
@@ -19,6 +20,7 @@
   , oFilter :: Maybe FilterOption
   , oColor :: Maybe ColorOption
   , oVerbose :: Verbosity
+  , oAutoSSO :: Maybe AutoSSOOption
   }
   deriving stock Generic
   deriving Semigroup via GenericSemigroupMonoid Options
@@ -29,6 +31,9 @@
 filterL :: Lens' Options (Maybe FilterOption)
 filterL = lens oFilter $ \x y -> x { oFilter = y }
 
+autoSSOL :: Lens' Options (Maybe AutoSSOOption)
+autoSSOL = lens oAutoSSO $ \x y -> x { oAutoSSO = y }
+
 instance HasDirectoryOption Options where
   directoryOptionL = directoryL . maybeLens defaultDirectoryOption
 
@@ -41,6 +46,9 @@
 instance HasVerboseOption Options where
   verboseOptionL = lens oVerbose $ \x y -> x { oVerbose = y }
 
+instance HasAutoSSOOption Options where
+  autoSSOOptionL = autoSSOL . maybeLens defaultAutoSSOOption
+
 -- brittany-disable-next-binding
 
 envParser :: Env.Parser Env.Error Options
@@ -49,6 +57,7 @@
   <*> optional (envFilterOption "specifications")
   <*> pure mempty -- use LOG_COLOR
   <*> pure mempty -- use LOG_LEVEL
+  <*> optional envAutoSSOOption
 
 -- brittany-disable-next-binding
 
@@ -58,3 +67,4 @@
   <*> optional (filterOption "specifications")
   <*> optional colorOption
   <*> verboseOption
+  <*> optional autoSSOOption
diff --git a/src/Stackctl/Prompt.hs b/src/Stackctl/Prompt.hs
--- a/src/Stackctl/Prompt.hs
+++ b/src/Stackctl/Prompt.hs
@@ -1,6 +1,7 @@
 module Stackctl.Prompt
   ( prompt
   , promptContinue
+  , promptOrExit
   ) where
 
 import Stackctl.Prelude
@@ -34,7 +35,13 @@
 
 promptContinue
   :: (MonadIO m, MonadLogger m, MonadReader env m, HasLogger env) => m ()
-promptContinue = prompt "Continue (y/n)" parse dispatch
+promptContinue = promptOrExit "Continue"
+
+promptOrExit
+  :: (MonadIO m, MonadLogger m, MonadReader env m, HasLogger env)
+  => Text
+  -> m ()
+promptOrExit msg = prompt (msg <> " (y/n)") parse dispatch
  where
   parse x
     | x `elem` ["y", "Y"] = Right True
diff --git a/src/Stackctl/Spec/List.hs b/src/Stackctl/Spec/List.hs
new file mode 100644
--- /dev/null
+++ b/src/Stackctl/Spec/List.hs
@@ -0,0 +1,62 @@
+module Stackctl.Spec.List
+  ( ListOptions(..)
+  , parseListOptions
+  , runList
+  ) where
+
+import Stackctl.Prelude
+
+import Blammo.Logging.Logger (pushLoggerLn)
+import Options.Applicative
+import Stackctl.AWS
+import Stackctl.AWS.Scope
+import Stackctl.Colors
+import Stackctl.Config (HasConfig)
+import Stackctl.DirectoryOption (HasDirectoryOption(..))
+import Stackctl.FilterOption (HasFilterOption)
+import Stackctl.Spec.Discover
+import Stackctl.StackSpec
+
+data ListOptions = ListOptions
+
+-- brittany-disable-next-binding
+
+parseListOptions :: Parser ListOptions
+parseListOptions = pure ListOptions
+
+runList
+  :: ( MonadUnliftIO m
+     , MonadMask m
+     , MonadResource m
+     , MonadLogger m
+     , MonadReader env m
+     , HasAwsScope env
+     , HasAwsEnv env
+     , HasLogger env
+     , HasConfig env
+     , HasDirectoryOption env
+     , HasFilterOption env
+     )
+  => ListOptions
+  -> m ()
+runList _ = do
+  specs <- discoverSpecs
+  Colors {..} <- getColorsLogger
+
+  for_ specs $ \spec -> do
+    let
+      path = stackSpecFilePath spec
+      name = stackSpecStackName spec
+
+    exists <- isJust <$> awsCloudFormationDescribeStackMaybe name
+
+    let
+      formatted :: Text
+      formatted =
+        "  "
+          <> (if exists then green "✓ " else yellow "✗ ")
+          <> cyan (unStackName name)
+          <> " => "
+          <> magenta (pack path)
+
+    pushLoggerLn formatted
diff --git a/src/Stackctl/StackSpec.hs b/src/Stackctl/StackSpec.hs
--- a/src/Stackctl/StackSpec.hs
+++ b/src/Stackctl/StackSpec.hs
@@ -1,5 +1,6 @@
 module Stackctl.StackSpec
   ( StackSpec
+  , stackSpecFilePath
   , stackSpecSpecPath
   , stackSpecSpecBody
   , stackSpecStackName
@@ -45,6 +46,10 @@
 stackSpecSpecRoot :: StackSpec -> FilePath
 stackSpecSpecRoot = ssSpecRoot
 
+stackSpecFilePath :: StackSpec -> FilePath
+stackSpecFilePath spec =
+  FilePath.normalise $ stackSpecSpecRoot spec </> stackSpecStackFile spec
+
 stackSpecSpecPath :: StackSpec -> StackSpecPath
 stackSpecSpecPath = ssSpecPath
 
@@ -160,10 +165,7 @@
       liftIO $ Yaml.encodeFile specPath $ stackSpecSpecBody stackSpec
  where
   templatePath = unStackTemplate $ stackSpecTemplate stackSpec
-  specPath =
-    FilePath.normalise
-      $ stackSpecSpecRoot stackSpec
-      </> stackSpecStackFile stackSpec
+  specPath = stackSpecFilePath stackSpec
 
 readStackSpec
   :: (MonadIO m, MonadReader env m, HasConfig env)
diff --git a/src/Stackctl/Subcommand.hs b/src/Stackctl/Subcommand.hs
--- a/src/Stackctl/Subcommand.hs
+++ b/src/Stackctl/Subcommand.hs
@@ -10,6 +10,7 @@
 
 import qualified Env
 import Options.Applicative
+import Stackctl.AutoSSO
 import Stackctl.CLI
 import Stackctl.ColorOption
 import Stackctl.Options
@@ -61,7 +62,10 @@
 -- @
 --
 runAppSubcommand
-  :: (HasColorOption options, HasVerboseOption options)
+  :: ( HasColorOption options
+     , HasVerboseOption options
+     , HasAutoSSOOption options
+     )
   => (subOptions -> AppT (App options) IO a)
   -> subOptions
   -> options
diff --git a/stackctl.cabal b/stackctl.cabal
--- a/stackctl.cabal
+++ b/stackctl.cabal
@@ -1,6 +1,6 @@
 cabal-version:   1.18
 name:            stackctl
-version:         1.4.0.1
+version:         1.4.2.0
 license:         MIT
 license-file:    LICENSE
 copyright:       2022 Renaissance Learning Inc
@@ -21,6 +21,7 @@
 library
     exposed-modules:
         Stackctl.Action
+        Stackctl.AutoSSO
         Stackctl.AWS
         Stackctl.AWS.CloudFormation
         Stackctl.AWS.Core
@@ -50,6 +51,7 @@
         Stackctl.Spec.Deploy
         Stackctl.Spec.Discover
         Stackctl.Spec.Generate
+        Stackctl.Spec.List
         Stackctl.StackDescription
         Stackctl.StackSpec
         Stackctl.StackSpecPath
@@ -91,6 +93,7 @@
         amazonka-core,
         amazonka-ec2,
         amazonka-lambda,
+        amazonka-sso,
         amazonka-sts,
         base >=4 && <5,
         bytestring,
@@ -113,6 +116,7 @@
         text,
         time,
         transformers,
+        typed-process,
         unliftio,
         unliftio-core,
         unordered-containers,
