diff --git a/HISTORY.rst b/HISTORY.rst
index c3f2eb7..053b986 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -4,6 +4,32 @@ History
 
 .. current developments
 
+v0.2.0
+------
+
+* Add CLI tools (:issue:`37`). See ``rctools gcs repdirstruc --help`` to start
+* Add new function ``rhg_compute_tools.gcs.replicate_directory_structure_on_gcs`` to copy directory trees into GCS
+* Fixes to docstrings and metadata (:issue:`43`) (:issue:`45`)
+
+
+v0.1.8
+------
+
+* Deployment fixes
+
+v0.1.7
+------
+
+* Design tools: use RHG & CIL colors & styles
+* Plotting helpers: generate cmaps with consistent colors & norms, and apply a colorbar to geopandas plots with nonlinear norms
+* Autoscaling fix for kubecluster: switch to dask_kubernetes.KubeCluster to allow use of recent bug fixes
+
+
+v0.1.6
+------
+
+* Add ``rhg_compute_tools.gcs.cp_gcs`` and ``rhg_compute_tools.gcs.sync_gcs`` utilities
+
 v0.1.5
 ------
 
diff --git a/requirements.txt b/requirements.txt
index d19ea9f..9e968c3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@ google-cloud-storage==1.16.1
 dask-kubernetes==0.8.0
 matplotlib==3.0.3
 numpy>=1.14
+click
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 903cbb1..0bf806b 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -8,4 +8,5 @@ Sphinx==1.7.4
 pyyaml>=4.2b1
 pytest-cov==2.5.1
 pytest-runner==4.2
+pytest-mock
 twine==1.13.0
diff --git a/rhg_compute_tools/cli.py b/rhg_compute_tools/cli.py
new file mode 100644
index 0000000..9aacdee
--- /dev/null
+++ b/rhg_compute_tools/cli.py
@@ -0,0 +1,36 @@
+import click
+from rhg_compute_tools.gcs import replicate_directory_structure_on_gcs
+
+
+@click.group(
+    context_settings={'help_option_names': ['-h', '--help']}
+)
+def rctools_cli():
+    """Rhodium Compute Tools"""
+    pass
+
+
+@rctools_cli.group()
+def gcs():
+    """Tools for interacting with Google Cloud Storage
+    """
+    pass
+
+
+@gcs.command()
+@click.argument('src', type=click.Path(exists=True))
+@click.argument('dst', type=click.Path())
+@click.option('-c', '--credentials', type=click.Path(exists=True),
+              envvar='GOOGLE_APPLICATION_CREDENTIALS',
+              help='Optional path to GCS credentials file.')
+def repdirstruc(src, dst, credentials):
+    """Replicate directory structure onto GCS bucket.
+
+    SRC is path to a local directory. Directories within will be replicated.
+    DST is gs://[bucket] and optional path to replicate SRC into.
+
+    If --credentials or -c is not explicitly given, checks the
+    GOOGLE_APPLICATIONS_CREDENTIALS environment variable for path to a GCS
+    credentials file.
+    """
+    replicate_directory_structure_on_gcs(src, dst, credentials)
diff --git a/rhg_compute_tools/gcs.py b/rhg_compute_tools/gcs.py
index 93c1ac6..cfe89b8 100644
--- a/rhg_compute_tools/gcs.py
+++ b/rhg_compute_tools/gcs.py
@@ -47,13 +47,12 @@ def _get_path_types(src, dest):
     return src_gs, dest_gs, dest_gcs
 
 
