Repository: oumi-ai/oumi
Pull request: Added fetch command and modified infer command to resolve oumi://
# Description

<!--
Thank you for contributing to Oumi! Before sending your PR out for review, please take a quick read through this template.

When your PR is merged, its title will appear in our release notes. Make sure your title gives a clear description of your change!

After you've updated your title, please replace this section with a detailed description of your change. Include as much context as possible so your reviewers can easily understand *what* you're changing and *why*.
The more information you provide, the faster we can review your change!
-->
<!--↓↓↓↓↓↓↓↓↓↓ Describe your change below ↓↓↓↓↓↓↓↓↓↓-->
A new CLI endpoint ```fetch``` has been added to allows users to directly download configs from the github repo. The same function is then reused to allow the resolution of ```oumi://``` to download a the config file from the github repo and provide the address of this newly downloaded config file for further use by the ```infer``` command.

A test has been added in the ```tests_cli_main.py``` to check for successful registration of the ```fetch``` entry-point.

A new test file has been added named ```tests_cli_fetch.py``` that has three tests that cover the cases of default directory, explicit ```--output-dir``` passing and passing output directory via ```OUMI_DIR``` environment variable.
<!--↑↑↑↑↑↑↑↑↑↑ Describe your change above ↑↑↑↑↑↑↑↑↑↑-->

## Related issues

<!--
Make sure to list any relevant related issues to your change. More often than not this will be the single issue fixed by your PR.
-->
<!--↓↓↓↓↓↓↓↓↓↓ List your related issues below ↓↓↓↓↓↓↓↓↓↓-->

Fixes #1374 (issue)

<!--↑↑↑↑↑↑↑↑↑↑ List your related issues above ↑↑↑↑↑↑↑↑↑↑-->

## Before submitting

- [ ] This PR only changes documentation. (You can ignore the following checks in that case)
- [x] Did you read the [contributor guideline](https://github.com/oumi-ai/oumi/blob/main/CONTRIBUTING.md) Pull Request guidelines?
- [x] Did you link the issue(s) related to this PR in the section above?
- [x] Did you add / update tests where needed?

## Reviewers

At least one review from a member of `oumi-ai/oumi-staff` is required.

<!-- Add `oumi-ai/oumi-staff` as a reviewer when your PR is ready for review.

You are also welcome to add individual members of `oumi-ai/oumi-staff` as reviewers.

If no one has reviewed your PR after several days, feel free to add a comment tagging specific reviewers.

 -->


Problem statement:
[Feature] Add a new  "oumi fetch" command to pull configs via the CLI
### Feature request

Create a new CLI command `oumi fetch` that will download configs from the oumi github repo to a local directory specified by an environment variable.

Any CLI actions using the `oumi://` prefix will look for the config in that folder.



### Motivation / references

This makes running Oumi much less disruptive. Users could even set the environment variable to a clone of the oumi repo to run the CLI anywhere. Consider the following:

```
OUMI_DIR=~/.oumi/configs


oumi infer -c oumi://smollm/inference/135m_infer.yaml \
  --generation.max_new_tokens 40 \
  --generation.temperature 0.7 \
  --interactive
```

### Your contribution

This change requires:

- Adding a new CLI entrypoint
- Updating the config loading logic for each CLI entrypoint to check for the `oumi://` prefix and resolve it accordingly.

Hints from issue discussion:
I recently worked on a CLI related issue so the steps for this issue seem clear to me. Can this be assigned to me? I already have the code for this (I was just playing around to see if my process was correct😅)
Definitely, thanks for volunteering, @Spaarsh ! Let me know if you have any questions about this task!

Commit message: Adding fetch registration test

Patch to review:
diff --git a/src/oumi/cli/cli_utils.py b/src/oumi/cli/cli_utils.py
index c890b1dbc..d32e78681 100644
--- a/src/oumi/cli/cli_utils.py
+++ b/src/oumi/cli/cli_utils.py
@@ -15,6 +15,7 @@
 import logging
 import os
 from enum import Enum
+from pathlib import Path
 from typing import Annotated, Optional
 
 import typer
@@ -137,3 +138,46 @@ def set_log_level(level: Optional[LogLevel]):
         callback=set_log_level,
     ),
 ]
