diff --git a/exe/Main.hs b/exe/Main.hs
--- a/exe/Main.hs
+++ b/exe/Main.hs
@@ -5,21 +5,20 @@
 
 module Main where
 
-import           Control.Monad
-import           Control.Monad.IO.Class
-import           Data.Char (isUpper)
-import           Data.List (find, isPrefixOf)
-import           DynFlags (parseDynamicFlagsCmdLine, updOptLevel)
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.Char (isUpper)
+import Data.List (find, isPrefixOf)
+import DynFlags (parseDynamicFlagsCmdLine, updOptLevel)
 import qualified GHC as GHC
-import           HsInspect.Imports
-import           HsInspect.Modules
-import           HsInspect.Packages
-import           HsInspect.Search
-import           HsInspect.Sexp as S
-import           Json
-import           Outputable (defaultUserStyle, initSDocContext, runSDoc)
-import           System.Environment (getArgs)
-import           System.Exit
+import HsInspect.Imports
+import HsInspect.Index
+import HsInspect.Packages
+import HsInspect.Sexp as S
+import Json
+import Outputable (defaultUserStyle, initSDocContext, runSDoc)
+import System.Environment (getArgs)
+import System.Exit
 
 version :: String
 #ifdef CURRENT_PACKAGE_VERSION
@@ -35,9 +34,8 @@
   "  imports /path/to/file.hs - list the qualified imports for the file\n" ++
   "                             along with their locally qualified (and\n" ++
   "                             unqualified) names.\n" ++
-  "  modules /path/to/file.hs - list all modules that could be imported by file.\n" ++
-  "  packages /path/to/dir    - list all packages that are imported by this dir.\n" ++
-  "  search  /path/to/file.hs QUERY - Hoogle query within the file's context.\n "
+  "  index                    - list all dependency packages, modules, terms and types.\n" ++
+  "  packages /path/to/dir    - list all packages that are referenced by sources in this dir.\n"
 
 -- Possible backends:
 --
@@ -73,14 +71,11 @@
       "imports" : file : rest -> do
         quals <- imports file
         respond rest quals
-      "modules" : rest -> do
-        hits <- modules homeModules
+      "index" : rest -> do
+        hits <- index
         respond rest hits
       "packages" : dir : rest -> do
         hits <- packages dir
-        respond rest hits
-      "search" : query : rest -> do
-        hits <- search query
         respond rest hits
       _ ->
         liftIO $ error "invalid parameters"
diff --git a/hsinspect.cabal b/hsinspect.cabal
--- a/hsinspect.cabal
+++ b/hsinspect.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name:          hsinspect
-version:       0.0.6
+version:       0.0.7
 synopsis:      Inspect Haskell source files.
 license:       GPL-3.0-or-later
 license-file:  LICENSE
@@ -34,8 +34,9 @@
 
   ghc-options:      -Wall -Werror=missing-home-modules
   default-language: Haskell2010
+
   if flag(ghcflags)
-    ghc-options: -fplugin GhcFlags.Plugin
+    ghc-options:   -fplugin GhcFlags.Plugin
     build-depends: ghcflags
 
 executable hsinspect
@@ -52,9 +53,8 @@
   -- cabal-fmt: expand library
   exposed-modules:
     HsInspect.Imports
-    HsInspect.Modules
+    HsInspect.Index
     HsInspect.Packages
-    HsInspect.Search
     HsInspect.Sexp
     HsInspect.Util
     HsInspect.Workarounds
diff --git a/library/HsInspect/Imports.hs b/library/HsInspect/Imports.hs
--- a/library/HsInspect/Imports.hs
+++ b/library/HsInspect/Imports.hs
@@ -62,13 +62,17 @@
        in Qualified ln lqn fqn
 
 -- Note that `nameSrcLoc gre_name` is empty
--- TODO what other information is available?
+-- TODO unitid (can be used to lookup source code)
 -- TODO "and originally defined" / ppr_defn_site
+
+-- 1. local name
+-- 2. locally qualified name
+-- 3. fully qualified name
 data Qualified
   = Qualified
-      (Maybe String) -- ^^ local name
-      (Maybe String) -- ^^ locally qualifed name
-      String -- ^^ fully qualified name
+      (Maybe String)
+      (Maybe String)
+      String
   deriving (Eq, Show)
 
 instance ToSexp Qualified where
