Rename project from SmartTools to CmdForge
Major rename operation: - Rename Python package: smarttools -> cmdforge - Update CLI entry point: smarttools -> cmdforge - Update all imports and module references - Update pyproject.toml with new name, URLs, entry point - Update all HTML templates with new branding - Update documentation (CLAUDE.md, README.md, docs/*, wiki/*) - Update environment variables: - SMARTTOOLS_ENV -> CMDFORGE_ENV - SMARTTOOLS_REGISTRY_DB -> CMDFORGE_REGISTRY_DB - SMARTTOOLS_TOKEN -> CMDFORGE_TOKEN - SMARTTOOLS_REGISTRY_* -> CMDFORGE_REGISTRY_* - Update Dockerfile and docker-compose.yml - Update tests to use new package name - Update scripts and examples - Update package.json and tailwind.config.js All 158 unit tests pass. The CLI is working correctly with the new cmdforge command. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
+61
-61
@@ -7,8 +7,8 @@ from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from smarttools.cli import main
|
||||
from smarttools.tool import Tool, ToolArgument, PromptStep
|
||||
from cmdforge.cli import main
|
||||
from cmdforge.tool import Tool, ToolArgument, PromptStep
|
||||
|
||||
|
||||
class TestCLIBasics:
|
||||
@@ -17,34 +17,34 @@ class TestCLIBasics:
|
||||
def test_help_flag(self, capsys):
|
||||
"""--help should show usage."""
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
with patch('sys.argv', ['smarttools', '--help']):
|
||||
with patch('sys.argv', ['cmdforge', '--help']):
|
||||
main()
|
||||
|
||||
assert exc_info.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert 'usage' in captured.out.lower() or 'smarttools' in captured.out.lower()
|
||||
assert 'usage' in captured.out.lower() or 'cmdforge' in captured.out.lower()
|
||||
|
||||
def test_version_flag(self, capsys):
|
||||
"""--version should show version."""
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
with patch('sys.argv', ['smarttools', '--version']):
|
||||
with patch('sys.argv', ['cmdforge', '--version']):
|
||||
main()
|
||||
|
||||
assert exc_info.value.code == 0
|
||||
|
||||
|
||||
class TestListCommand:
|
||||
"""Tests for 'smarttools list' command."""
|
||||
"""Tests for 'cmdforge list' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_list_empty(self, temp_tools_dir, capsys):
|
||||
"""List with no tools should show message."""
|
||||
with patch('sys.argv', ['smarttools', 'list']):
|
||||
with patch('sys.argv', ['cmdforge', 'list']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -53,12 +53,12 @@ class TestListCommand:
|
||||
|
||||
def test_list_with_tools(self, temp_tools_dir, capsys):
|
||||
"""List should show available tools."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
save_tool(Tool(name="test-tool", description="A test tool"))
|
||||
save_tool(Tool(name="another-tool", description="Another tool"))
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'list']):
|
||||
with patch('sys.argv', ['cmdforge', 'list']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -68,42 +68,42 @@ class TestListCommand:
|
||||
|
||||
|
||||
class TestCreateCommand:
|
||||
"""Tests for 'smarttools create' command."""
|
||||
"""Tests for 'cmdforge create' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_create_minimal(self, temp_tools_dir, capsys):
|
||||
"""Create a minimal tool."""
|
||||
with patch('sys.argv', ['smarttools', 'create', 'my-tool']):
|
||||
with patch('sys.argv', ['cmdforge', 'create', 'my-tool']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
|
||||
from smarttools.tool import tool_exists
|
||||
from cmdforge.tool import tool_exists
|
||||
assert tool_exists('my-tool')
|
||||
|
||||
def test_create_with_description(self, temp_tools_dir):
|
||||
"""Create tool with description."""
|
||||
with patch('sys.argv', ['smarttools', 'create', 'described-tool',
|
||||
with patch('sys.argv', ['cmdforge', 'create', 'described-tool',
|
||||
'-d', 'A helpful description']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
|
||||
from smarttools.tool import load_tool
|
||||
from cmdforge.tool import load_tool
|
||||
tool = load_tool('described-tool')
|
||||
assert tool.description == 'A helpful description'
|
||||
|
||||
def test_create_duplicate_fails(self, temp_tools_dir, capsys):
|
||||
"""Creating duplicate tool should fail."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
save_tool(Tool(name="existing"))
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'create', 'existing']):
|
||||
with patch('sys.argv', ['cmdforge', 'create', 'existing']):
|
||||
result = main()
|
||||
|
||||
assert result != 0
|
||||
@@ -113,28 +113,28 @@ class TestCreateCommand:
|
||||
@pytest.mark.skip(reason="CLI does not yet validate tool names - potential enhancement")
|
||||
def test_create_invalid_name(self, temp_tools_dir, capsys):
|
||||
"""Invalid tool name should fail."""
|
||||
with patch('sys.argv', ['smarttools', 'create', 'invalid/name']):
|
||||
with patch('sys.argv', ['cmdforge', 'create', 'invalid/name']):
|
||||
result = main()
|
||||
|
||||
assert result != 0
|
||||
|
||||
|
||||
class TestDeleteCommand:
|
||||
"""Tests for 'smarttools delete' command."""
|
||||
"""Tests for 'cmdforge delete' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_delete_existing(self, temp_tools_dir, capsys):
|
||||
"""Delete an existing tool."""
|
||||
from smarttools.tool import save_tool, tool_exists
|
||||
from cmdforge.tool import save_tool, tool_exists
|
||||
save_tool(Tool(name="to-delete"))
|
||||
assert tool_exists("to-delete")
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'delete', 'to-delete', '-f']):
|
||||
with patch('sys.argv', ['cmdforge', 'delete', 'to-delete', '-f']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -142,24 +142,24 @@ class TestDeleteCommand:
|
||||
|
||||
def test_delete_nonexistent(self, temp_tools_dir, capsys):
|
||||
"""Deleting nonexistent tool should fail."""
|
||||
with patch('sys.argv', ['smarttools', 'delete', 'nonexistent', '-f']):
|
||||
with patch('sys.argv', ['cmdforge', 'delete', 'nonexistent', '-f']):
|
||||
result = main()
|
||||
|
||||
assert result != 0
|
||||
|
||||
|
||||
class TestRunCommand:
|
||||
"""Tests for 'smarttools run' command."""
|
||||
"""Tests for 'cmdforge run' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_run_simple_tool(self, temp_tools_dir, capsys):
|
||||
"""Run a simple tool without AI calls."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
tool = Tool(
|
||||
name="echo",
|
||||
@@ -167,7 +167,7 @@ class TestRunCommand:
|
||||
)
|
||||
save_tool(tool)
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'run', 'echo']):
|
||||
with patch('sys.argv', ['cmdforge', 'run', 'echo']):
|
||||
with patch('sys.stdin', StringIO("Hello")):
|
||||
with patch('sys.stdin.isatty', return_value=False):
|
||||
result = main()
|
||||
@@ -178,7 +178,7 @@ class TestRunCommand:
|
||||
|
||||
def test_run_with_mock_provider(self, temp_tools_dir, capsys):
|
||||
"""Run tool with mock provider."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
tool = Tool(
|
||||
name="summarize",
|
||||
@@ -189,7 +189,7 @@ class TestRunCommand:
|
||||
)
|
||||
save_tool(tool)
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'run', 'summarize']):
|
||||
with patch('sys.argv', ['cmdforge', 'run', 'summarize']):
|
||||
with patch('sys.stdin', StringIO("Some text")):
|
||||
with patch('sys.stdin.isatty', return_value=False):
|
||||
result = main()
|
||||
@@ -200,24 +200,24 @@ class TestRunCommand:
|
||||
|
||||
def test_run_nonexistent_tool(self, temp_tools_dir, capsys):
|
||||
"""Running nonexistent tool should fail."""
|
||||
with patch('sys.argv', ['smarttools', 'run', 'nonexistent']):
|
||||
with patch('sys.argv', ['cmdforge', 'run', 'nonexistent']):
|
||||
result = main()
|
||||
|
||||
assert result != 0
|
||||
|
||||
|
||||
class TestTestCommand:
|
||||
"""Tests for 'smarttools test' command."""
|
||||
"""Tests for 'cmdforge test' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_test_tool(self, temp_tools_dir, capsys):
|
||||
"""Test command should run with mock provider."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
tool = Tool(
|
||||
name="test-me",
|
||||
@@ -229,7 +229,7 @@ class TestTestCommand:
|
||||
save_tool(tool)
|
||||
|
||||
# Provide stdin input for the test command
|
||||
with patch('sys.argv', ['smarttools', 'test', 'test-me']):
|
||||
with patch('sys.argv', ['cmdforge', 'test', 'test-me']):
|
||||
with patch('sys.stdin', StringIO("test input")):
|
||||
result = main()
|
||||
|
||||
@@ -240,17 +240,17 @@ class TestTestCommand:
|
||||
|
||||
|
||||
class TestProvidersCommand:
|
||||
"""Tests for 'smarttools providers' command."""
|
||||
"""Tests for 'cmdforge providers' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_providers_file(self, tmp_path):
|
||||
providers_file = tmp_path / ".smarttools" / "providers.yaml"
|
||||
with patch('smarttools.providers.PROVIDERS_FILE', providers_file):
|
||||
providers_file = tmp_path / ".cmdforge" / "providers.yaml"
|
||||
with patch('cmdforge.providers.PROVIDERS_FILE', providers_file):
|
||||
yield providers_file
|
||||
|
||||
def test_providers_list(self, temp_providers_file, capsys):
|
||||
"""List providers."""
|
||||
with patch('sys.argv', ['smarttools', 'providers']):
|
||||
with patch('sys.argv', ['cmdforge', 'providers']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -260,47 +260,47 @@ class TestProvidersCommand:
|
||||
|
||||
def test_providers_add(self, temp_providers_file, capsys):
|
||||
"""Add a custom provider."""
|
||||
with patch('sys.argv', ['smarttools', 'providers', 'add',
|
||||
with patch('sys.argv', ['cmdforge', 'providers', 'add',
|
||||
'custom', 'my-ai --prompt']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
|
||||
from smarttools.providers import get_provider
|
||||
from cmdforge.providers import get_provider
|
||||
provider = get_provider('custom')
|
||||
assert provider is not None
|
||||
assert provider.command == 'my-ai --prompt'
|
||||
|
||||
def test_providers_remove(self, temp_providers_file, capsys):
|
||||
"""Remove a provider."""
|
||||
from smarttools.providers import add_provider, Provider
|
||||
from cmdforge.providers import add_provider, Provider
|
||||
add_provider(Provider('removeme', 'cmd'))
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'providers', 'remove', 'removeme']):
|
||||
with patch('sys.argv', ['cmdforge', 'providers', 'remove', 'removeme']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
|
||||
from smarttools.providers import get_provider
|
||||
from cmdforge.providers import get_provider
|
||||
assert get_provider('removeme') is None
|
||||
|
||||
|
||||
class TestRefreshCommand:
|
||||
"""Tests for 'smarttools refresh' command."""
|
||||
"""Tests for 'cmdforge refresh' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_refresh_creates_wrappers(self, temp_tools_dir, capsys):
|
||||
"""Refresh should create wrapper scripts."""
|
||||
from smarttools.tool import save_tool, get_bin_dir
|
||||
from cmdforge.tool import save_tool, get_bin_dir
|
||||
|
||||
save_tool(Tool(name="wrapper-test"))
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'refresh']):
|
||||
with patch('sys.argv', ['cmdforge', 'refresh']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -310,17 +310,17 @@ class TestRefreshCommand:
|
||||
|
||||
|
||||
class TestDocsCommand:
|
||||
"""Tests for 'smarttools docs' command."""
|
||||
"""Tests for 'cmdforge docs' command."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tools_dir(self, tmp_path):
|
||||
with patch('smarttools.tool.TOOLS_DIR', tmp_path / ".smarttools"):
|
||||
with patch('smarttools.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
with patch('cmdforge.tool.TOOLS_DIR', tmp_path / ".cmdforge"):
|
||||
with patch('cmdforge.tool.BIN_DIR', tmp_path / ".local" / "bin"):
|
||||
yield tmp_path
|
||||
|
||||
def test_docs_for_tool_with_readme(self, temp_tools_dir, capsys):
|
||||
"""Docs should show README content when it exists."""
|
||||
from smarttools.tool import save_tool, get_tools_dir
|
||||
from cmdforge.tool import save_tool, get_tools_dir
|
||||
|
||||
tool = Tool(
|
||||
name="documented",
|
||||
@@ -332,7 +332,7 @@ class TestDocsCommand:
|
||||
readme_path = get_tools_dir() / "documented" / "README.md"
|
||||
readme_path.write_text("# Documented Tool\n\nThis is the documentation.")
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'docs', 'documented']):
|
||||
with patch('sys.argv', ['cmdforge', 'docs', 'documented']):
|
||||
result = main()
|
||||
|
||||
assert result == 0
|
||||
@@ -342,12 +342,12 @@ class TestDocsCommand:
|
||||
|
||||
def test_docs_no_readme(self, temp_tools_dir, capsys):
|
||||
"""Docs without README should prompt to create one."""
|
||||
from smarttools.tool import save_tool
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
tool = Tool(name="no-docs")
|
||||
save_tool(tool)
|
||||
|
||||
with patch('sys.argv', ['smarttools', 'docs', 'no-docs']):
|
||||
with patch('sys.argv', ['cmdforge', 'docs', 'no-docs']):
|
||||
result = main()
|
||||
|
||||
assert result == 1 # Returns 1 when no README
|
||||
|
||||
Reference in New Issue
Block a user