+
+
+def resolve_oumi_prefix(
+    config_path: str, output_dir: Optional[Path] = None
+) -> tuple[str, Path]:
+    """Resolves oumi:// prefix and determines output directory.
+
+    Args:
+        config_path: Path that may contain oumi:// prefix
+        output_dir: Optional output directory override
+
+    Returns:
+        tuple[str, Path]: (cleaned path, output directory)
+    """
+    config_path = config_path[7:]
+
+    config_dir = output_dir or os.environ.get("OUMI_DIR") or "~/.oumi/configs"
+    config_dir = Path(config_dir).expanduser()
+    config_dir.mkdir(parents=True, exist_ok=True)
+
+    return config_path, config_dir
+
+
+def resolve_and_fetch_config(
+    config_path: str, output_dir: Optional[Path] = None
+) -> Path:
+    """Resolve oumi:// prefix and fetch config if needed.
+
+    Args:
+        config_path: Original config path that may contain oumi:// prefix
+        output_dir: Optional override for output directory
+
+    Returns:
+        Path: Local path to the config file
+    """
+    if not config_path.startswith("oumi://"):
+        return Path(config_path)
+
+    from oumi.cli.fetch import fetch
+
+    fetch(config_path, output_dir)
+
+    return Path(config_path)
diff --git a/src/oumi/cli/fetch.py b/src/oumi/cli/fetch.py
new file mode 100644
index 000000000..d42af5767
--- /dev/null
+++ b/src/oumi/cli/fetch.py
@@ -0,0 +1,82 @@
+# Copyright 2025 - Oumi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+from typing import Annotated, Optional
+
+import requests
+import typer
+import yaml
+
+from oumi.cli.cli_utils import resolve_oumi_prefix
+from oumi.utils.logging import logger
+
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
+OUMI_DIR = "~/.oumi/configs"
+
+
+def fetch(
+    config_path: Annotated[
+        str,
+        typer.Argument(
+            help="Path to config (e.g. oumi://smollm/inference/135m_infer.yaml)"
+        ),
+    ],
+    output_dir: Annotated[
+        Optional[Path],
+        typer.Option(
+            "--output-dir",
+            "-o",
+            help=(
+                "Directory to save configs "
+                "(defaults to OUMI_DIR env var or ~/.oumi/configs)"
+            ),
+        ),
+    ] = None,
+) -> None:
+    """Fetch configuration files from GitHub repository."""
+    # Remove oumi:// prefix if present
+    if config_path.startswith("oumi://"):
+        config_path, config_dir = resolve_oumi_prefix(config_path, output_dir)
+
+    else:
+        # raise error
+        logger.error("Invalid config path")
+        raise typer.Exit(1)
+
+    try:
+        # Fetch from GitHub
+        github_url = f"{OUMI_GITHUB_RAW}/{config_path}"
+        response = requests.get(github_url)
+        response.raise_for_status()
+        config_content = response.text
+
+        # Validate YAML
+        yaml.safe_load(config_content)
+
+        # Save to destination
+        local_path = (config_dir or Path(OUMI_DIR).expanduser()) / config_path
+        local_path.parent.mkdir(parents=True, exist_ok=True)
+
+        with open(local_path, "w") as f:
+            f.write(config_content)
+
+        logger.info(f"Successfully downloaded config to {local_path}")
+
+    except requests.RequestException as e:
+        logger.error(f"Failed to download config from GitHub: {e}")
+        raise typer.Exit(1)
+    except yaml.YAMLError:
+        logger.error("Invalid YAML configuration")
+        raise typer.Exit(1)
diff --git a/src/oumi/cli/infer.py b/src/oumi/cli/infer.py
index b521cb0ea..59dbe5f07 100644
--- a/src/oumi/cli/infer.py
+++ b/src/oumi/cli/infer.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import os
+from pathlib import Path
 from typing import Annotated, Final, Optional
 
 import typer
@@ -21,6 +22,8 @@
 from oumi.utils.logging import logger
 
 _DEFAULT_CLI_PDF_DPI: Final[int] = 200
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
+OUMI_DIR = "~/.oumi/configs"
 
 
 def infer(
@@ -32,6 +35,16 @@ def infer(
             help="Path to the configuration file for inference.",
         ),
     ] = None,
