diff --git a/rocrate/cli.py b/rocrate/cli.py
index 9e63e54..8ff4e88 100644
--- a/rocrate/cli.py
+++ b/rocrate/cli.py
@@ -69,6 +69,36 @@ def add():
     pass
 
 
+@add.command()
+@click.argument('path', type=click.Path(exists=True, dir_okay=False))
+@OPTION_CRATE_PATH
+def file(crate_dir, path):
+    crate = ROCrate(crate_dir, init=False, gen_preview=False)
+    source = Path(path).resolve(strict=True)
+    try:
+        dest_path = source.relative_to(crate_dir)
+    except ValueError:
+        # For now, only support adding an existing file to the metadata
+        raise ValueError(f"{source} is not in the crate dir {crate_dir}")
+    crate.add_file(source, dest_path)
+    crate.metadata.write(crate_dir)
+
+
+@add.command()
+@click.argument('path', type=click.Path(exists=True, file_okay=False))
+@OPTION_CRATE_PATH
+def dataset(crate_dir, path):
+    crate = ROCrate(crate_dir, init=False, gen_preview=False)
+    source = Path(path).resolve(strict=True)
+    try:
+        dest_path = source.relative_to(crate_dir)
+    except ValueError:
+        # For now, only support adding an existing directory to the metadata
+        raise ValueError(f"{source} is not in the crate dir {crate_dir}")
+    crate.add_dataset(source, dest_path)
+    crate.metadata.write(crate_dir)
+
+
 @add.command()
 @click.argument('path', type=click.Path(exists=True))
 @click.option('-l', '--language', type=click.Choice(LANG_CHOICES), default="cwl")

diff --git a/test/test_cli.py b/test/test_cli.py
index 12e97ba..b6b8b69 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -20,6 +20,7 @@ import json
 import click
 from click.testing import CliRunner
 import pytest
+import shutil
 
 from rocrate.cli import cli
 from rocrate.model import File
@@ -132,6 +133,58 @@ def test_cli_init_exclude(test_data_dir, helpers):
         assert not e.id.startswith("test")
 
 
+@pytest.mark.parametrize("cwd", [False, True])
+def test_cli_add_file(tmpdir, test_data_dir, helpers, monkeypatch, cwd):
+    # init
+    crate_dir = tmpdir / "crate"
+    crate_dir.mkdir()
+    runner = CliRunner()
+    assert runner.invoke(cli, ["init", "-c", str(crate_dir)]).exit_code == 0
+    json_entities = helpers.read_json_entities(crate_dir)
+    assert set(json_entities) == {"./", "ro-crate-metadata.json"}
+    # add
+    shutil.copy(test_data_dir / "sample_file.txt", crate_dir)
+    file_path = crate_dir / "sample_file.txt"
+    args = ["add", "file"]
+    if cwd:
+        monkeypatch.chdir(str(crate_dir))
+        file_path = file_path.relative_to(crate_dir)
+    else:
+        args.extend(["-c", str(crate_dir)])
+    args.append(str(file_path))
+    result = runner.invoke(cli, args)
+    assert result.exit_code == 0
+    json_entities = helpers.read_json_entities(crate_dir)
+    assert "sample_file.txt" in json_entities
+    assert json_entities["sample_file.txt"]["@type"] == "File"
+
+
+@pytest.mark.parametrize("cwd", [False, True])
+def test_cli_add_dataset(tmpdir, test_data_dir, helpers, monkeypatch, cwd):
+    # init
+    crate_dir = tmpdir / "crate"
+    crate_dir.mkdir()
+    runner = CliRunner()
+    assert runner.invoke(cli, ["init", "-c", str(crate_dir)]).exit_code == 0
+    json_entities = helpers.read_json_entities(crate_dir)
+    assert set(json_entities) == {"./", "ro-crate-metadata.json"}
+    # add
+    dataset_path = crate_dir / "test_add_dir"
+    shutil.copytree(test_data_dir / "test_add_dir", dataset_path)
+    args = ["add", "dataset"]
+    if cwd:
+        monkeypatch.chdir(str(crate_dir))
+        dataset_path = dataset_path.relative_to(crate_dir)
+    else:
+        args.extend(["-c", str(crate_dir)])
+    args.append(str(dataset_path))
+    result = runner.invoke(cli, args)
+    assert result.exit_code == 0
+    json_entities = helpers.read_json_entities(crate_dir)
+    assert "test_add_dir/" in json_entities
+    assert json_entities["test_add_dir/"]["@type"] == "Dataset"
+
+
 @pytest.mark.parametrize("cwd", [False, True])
 def test_cli_add_workflow(test_data_dir, helpers, monkeypatch, cwd):
     # init

CLI subcommands to add files and directories
Add subcommands to allow this:

```bash
mkdir crate
cd crate
rocrate init  # generates minimal metadata file and nothing else
cp /some/other/path/file1 .
cp -rf /some/path/dir1 .
rocrate add file file1
rocrate add dataset dir1
```

test/test_cli.py::test_cli_help[command_path6] test/test_cli.py::test_cli_help[command_path7] test/test_cli.py::test_cli_add_file[False] test/test_cli.py::test_cli_add_file[True] test/test_cli.py::test_cli_add_dataset[False] test/test_cli.py::test_cli_add_dataset[True]
test/test_cli.py::test_cli_help[command_path0] test/test_cli.py::test_cli_help[command_path1] test/test_cli.py::test_cli_help[command_path2] test/test_cli.py::test_cli_help[command_path3] test/test_cli.py::test_cli_help[command_path4] test/test_cli.py::test_cli_help[command_path5] test/test_cli.py::test_cli_init[False-False] test/test_cli.py::test_cli_init[False-True] test/test_cli.py::test_cli_init[True-False] test/test_cli.py::test_cli_init[True-True] test/test_cli.py::test_cli_init_exclude test/test_cli.py::test_cli_add_workflow[False] test/test_cli.py::test_cli_add_workflow[True] test/test_cli.py::test_cli_add_test_metadata[False] test/test_cli.py::test_cli_add_test_metadata[True] test/test_cli.py::test_cli_add_test_metadata_explicit_ids[False] test/test_cli.py::test_cli_add_test_metadata_explicit_ids[True] test/test_cli.py::test_cli_write_zip[False] test/test_cli.py::test_cli_write_zip[True]