From 0c8d407793b287f751b7b5844cc21f480659bcb3 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 17 Mar 2026 20:40:21 -0700 Subject: [PATCH] Rename cli/ to unsloth_cli/ to fix namespace collision with stringzilla (#4393) * Rename cli/ to unsloth_cli/ to fix namespace collision with stringzilla stringzilla installs a namespace package at cli/ (cli/split.py, cli/wc.py) in site-packages without an __init__.py. When unsloth is installed as an editable package (pip install -e .), the entry point script does `from cli import app` which finds stringzilla's namespace cli/ first and fails with `ImportError: cannot import name 'app' from 'cli'`. Non-editable installs happened to work because unsloth's cli/__init__.py overwrites the namespace directory, but this is fragile and breaks if stringzilla is installed after unsloth. Renaming to unsloth_cli/ avoids the collision entirely and fixes both editable and non-editable install paths. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update stale cli/ references in comments and license files --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- COPYING | 2 +- LICENSE | 2 +- cli.py | 2 +- pyproject.toml | 2 +- {cli => unsloth_cli}/__init__.py | 10 +++++----- {cli => unsloth_cli}/commands/__init__.py | 0 {cli => unsloth_cli}/commands/export.py | 0 {cli => unsloth_cli}/commands/inference.py | 0 {cli => unsloth_cli}/commands/studio.py | 4 ++-- {cli => unsloth_cli}/commands/train.py | 4 ++-- {cli => unsloth_cli}/commands/ui.py | 6 +++++- {cli => unsloth_cli}/config.py | 0 {cli => unsloth_cli}/options.py | 0 13 files changed, 18 insertions(+), 14 deletions(-) rename {cli => unsloth_cli}/__init__.py (68%) rename {cli => unsloth_cli}/commands/__init__.py (100%) rename {cli => unsloth_cli}/commands/export.py (100%) rename {cli => unsloth_cli}/commands/inference.py (100%) rename {cli => unsloth_cli}/commands/studio.py (97%) rename {cli => unsloth_cli}/commands/train.py (97%) rename {cli => unsloth_cli}/commands/ui.py (96%) rename {cli => unsloth_cli}/config.py (100%) rename {cli => unsloth_cli}/options.py (100%) diff --git a/COPYING b/COPYING index cb7331bd3..4213fd700 100644 --- a/COPYING +++ b/COPYING @@ -661,4 +661,4 @@ For more information on this, and how to apply and follow the GNU AGPL, see . Files under unsloth/*, tests/*, scripts/* are Apache 2.0 licensed. -Files under studio/*, cli/* which is optional to install are AGPLv3 licensed. \ No newline at end of file +Files under studio/*, unsloth_cli/* which is optional to install are AGPLv3 licensed. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8c3c43e37..e1600b0c0 100644 --- a/LICENSE +++ b/LICENSE @@ -188,7 +188,7 @@ Copyright [2024-] [Unsloth AI. Inc team, Daniel Han-Chen & Michael Han-Chen] Files under unsloth/*, tests/*, scripts/* are Apache 2.0 licensed. - Files under studio/*, cli/* which is optional to install are AGPLv3 licensed. + Files under studio/*, unsloth_cli/* which is optional to install are AGPLv3 licensed. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cli.py b/cli.py index 0965b424e..534babed2 100644 --- a/cli.py +++ b/cli.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 -from cli import app +from unsloth_cli import app if __name__ == "__main__": app() diff --git a/pyproject.toml b/pyproject.toml index f7112aa89..d9898c6a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ ] [project.scripts] -unsloth = "cli:app" +unsloth = "unsloth_cli:app" [tool.setuptools.dynamic] version = {attr = "unsloth.models._utils.__version__"} diff --git a/cli/__init__.py b/unsloth_cli/__init__.py similarity index 68% rename from cli/__init__.py rename to unsloth_cli/__init__.py index 434ee4d2e..3b9043c5b 100644 --- a/cli/__init__.py +++ b/unsloth_cli/__init__.py @@ -3,11 +3,11 @@ import typer -from cli.commands.train import train -from cli.commands.inference import inference -from cli.commands.export import export, list_checkpoints -from cli.commands.ui import ui -from cli.commands.studio import studio_app +from unsloth_cli.commands.train import train +from unsloth_cli.commands.inference import inference +from unsloth_cli.commands.export import export, list_checkpoints +from unsloth_cli.commands.ui import ui +from unsloth_cli.commands.studio import studio_app app = typer.Typer( help = "Command-line interface for Unsloth training, inference, and export.", diff --git a/cli/commands/__init__.py b/unsloth_cli/commands/__init__.py similarity index 100% rename from cli/commands/__init__.py rename to unsloth_cli/commands/__init__.py diff --git a/cli/commands/export.py b/unsloth_cli/commands/export.py similarity index 100% rename from cli/commands/export.py rename to unsloth_cli/commands/export.py diff --git a/cli/commands/inference.py b/unsloth_cli/commands/inference.py similarity index 100% rename from cli/commands/inference.py rename to unsloth_cli/commands/inference.py diff --git a/cli/commands/studio.py b/unsloth_cli/commands/studio.py similarity index 97% rename from cli/commands/studio.py rename to unsloth_cli/commands/studio.py index da5198ad7..829f086b7 100644 --- a/cli/commands/studio.py +++ b/unsloth_cli/commands/studio.py @@ -14,7 +14,7 @@ studio_app = typer.Typer(help = "Unsloth Studio commands.") STUDIO_HOME = Path.home() / ".unsloth" / "studio" -# __file__ is cli/commands/studio.py — two parents up is the package root +# __file__ is unsloth_cli/commands/studio.py -- two parents up is the package root # (either site-packages or the repo root for editable installs). _PACKAGE_ROOT = Path(__file__).resolve().parent.parent.parent @@ -33,7 +33,7 @@ def _find_run_py() -> Optional[Path]: No CWD dependency — works from any directory. Since studio/ is now a proper package (has __init__.py), it lives in - site-packages after pip install, right next to cli/. + site-packages after pip install, right next to unsloth_cli/. """ # 1. Relative to __file__ (site-packages or editable repo root) run_py = _PACKAGE_ROOT / "studio" / "backend" / "run.py" diff --git a/cli/commands/train.py b/unsloth_cli/commands/train.py similarity index 97% rename from cli/commands/train.py rename to unsloth_cli/commands/train.py index 665956a80..115762009 100644 --- a/cli/commands/train.py +++ b/unsloth_cli/commands/train.py @@ -7,8 +7,8 @@ from typing import Optional import typer -from cli.config import Config, load_config -from cli.options import add_options_from_config +from unsloth_cli.config import Config, load_config +from unsloth_cli.options import add_options_from_config @add_options_from_config(Config) diff --git a/cli/commands/ui.py b/unsloth_cli/commands/ui.py similarity index 96% rename from cli/commands/ui.py rename to unsloth_cli/commands/ui.py index 79a76e629..eb6a8a69e 100644 --- a/cli/commands/ui.py +++ b/unsloth_cli/commands/ui.py @@ -25,7 +25,11 @@ def ui( ), ): """Launch the Unsloth web UI backend server (alias for 'unsloth studio').""" - from cli.commands.studio import _studio_venv_python, _find_run_py, STUDIO_HOME + from unsloth_cli.commands.studio import ( + _studio_venv_python, + _find_run_py, + STUDIO_HOME, + ) # Re-execute in studio venv if available and not already inside it studio_venv_dir = STUDIO_HOME / ".venv" diff --git a/cli/config.py b/unsloth_cli/config.py similarity index 100% rename from cli/config.py rename to unsloth_cli/config.py diff --git a/cli/options.py b/unsloth_cli/options.py similarity index 100% rename from cli/options.py rename to unsloth_cli/options.py