diff --git a/bin/monitor.py b/geomagio/processing/monitor.py
similarity index 81%
rename from bin/monitor.py
rename to geomagio/processing/monitor.py
index ad57d77a103778e582ca877b9a5d9a9a36d54cee..78b4cbb86471bff6e5ed854386647ff3823264e3 100755
--- a/bin/monitor.py
+++ b/geomagio/processing/monitor.py
@@ -1,21 +1,14 @@
 #! /usr/bin/env python
 
 """Monitor """
-from os import path
 import sys
-
-# ensure geomag is on the path before importing
-try:
-    import geomagio  # noqa (tells linter to ignore this line.)
-except ImportError:
-    script_dir = path.dirname(path.abspath(__file__))
-    sys.path.append(path.normpath(path.join(script_dir, "..")))
-
+from typing import List
 import argparse
 import sys
+
 from obspy.core import UTCDateTime
-import geomagio.TimeseriesUtility as TimeseriesUtility
-import geomagio.edge as edge
+
+from .. import TimeseriesUtility, edge
 
 
 def calculate_warning_threshold(warning_threshold, interval):
@@ -183,28 +176,35 @@ def print_html_header(starttime, endtime, title):
     )
 
 
-def print_observatories(args):
+def print_observatories(
+    starttime: UTCDateTime,
+    endtime: UTCDateTime,
+    observatories: List[str],
+    edge_host: str,
+    channels: List[str],
+    data_type: str,
+    gaps_only: bool,
+    intervals: List[str],
+    location_code: str,
+    warning_threshold: int,
+):
     """Print all the observatories
-    Parameters
-    ---------
-    args: dictionary
-        Holds all the command line arguments. See parse_args
 
     Returns
     -------
     Boolean: if a warning was issued.
 
     """
-    intervals = args.intervals
-    channels = args.channels
-    starttime = args.starttime
-    endtime = args.endtime
-    host = args.edge_host
+    intervals = intervals
+    channels = channels
+    starttime = starttime
+    endtime = endtime
+    host = edge_host
     table_header = get_table_header()
     warning_issued = False
     table_end = "</tbody>\n" + "</table>\n"
 
-    for observatory in args.observatories:
+    for observatory in observatories:
         summary_table = ""
         gap_details = ""
         print_it = False
@@ -215,23 +215,21 @@ def print_observatories(args):
                 host=host,
                 port=2060,
                 observatory=observatory,
-                type=args.type,
+                type=data_type,
                 channels=channels,
-                locationCode=args.locationcode,
+                locationCode=location_code,
                 interval=interval,
             )
 
             timeseries = factory.get_timeseries(starttime=starttime, endtime=endtime)
             gaps = TimeseriesUtility.get_stream_gaps(timeseries)
-            if args.gaps_only and not has_gaps(gaps):
+            if gaps_only and not has_gaps(gaps):
                 continue
             else:
                 print_it = True
 
             warning = ""
-            warning_threshold = calculate_warning_threshold(
-                args.warning_threshold, interval
-            )
+            warning_threshold = calculate_warning_threshold(warning_threshold, interval)
 
             summary_table += "<tr>"
             summary_table += '<td style="text-align:center;">'
@@ -279,7 +277,38 @@ def print_observatories(args):
     return warning_issued
 
 
-def main(args):
+def generate_report(
+    starttime: UTCDateTime,
+    endtime: UTCDateTime,
+    observatories: List[str],
+    edge_host: str = "127.0.0.1",
+    channels: List[str] = ["H", "E", "Z", "F"],
+    data_type: str = "variation",
+    gaps_only: bool = True,
+    intervals: List[str] = ("minute",),
+    location_code: str = "R0",
+    title: str = "",
+    warning_threshold: int = 60,
+):
+    print_html_header(starttime=starttime, endtime=endtime, title=title)
+    warning_issued = print_observatories(
+        starttime=starttime,
+        endtime=endtime,
+        observatories=observatories,
+        edge_host=edge_host,
+        channels=channels,
+        data_type=data_type,
+        gaps_only=gaps_only,
+        intervals=intervals,
+        location_code=location_code,
+        warning_threshold=warning_threshold,
+    )
+    print("</body>\n" + "</html>\n")
+
+    sys.exit(warning_issued)
+
+
+def main(args=None):
     """command line tool for building geomag monitoring reports
 
     Inputs
@@ -291,12 +320,21 @@ def main(args):
     parses command line options using argparse
     Output is in HTML.
     """
-    print_html_header(args.starttime, args.endtime, args.title)
-
-    warning_issued = print_observatories(args)
-    print("</body>\n" + "</html>\n")
-
-    sys.exit(warning_issued)
+    if args is None:
+        args = parse_args(sys.argv[1:])
+    generate_report(
+        starttime=args.starttime,
+        endtime=args.endtime,
+        observatories=args.observatories,
+        edge_host=args.edge_host,
+        channels=args.channels,
+        data_type=args.type,
+        gaps_only=args.gaps_only,
+        intervals=args.intervals,
+        location_code=args.locationcode,
+        title=args.title,
+        warning_threshold=args.warning_threshold,
+    )
 
 
 def parse_args(args):
@@ -329,7 +367,12 @@ def parse_args(args):
         default=None,
         help="UTC date YYYY-MM-DD HH:MM:SS",
     )
-    parser.add_argument("--edge-host", required=True, help="IP/URL for edge connection")
+    parser.add_argument(
+        "--edge-host",
+        required=False,
+        default="127.0.0.1",
+        help="IP/URL for edge connection",
+    )
     parser.add_argument(
         "--observatories",
         required=True,
@@ -374,5 +417,4 @@ def parse_args(args):
 
 
 if __name__ == "__main__":
-    args = parse_args(sys.argv[1:])
-    main(args)
+    main()
diff --git a/pyproject.toml b/pyproject.toml
index fabac221aa34ab63097eea0ebd536af9fb9e9e23..3ee7c6e479caed0f51ed738d48c0cd5f05e596e5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -61,6 +61,7 @@ types-requests = "^2.25.6"
 [tool.poetry.scripts]
 generate-matrix = "geomagio.processing.affine_matrix:main"
 geomag-metadata = "geomagio.metadata.main:main"
+geomag-monitor = "geomagio.processing.monitor:main"
 geomag-py = "geomagio.Controller:main"
 magproc-prepfiles = "geomagio.processing.magproc:main"
 make-cal = "geomagio.processing.make_cal:main"