diff --git a/library/HsInspect/Index.hs b/library/HsInspect/Index.hs
new file mode 100644
--- /dev/null
+++ b/library/HsInspect/Index.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns #-}
+
+-- | Dumps an index of all terms and their types
+module HsInspect.Index
+  ( index,
+    PackageEntries,
+  )
+where
+
+import Avail (AvailInfo (..))
+import BinIface
+  ( CheckHiWay (..),
+    TraceBinIFaceReading (..),
+    readBinIface,
+  )
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.List (isSuffixOf)
+import Data.Maybe (catMaybes, maybeToList)
+import Data.Set (Set)
+import qualified Data.Set as Set
+import qualified GHC
+import GHC.PackageDb
+import HsInspect.Sexp
+import HsInspect.Util
+import HscTypes (ModIface (..))
+import qualified Id as GHC
+import Json
+import Module (Module (..), moduleNameString, unitIdString)
+import Outputable (showPpr)
+import PackageConfig
+--import System.IO (hPutStrLn, stderr)
+import PackageConfig (packageConfigId)
+import Packages (explicitPackages, lookupPackage)
+import TcEnv (tcLookup)
+import TcRnMonad (initTcInteractive)
+import qualified TcRnTypes as GHC
+
+index :: GHC.GhcMonad m => m [PackageEntries]
+index = do
+  -- TODO the home package
+  dflags <- GHC.getSessionDynFlags
+  let explicit = explicitPackages $ GHC.pkgState dflags
+      pkgcfgs = maybeToList . lookupPackage dflags =<< explicit
+  traverse getSymbols pkgcfgs
+
+-- TODO Maybe haddock-html
+-- TODO Maybe source definition (or should we leave source resolution to downstream?)
+getSymbols :: GHC.GhcMonad m => PackageConfig -> m PackageEntries
+getSymbols pkg = do
+  let findHis dir = filter (".hi" `isSuffixOf`) <$> liftIO (walk dir)
+      exposed = Set.fromList $ fst <$> exposedModules pkg
+      unitid = packageConfigId pkg
+  his <- join <$> traverse findHis (importDirs pkg)
+  PackageEntries unitid . catMaybes <$> traverse (hiToSymbols exposed) his
+
+hiToSymbols :: GHC.GhcMonad m => Set GHC.ModuleName -> FilePath -> m (Maybe ModuleEntries)
+hiToSymbols exposed hi = do
+  env <- GHC.getSession
+  dflags <- GHC.getSessionDynFlags
+  (_, hits) <-
+    -- TODO use initTc instead of initTcInteractive
+    liftIO . initTcInteractive env $ do
+      iface <- readBinIface IgnoreHiWay QuietBinIFaceReading hi
+      let m = mi_module iface
+          modName = moduleName m
+      if not $ Set.member (GHC.moduleName m) exposed
+        then pure Nothing
+        else do
+          let thing (Avail name) = traverse tcLookup [name]
+              -- TODO the fields in AvailTC
+              thing (AvailTC name members _) = traverse tcLookup (name : members)
+
+          things <- join <$> traverse thing (mi_exports iface)
+
+          -- TODO refactor this code to return the Module and TcTyThing and
+          -- do the conversion to Entry in the caller.
+          pure . Just . ModuleEntries modName . catMaybes $ (tyrender dflags) <$> things
+  pure $ join hits
+
+tyrender :: GHC.DynFlags -> GHC.TcTyThing -> Maybe Entry
+tyrender dflags (GHC.AGlobal (GHC.AnId var)) =
+  Just
+    $ Entry (showPpr dflags $ GHC.idName var)
+        (showPpr dflags $ GHC.idType var)
+-- TODO investigate what we're skipping
+tyrender _ _ = Nothing
+
+-- TODO normalise the type string to make it easier for downstream tools to perform searches
+-- TODO note if this is the original definition point (vs a re-export)
+data Entry = Entry String String
+
+data ModuleEntries = ModuleEntries GHC.ModuleName [Entry]
+
+data PackageEntries = PackageEntries GHC.UnitId [ModuleEntries]
+
+instance ToSexp Entry where
+  toSexp (Entry term typ) =
+    alist
+      [ ("name", SexpString term),
+        ("type", SexpString typ)
+      ]
+
+instance ToSexp ModuleEntries where
+  toSexp (ModuleEntries modl entries) =
+    alist
+      [ ("module", SexpString . moduleNameString $ modl),
+        ("ids", toSexp entries)
+      ]
+
+instance ToSexp PackageEntries where
+  toSexp (PackageEntries pkg modules) =
+    alist
+      [ ("unitid", SexpString . unitIdString $ pkg),
+        ("modules", toSexp modules)
+      ]
+
+instance ToJson Entry where
+  json (Entry term typ) =
+    JSObject
+      [ ("name", JSString term),
+        ("type", JSString typ)
+      ]
+
+instance ToJson ModuleEntries where
+  json (ModuleEntries modl entries) =
+    JSObject
+      [ ("module", JSString . moduleNameString $ modl),
+        ("ids", JSArray $ json <$> entries)
+      ]
+
+instance ToJson PackageEntries where
+  json (PackageEntries pkg modules) =
+    JSObject
+      [ ("unitid", JSString . unitIdString $ pkg),
+        ("modules", JSArray $ json <$> modules)
+      ]
diff --git a/library/HsInspect/Modules.hs b/library/HsInspect/Modules.hs
deleted file mode 100644
--- a/library/HsInspect/Modules.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-
--- | Calculate all exposed modules that could be imported.
-module HsInspect.Modules
-  ( modules,
-    Hit,
-  )
-where
-
-import Data.List (sort)
-import Data.Set (Set)
-import qualified Data.Set as Set
-import qualified GHC
-import GHC.PackageDb
-import HsInspect.Sexp
-import Json
-import Module (UnitId)
-import PackageConfig
-import Packages (explicitPackages)
-
-modules :: GHC.GhcMonad m => [String] -> m [Hit]
-modules homeModules = do
-  -- TODO where is base?
-  dflags <- GHC.getSessionDynFlags
-  let Just dbs = GHC.pkgDatabase dflags
-      loaded = Set.fromList . explicitPackages $ GHC.pkgState dflags
-      home = Hit <$> homeModules
-      away = (mods loaded =<<) =<< (snd <$> dbs)
-  pure . sort $ home <> away
-
-mods :: Set UnitId -> PackageConfig -> [Hit]
-mods allowed p =
-  if Set.notMember (packageConfigId p) allowed
-    then []
-    else Hit . GHC.moduleNameString . fst <$> exposedModules p
-
-data Hit = Hit String
-  deriving (Eq, Ord)
-
-instance ToSexp Hit where
-  toSexp (Hit txt) = toSexp txt
-
-instance ToJson Hit where
-  json (Hit txt) = JSString txt
diff --git a/library/HsInspect/Packages.hs b/library/HsInspect/Packages.hs
--- a/library/HsInspect/Packages.hs
+++ b/library/HsInspect/Packages.hs
@@ -5,27 +5,27 @@
 
 module HsInspect.Packages (packages, PkgSummary) where
 
