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:
rob
2026-01-02 22:15:55 -04:00
co-authored by Claude Opus 4.5
parent 2939dd806b
commit 497fe87fc5
113 changed files with 822 additions and 822 deletions
+61 -61
View File
@@ -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