-def replicate_directory_structure_on_gcs(src, dst, storage_client):
-    '''
+def replicate_directory_structure_on_gcs(src, dst, client_or_creds):
+    """
     Replicate a local directory structure on google cloud storage
-    
+
     Parameters
     ----------
-    
     src : str
         Path to the root directory on the source machine. The directory
         structure within this directory will be reproduced within `dst`,
@@ -61,9 +60,15 @@ def replicate_directory_structure_on_gcs(src, dst, storage_client):
     dst : str
         A url for the root directory of the destination, starting with
         `gs://[bucket_name]/`, e.g. `gs://my_bucket/path/to/my/data`
-    storage_client : object
-        An authenticated :py:class:`google.cloud.storage.client.Client` object
-    '''
+    client_or_creds : google.cloud.storage.client.Client or str
+        An authenticated :py:class:`google.cloud.storage.client.Client` object,
+        or a str path to storage credentials authentication file.
+    """
+    if isinstance(client_or_creds, str):
+        credentials = service_account.Credentials.from_service_account_file(
+            client_or_creds
+        )
+        client_or_creds = storage.Client(credentials=credentials)
 
     if dst.startswith('gs://'):
         dst = dst[5:]
@@ -71,11 +76,11 @@ def replicate_directory_structure_on_gcs(src, dst, storage_client):
         dst = dst[6:]
     else:
         raise ValueError('dst must begin with `gs://` or `gcs://`')
-    
+
     bucket_name = dst.split('/')[0]
     blob_path = '/'.join(dst.split('/')[1:])
-    
-    bucket = storage_client.get_bucket(bucket_name)
+
+    bucket = client_or_creds.get_bucket(bucket_name)
 
     for d, dirnames, files in os.walk(src):
         dest_path = os.path.join(blob_path, os.path.relpath(d, src))
diff --git a/setup.py b/setup.py
index 1cdca31..2c039e2 100644
--- a/setup.py
+++ b/setup.py
@@ -60,4 +60,9 @@ setup(
     test_suite='tests',
     tests_require=test_requirements,
     setup_requires=setup_requirements,
+    entry_points={
+        'console_scripts': [
+            'rctools = rhg_compute_tools.cli:rctools_cli',
+        ]
+    },
 )

Add command line gcs tools
would be great to do something along the lines of

```bash
rhg-compute-tools gcs rsync src dst
```

Which suggests we probably need a broader command line strategy for handling different submodules
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..786f38b
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,79 @@
+"""
+White-box testing to ensure that CLI passes variables correctly
+"""
+
+
+import pytest
+import click
+from click.testing import CliRunner
+import rhg_compute_tools.cli
+
+
+@pytest.fixture
+def replicate_directory_structure_on_gcs_stub(mocker):
+    mocker.patch.object(rhg_compute_tools.cli,
+                        'replicate_directory_structure_on_gcs',
+                        new=lambda *args: click.echo(';'.join(args)))
+
+
+@pytest.fixture
+def tempfl_path(tmpdir):
+    """Creates a temporary file, returning its path"""
+    file = tmpdir.join('file.json')
+    file.write('foobar')
+    return str(file)
+
+
+@pytest.mark.parametrize('credflag', ['-c', '--credentials', None])
+def test_repdirstruc(credflag, replicate_directory_structure_on_gcs_stub,
+                     tmpdir, tempfl_path, monkeypatch):
+    """Test rctools gcs repdirstruc for main input options
+    """
+    # Setup CLI args
+    cred_path = str(tempfl_path)
+    credargs = [credflag, cred_path]
+    src_path = str(tmpdir)
+    dst_path = 'gcs://foo/bar'
+
+    if credflag is None:
+        monkeypatch.setenv('GOOGLE_APPLICATION_CREDENTIALS', cred_path)
+        credargs = []
+
+    # Run CLI
+    runner = CliRunner()
+    result = runner.invoke(
+        rhg_compute_tools.cli.rctools_cli,
+        ['gcs', 'repdirstruc'] + credargs + [src_path, dst_path],
+    )
+
+    expected_output = ';'.join([src_path, dst_path, cred_path]) + '\n'
+    assert result.output == expected_output
+
+
+@pytest.mark.parametrize('credflag', ['-c', '--credentials', None])
+def test_repdirstruc_nocredfile(credflag, replicate_directory_structure_on_gcs_stub,
+                                tmpdir, monkeypatch):
+    """Test rctools gcs repdirstruc for graceful fail when cred file missing
+    """
+    # Setup CLI args
+    cred_path = '_foobar.json'
+    credargs = [credflag, cred_path]
+    src_path = str(tmpdir)
+    dst_path = 'gcs://foo/bar'
+
+    if credflag is None:
+        monkeypatch.setenv('GOOGLE_APPLICATION_CREDENTIALS', cred_path)
+        credargs = []
+
+    # Run CLI
+    runner = CliRunner()
+    result = runner.invoke(
+        rhg_compute_tools.cli.rctools_cli,
+        ['gcs', 'repdirstruc'] + credargs + [src_path, dst_path],
+    )
+
+    expected_output = 'Usage: rctools-cli gcs repdirstruc [OPTIONS] SRC DST\nTry' \
+                      ' "rctools-cli gcs repdirstruc -h" for help.\n\nError: ' \
+                      'Invalid value for "-c" / "--credentials": Path ' \
+                      '"_foobar.json" does not exist.\n'
+    assert result.output == expected_output

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock pytest
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cachetools==4.2.4
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
click==8.1.8
cloudpickle==2.2.1
coverage==7.2.7
cycler==0.11.0
dask==2022.2.0
dask-kubernetes==0.8.0
distributed==2022.2.0
durationpy==0.9
exceptiongroup==1.2.2
fsspec==2023.1.0
google-api-core==2.10.2
google-auth==1.35.0
google-cloud-core==1.7.3
google-cloud-storage==1.16.1
google-crc32c==1.5.0
google-resumable-media==2.7.2
googleapis-common-protos==1.69.2
HeapDict==1.0.1
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
Jinja2==3.1.6
kiwisolver==1.4.5
kubernetes==32.0.1
locket==1.0.0
MarkupSafe==2.1.5
matplotlib==3.0.3
msgpack==1.0.5
numpy==1.21.6
oauthlib==3.2.2
packaging==24.0
partd==1.4.1
pluggy==1.2.0
protobuf==4.24.4
psutil==7.0.0
pyasn1==0.5.1
pyasn1-modules==0.3.0
pyparsing==3.1.4
pytest==7.4.4
pytest-cov==4.1.0
pytest-mock==3.11.1
python-dateutil==2.9.0.post0
PyYAML==6.0.1
requests==2.31.0
requests-oauthlib==2.0.0
-e git+https://github.com/RhodiumGroup/rhg_compute_tools.git@502b8bd0d54417db4e71ce82a363a470314f50e2#egg=rhg_compute_tools
rsa==4.9
six==1.17.0
sortedcontainers==2.4.0
tblib==2.0.0
tomli==2.0.1
toolz==0.12.1
tornado==6.2
typing_extensions==4.7.1
urllib3==2.0.7
websocket-client==1.6.1
zict==2.2.0
zipp==3.15.0

name: rhg_compute_tools
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==4.2.4
      - charset-normalizer==3.4.1
      - click==8.1.8
      - cloudpickle==2.2.1
      - coverage==7.2.7
      - cycler==0.11.0
      - dask==2022.2.0
      - dask-kubernetes==0.8.0
      - distributed==2022.2.0
      - durationpy==0.9
      - exceptiongroup==1.2.2
      - fsspec==2023.1.0
      - google-api-core==2.10.2
      - google-auth==1.35.0
      - google-cloud-core==1.7.3
      - google-cloud-storage==1.16.1
      - google-crc32c==1.5.0
      - google-resumable-media==2.7.2
      - googleapis-common-protos==1.69.2
      - heapdict==1.0.1
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - jinja2==3.1.6
      - kiwisolver==1.4.5
      - kubernetes==32.0.1
      - locket==1.0.0
      - markupsafe==2.1.5
      - matplotlib==3.0.3
      - msgpack==1.0.5
      - numpy==1.21.6
      - oauthlib==3.2.2
      - packaging==24.0
      - partd==1.4.1
      - pluggy==1.2.0
      - protobuf==4.24.4
      - psutil==7.0.0
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pyparsing==3.1.4
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.1
      - requests==2.31.0
      - requests-oauthlib==2.0.0
      - rsa==4.9
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tblib==2.0.0
      - tomli==2.0.1
      - toolz==0.12.1
      - tornado==6.2
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - websocket-client==1.6.1
      - zict==2.2.0
      - zipp==3.15.0
prefix: /opt/conda/envs/rhg_compute_tools

tests/test_cli.py::test_repdirstruc[-c] tests/test_cli.py::test_repdirstruc[--credentials] tests/test_cli.py::test_repdirstruc[None]
tests/test_cli.py::test_repdirstruc_nocredfile[-c] tests/test_cli.py::test_repdirstruc_nocredfile[--credentials] tests/test_cli.py::test_repdirstruc_nocredfile[None]
HISTORY.rst requirements_dev.txt setup.py rhg_compute_tools/cli.py requirements.txt rhg_compute_tools/gcs.py
HISTORY.rst requirements_dev.txt setup.py rhg_compute_tools/cli.py requirements.txt rhg_compute_tools/gcs.py