-import           BasicTypes (StringLiteral(..))
-import           Control.Monad (join, void)
-import           Control.Monad.IO.Class (liftIO)
-import           Data.List (isSuffixOf, nub, sort, (\\))
-import           Data.Maybe (catMaybes)
+import BasicTypes (StringLiteral(..))
+import Control.Monad (join, void)
+import Control.Monad.IO.Class (liftIO)
+import Data.List (isSuffixOf, nub, sort, (\\))
+import Data.Maybe (catMaybes)
 import qualified Data.Set as Set
-import           FastString
-import           Finder (findImportedModule)
+import FastString
+import Finder (findImportedModule)
 import qualified GHC
-import           HscTypes (FindResult(..))
-import           HsInspect.Sexp
-import           HsInspect.Util
-import           HsInspect.Workarounds
-import           Json
-import           Module (Module(..), ModuleName, moduleNameString, unitIdString)
-import           Packages (PackageState(..))
+import HscTypes (FindResult(..))
+import HsInspect.Sexp
+import HsInspect.Util
+import HsInspect.Workarounds
+import Json
+import Module (Module(..), ModuleName, moduleNameString, unitIdString)
+import Packages (PackageState(..))
 
 -- Similar to packunused / weeder, but more reliable (and doesn't require a
 -- separate -ddump-minimal-imports pass).
 --
--- TODO support list of dirs not just one
+-- TODO get the dirs from the dynflags not the user
 packages :: GHC.GhcMonad m => FilePath -> m PkgSummary
 packages dir = do
   -- We load all .hs files in dir, assuming they are the sources of the home