+    output_dir: Annotated[
+        Optional[Path],
+        typer.Option(
+            "--output-dir",
+            help=(
+                "Directory to save configs "
+                "(defaults to OUMI_DIR env var or ~/.oumi/configs)"
+            ),
+        ),
+    ] = None,
     interactive: Annotated[
         bool,
         typer.Option("-i", "--interactive", help="Run in an interactive session."),
@@ -67,6 +80,8 @@ def infer(
     Args:
         ctx: The Typer context object.
         config: Path to the configuration file for inference.
+        output_dir: Directory to save configs
+        (defaults to OUMI_DIR env var or ~/.oumi/configs).
         interactive: Whether to run in an interactive session.
         image: Path to the input image for `image+text` VLLMs.
         system_prompt: System prompt for task-specific instructions.
@@ -74,6 +89,12 @@ def infer(
     """
     extra_args = cli_utils.parse_extra_cli_args(ctx)
 
+    if config:
+        if config.startswith("oumi://"):
+            _ = cli_utils.resolve_and_fetch_config(config, output_dir)
+            cleaned_path, config_dir = cli_utils.resolve_oumi_prefix(config, output_dir)
+            config = str(config_dir / cleaned_path)
+
     # Delayed imports
     from oumi import infer as oumi_infer
     from oumi import infer_interactive as oumi_infer_interactive
diff --git a/src/oumi/cli/main.py b/src/oumi/cli/main.py
index 4e566346b..5cb912d1f 100644
--- a/src/oumi/cli/main.py
+++ b/src/oumi/cli/main.py
@@ -21,6 +21,7 @@
 from oumi.cli.distributed_run import accelerate, torchrun
 from oumi.cli.env import env
 from oumi.cli.evaluate import evaluate
+from oumi.cli.fetch import fetch
 from oumi.cli.infer import infer
 from oumi.cli.judge import conversations, dataset, model
 from oumi.cli.launch import cancel, down, status, stop, up, which
@@ -108,6 +109,11 @@ def get_app() -> typer.Typer:
             "with reasonable default values for distributed training."
         ),
     )
+
+    app.command(
+        help="Fetch configuration files from GitHub repository.",
+    )(fetch)
+
     return app
 
 
diff --git a/tests/unit/cli/test_cli_fetch.py b/tests/unit/cli/test_cli_fetch.py
new file mode 100644
index 000000000..8267b6bfc
--- /dev/null
+++ b/tests/unit/cli/test_cli_fetch.py
@@ -0,0 +1,86 @@
+import tempfile
+from pathlib import Path
+from unittest.mock import Mock, patch
+
+import pytest
+import typer
+from typer.testing import CliRunner
+
+from oumi.cli.fetch import fetch
+
+runner = CliRunner()
+
+
+@pytest.fixture
+def app():
+    fake_app = typer.Typer()
+    fake_app.command()(fetch)
+    return fake_app
+
+
+@pytest.fixture
+def mock_response():
+    response = Mock()
+    response.text = "key: value"
+    response.raise_for_status.return_value = None
+    return response
+
+
+@pytest.fixture
+def mock_requests(mock_response):
+    with patch("oumi.cli.fetch.requests") as mock:
+        mock.get.return_value = mock_response
+        yield mock
+
+
+def test_fetch_with_explicit_output_dir(app, mock_requests):
+    with tempfile.TemporaryDirectory() as temp_dir:
+        # Given
+        output_dir = Path(temp_dir)
+        config_path = "oumi://smollm/inference/135m_infer.yaml"
+        expected_path = output_dir / "smollm/inference/135m_infer.yaml"
+
+        # When
+        result = runner.invoke(app, [config_path, "-o", str(output_dir)])
+
+        # Then
+        assert result.exit_code == 0
+        mock_requests.get.assert_called_once()
+        assert expected_path.exists()
+
+
+def test_fetch_with_oumi_dir_env(app, mock_requests, monkeypatch):
+    with tempfile.TemporaryDirectory() as temp_dir:
+        # Given
+        config_path = "oumi://smollm/inference/135m_infer.yaml"
+        expected_path = Path(temp_dir) / "smollm/inference/135m_infer.yaml"
+        monkeypatch.setenv("OUMI_DIR", temp_dir)
+
+        # When
+        result = runner.invoke(app, [config_path])
+
+        # Then
+        assert result.exit_code == 0
+        mock_requests.get.assert_called_once()
+        assert expected_path.exists()
+
+
+def test_fetch_with_default_dir(app, mock_requests, monkeypatch):
+    # Given
+    config_path = "oumi://smollm/inference/135m_infer.yaml"
+    expected_path = Path.home() / ".oumi/configs/smollm/inference/135m_infer.yaml"
+    monkeypatch.delenv("OUMI_DIR", raising=False)
+
+    # When
+    result = runner.invoke(app, [config_path])
+
+    # Then
+    assert result.exit_code == 0
+    mock_requests.get.assert_called_once()
+    assert expected_path.exists()
+
+    # Cleanup
+    if expected_path.exists():
+        expected_path.unlink()
+    if expected_path.parent.exists():
+        expected_path.parent.rmdir()
diff --git a/tests/unit/cli/test_cli_main.py b/tests/unit/cli/test_cli_main.py
index eee903c58..be53378cd 100644
--- a/tests/unit/cli/test_cli_main.py
+++ b/tests/unit/cli/test_cli_main.py
@@ -8,6 +8,7 @@
 from oumi.cli.distributed_run import accelerate, torchrun
 from oumi.cli.env import env
 from oumi.cli.evaluate import evaluate
+from oumi.cli.fetch import fetch
 from oumi.cli.infer import infer
 from oumi.cli.judge import conversations, dataset
 from oumi.cli.launch import cancel, down, status, stop, up, which
@@ -49,6 +50,13 @@ def mock_infer():
         yield m_infer
 
 
+@pytest.fixture
+def mock_fetch():
+    with patch("oumi.cli.main.fetch") as m_fetch:
+        _copy_command(m_fetch, fetch)
+        yield m_fetch
+
+
 @pytest.fixture
 def mock_down():
     with patch("oumi.cli.main.down") as m_down:
@@ -147,6 +155,11 @@ def test_main_infer_registered(mock_infer):
     mock_infer.assert_called_once()
 
 
+def test_main_fetch_registered(mock_fetch):
+    _ = runner.invoke(get_app(), ["fetch", "some/path", "--output-dir", "output/path"])
+    mock_fetch.assert_called_once()
+
+
 def test_main_eval_registered(mock_eval):
     _ = runner.invoke(
         get_app(), ["eval", "--config", "some/path", "--allow_extra" "args"]


Code review comments:
diff_hunk: @@ -137,3 +138,46 @@ def set_log_level(level: Optional[LogLevel]):
         callback=set_log_level,
     ),
 ]
+
+
+def resolve_oumi_prefix(
+    config_path: str, output_dir: Optional[Path] = None
+) -> tuple[str, Path]:
+    """Resolves oumi:// prefix and determines output directory.
+
+    Args:
+        config_path: Path that may contain oumi:// prefix
+        output_dir: Optional output directory override
+
+    Returns:
+        tuple[str, Path]: (cleaned path, output directory)
+    """
+    config_path = config_path[7:]
+
+    config_dir = output_dir or os.environ.get("OUMI_DIR") or "~/.oumi/configs"
+    config_dir = Path(config_dir).expanduser()
+    config_dir.mkdir(parents=True, exist_ok=True)
+
+    return config_path, config_dir
+
+
+def resolve_and_fetch_config(
+    config_path: str, output_dir: Optional[Path] = None
+) -> Path:
+    """Resolve oumi:// prefix and fetch config if needed.
+
+    Args:
+        config_path: Original config path that may contain oumi:// prefix
+        output_dir: Optional override for output directory
+
+    Returns:
+        Path: Local path to the config file
+    """
+    if not config_path.startswith("oumi://"):
path: src/oumi/cli/cli_utils.py
text: @user1:
```suggestion
    if not config_path.lower().startswith("oumi://"):
```

diff_hunk: @@ -0,0 +1,82 @@
+# Copyright 2025 - Oumi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+from typing import Annotated, Optional
+
+import requests
+import typer
+import yaml
+
+from oumi.cli.cli_utils import resolve_oumi_prefix
+from oumi.utils.logging import logger
+
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
+OUMI_DIR = "~/.oumi/configs"
+
+
+def fetch(
+    config_path: Annotated[
+        str,
+        typer.Argument(
+            help="Path to config (e.g. oumi://smollm/inference/135m_infer.yaml)"
+        ),
+    ],
+    output_dir: Annotated[
+        Optional[Path],
+        typer.Option(
+            "--output-dir",
+            "-o",
+            help=(
+                "Directory to save configs "
+                "(defaults to OUMI_DIR env var or ~/.oumi/configs)"
+            ),
+        ),
+    ] = None,
+) -> None:
+    """Fetch configuration files from GitHub repository."""
+    # Remove oumi:// prefix if present
+    if config_path.startswith("oumi://"):
+        config_path, config_dir = resolve_oumi_prefix(config_path, output_dir)
+
+    else:
+        # raise error
+        logger.error("Invalid config path")
+        raise typer.Exit(1)
path: src/oumi/cli/fetch.py
text: @user1:
With my change above, you can simplify this since `resolve_oumi_prefix(...)` now handles with and without the oumi prefix:

```suggestion
    config_path, config_dir = resolve_oumi_prefix(config_path, output_dir)
```

diff_hunk: @@ -67,13 +80,21 @@ def infer(
     Args:
         ctx: The Typer context object.
         config: Path to the configuration file for inference.
+        output_dir: Directory to save configs
+        (defaults to OUMI_DIR env var or ~/.oumi/configs).
         interactive: Whether to run in an interactive session.
         image: Path to the input image for `image+text` VLLMs.
         system_prompt: System prompt for task-specific instructions.
         level: The logging level for the specified command.
     """
     extra_args = cli_utils.parse_extra_cli_args(ctx)
 
+    if config:
+        if config.startswith("oumi://"):
+            _ = cli_utils.resolve_and_fetch_config(config, output_dir)
+            cleaned_path, config_dir = cli_utils.resolve_oumi_prefix(config, output_dir)
+            config = str(config_dir / cleaned_path)
path: src/oumi/cli/infer.py
text: @user1:
With my suggested changes to let `resolve_and_fetch_config` handle all prefixes, we can simplify this:

```suggestion
        config = cli_utils.resolve_and_fetch_config(config, output_dir)
```

diff_hunk: @@ -21,6 +22,8 @@
 from oumi.utils.logging import logger
 
 _DEFAULT_CLI_PDF_DPI: Final[int] = 200
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
+OUMI_DIR = "~/.oumi/configs"
path: src/oumi/cli/infer.py
text: @user1:
Let's delete these, I believe they're unused in this file

diff_hunk: @@ -108,6 +109,11 @@ def get_app() -> typer.Typer:
             "with reasonable default values for distributed training."
         ),
     )
+
+    app.command(
+        help="Fetch configuration files from GitHub repository.",
path: src/oumi/cli/main.py
text: @user1:
```suggestion
        help="Fetch configuration files from the oumi GitHub repository.",
```

Small nit :)

diff_hunk: @@ -0,0 +1,82 @@
+# Copyright 2025 - Oumi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+from typing import Annotated, Optional
+
+import requests
+import typer
+import yaml
+
+from oumi.cli.cli_utils import resolve_oumi_prefix
+from oumi.utils.logging import logger
+
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
+OUMI_DIR = "~/.oumi/configs"
+
+
+def fetch(
+    config_path: Annotated[
+        str,
+        typer.Argument(
+            help="Path to config (e.g. oumi://smollm/inference/135m_infer.yaml)"
+        ),
+    ],
+    output_dir: Annotated[
+        Optional[Path],
+        typer.Option(
+            "--output-dir",
+            "-o",
+            help=(
+                "Directory to save configs "
+                "(defaults to OUMI_DIR env var or ~/.oumi/configs)"
+            ),
+        ),
+    ] = None,
+) -> None:
+    """Fetch configuration files from GitHub repository."""
+    # Remove oumi:// prefix if present
+    if config_path.startswith("oumi://"):
+        config_path, config_dir = resolve_oumi_prefix(config_path, output_dir)
+
+    else:
+        # raise error
+        logger.error("Invalid config path")
+        raise typer.Exit(1)
+
+    try:
+        # Fetch from GitHub
+        github_url = f"{OUMI_GITHUB_RAW}/{config_path}"
path: src/oumi/cli/fetch.py
text: @user1:
```suggestion
        github_url = f"{OUMI_GITHUB_RAW}/{config_path.lstrip('/')}"
```
This lets us gracefully handle the user input if there are any leading slashes

diff_hunk: @@ -0,0 +1,82 @@
+# Copyright 2025 - Oumi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+from typing import Annotated, Optional
+
+import requests
+import typer
+import yaml
+
+from oumi.cli.cli_utils import resolve_oumi_prefix
+from oumi.utils.logging import logger
+
+OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main/configs/recipes"
path: src/oumi/cli/fetch.py
text: @user1:
```suggestion
OUMI_GITHUB_RAW = "https://raw.githubusercontent.com/oumi-ai/oumi/main"
```

Let's make this the top level directory so users can request samples from `/configs/examples` or `/configs/projects` as well

diff_hunk: @@ -137,3 +138,46 @@ def set_log_level(level: Optional[LogLevel]):
         callback=set_log_level,
     ),
 ]
+
+
+def resolve_oumi_prefix(
+    config_path: str, output_dir: Optional[Path] = None
+) -> tuple[str, Path]:
+    """Resolves oumi:// prefix and determines output directory.
+
+    Args:
+        config_path: Path that may contain oumi:// prefix
+        output_dir: Optional output directory override
+
+    Returns:
+        tuple[str, Path]: (cleaned path, output directory)
+    """
+    config_path = config_path[7:]
path: src/oumi/cli/cli_utils.py
text: @user1:
```suggestion
    oumi_prefix = "oumi://"
    if config_path.lower().startswith(oumi_prefix):
        config_path = config_path[len(oumi_prefix):]
```

This allows users to use the `oumi://` prefix when fetching, but it's not required.

So they could call `oumi fetch /configs/recipes/some_config.yaml`
or 
`oumi fetch oumi://configs/recipes/some_config.yaml`