diff --git a/library/HsInspect/Search.hs b/library/HsInspect/Search.hs
deleted file mode 100644
--- a/library/HsInspect/Search.hs
+++ /dev/null
@@ -1,73 +0,0 @@
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE ViewPatterns #-}
-
-module HsInspect.Search
-  ( search,
-    Hit,
-  )
-where
-
-import BinIface
-  ( CheckHiWay (..),
-    TraceBinIFaceReading (..),
-    readBinIface,
-  )
-import Control.Monad
-import Control.Monad.IO.Class
-import Data.List (isSuffixOf)
-import Data.Set (Set)
-import qualified Data.Set as Set
-import qualified GHC
-import GHC.PackageDb
-import HsInspect.Sexp
-import HsInspect.Util
-import HscTypes (ModIface (..))
-import Json
-import qualified Name as GHC
-import PackageConfig
-import Packages (explicitPackages)
-import TcRnMonad (initTcRnIf)
-
-search :: GHC.GhcMonad m => String -> m [Hit]
-search _query = do
-  -- TODO support home modules
-  -- TODO where is base?
-  dflags <- GHC.getSessionDynFlags
-
-  -- TODO this logic is used in Packages / Modules, share
-  let Just ((snd =<<) -> allPkgs) = GHC.pkgDatabase dflags
-      explicit = Set.fromList . explicitPackages $ GHC.pkgState dflags
-      pkgs = filter (\(packageConfigId -> pid) -> Set.member pid explicit) allPkgs
-  join <$> traverse getSymbols pkgs
-  -- ^ TODO filter and rank the symbols by the search
-
--- TODO Maybe haddock-html
-getSymbols :: GHC.GhcMonad m => PackageConfig -> m [Hit]
-getSymbols pkg = do
-  let findHis dir = filter (".hi" `isSuffixOf`) <$> liftIO (walk dir)
-      exposed = Set.fromList $ fst <$> exposedModules pkg
-  his <- join <$> traverse findHis (importDirs pkg)
-  join <$> traverse (hiToSymbols exposed) his
-
-hiToSymbols :: GHC.GhcMonad m => Set GHC.ModuleName -> FilePath -> m [Hit]
-hiToSymbols exposed hi = do
-  env <- GHC.getSession
-  iface <-
-    liftIO $ initTcRnIf 'z' env () ()
-      $ readBinIface IgnoreHiWay QuietBinIFaceReading hi
-  let m = mi_module iface
-  pure
-    $ if not $ Set.member (GHC.moduleName m) exposed
-      then []
-      else do
-        -- FIXME the Name is not very useful, use loadDecls
-        decl <- GHC.getName . snd <$> mi_decls iface
-        pure $ Hit m decl
-
-data Hit = Hit GHC.Module GHC.Name
-
-instance ToSexp Hit where
-  toSexp (Hit _ name) = toSexp . GHC.getOccString $ name
-
-instance ToJson Hit where
-  json (Hit _ name) = JSString . GHC.getOccString $ name
diff --git a/library/HsInspect/Workarounds.hs b/library/HsInspect/Workarounds.hs
--- a/library/HsInspect/Workarounds.hs
+++ b/library/HsInspect/Workarounds.hs
@@ -4,27 +4,27 @@
 
 module HsInspect.Workarounds where
 
-import           Control.Monad
-import           Control.Monad.IO.Class
-import           Data.List (delete, intercalate, isSuffixOf)
-import           Data.Set (Set)
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.List (delete, intercalate, isSuffixOf)
+import Data.Set (Set)
 import qualified Data.Set as Set
-import           DriverPhases (HscSource(..), Phase(..))
-import           DriverPipeline (preprocess)
-import           DynFlags (parseDynamicFilePragma)
-import           FastString
+import DriverPhases (HscSource(..), Phase(..))
+import DriverPipeline (preprocess)
+import DynFlags (parseDynamicFilePragma)
+import FastString
 import qualified GHC as GHC
-import           HeaderInfo (getOptions)
-import           HscTypes (Target(..), TargetId(..))
-import           HsImpExp (ImportDecl(..))
-import           Lexer
-import           Outputable (showPpr)
-import           Parser (parseHeader)
-import           RdrName (GlobalRdrEnv)
-import           SrcLoc
-import           StringBuffer
-import           System.Directory (getModificationTime, removeFile)
-import           TcRnTypes (tcg_rdr_env)
+import HeaderInfo (getOptions)
+import HscTypes (Target(..), TargetId(..))
+import HsImpExp (ImportDecl(..))
+import Lexer
+import Outputable (showPpr)
+import Parser (parseHeader)
+import RdrName (GlobalRdrEnv)
+import SrcLoc
+import StringBuffer
+import System.Directory (getModificationTime, removeFile)
+import TcRnTypes (tcg_rdr_env)
 
 -- WORKAROUND https://gitlab.haskell.org/ghc/ghc/merge_requests/1541
 importsOnly :: GHC.GhcMonad m => Set GHC.ModuleName -> FilePath -> m (Maybe GHC.ModuleName, Target)
