fips-agents-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fips_agents_cli/__init__.py +5 -0
- fips_agents_cli/__main__.py +6 -0
- fips_agents_cli/cli.py +34 -0
- fips_agents_cli/commands/__init__.py +1 -0
- fips_agents_cli/commands/create.py +182 -0
- fips_agents_cli/tools/__init__.py +1 -0
- fips_agents_cli/tools/filesystem.py +125 -0
- fips_agents_cli/tools/git.py +100 -0
- fips_agents_cli/tools/project.py +163 -0
- fips_agents_cli/version.py +3 -0
- fips_agents_cli-0.1.0.dist-info/METADATA +317 -0
- fips_agents_cli-0.1.0.dist-info/RECORD +15 -0
- fips_agents_cli-0.1.0.dist-info/WHEEL +4 -0
- fips_agents_cli-0.1.0.dist-info/entry_points.txt +2 -0
- fips_agents_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
fips_agents_cli/cli.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
"""Main CLI entry point for fips-agents-cli."""
|
2
|
+
|
3
|
+
import click
|
4
|
+
from rich.console import Console
|
5
|
+
|
6
|
+
from fips_agents_cli.commands.create import create
|
7
|
+
from fips_agents_cli.version import __version__
|
8
|
+
|
9
|
+
console = Console()
|
10
|
+
|
11
|
+
|
12
|
+
@click.group()
|
13
|
+
@click.version_option(version=__version__, prog_name="fips-agents")
|
14
|
+
@click.pass_context
|
15
|
+
def cli(ctx):
|
16
|
+
"""
|
17
|
+
FIPS Agents CLI - A tool for creating and managing FIPS-compliant AI agent projects.
|
18
|
+
|
19
|
+
Use 'fips-agents COMMAND --help' for more information on a specific command.
|
20
|
+
"""
|
21
|
+
ctx.ensure_object(dict)
|
22
|
+
|
23
|
+
|
24
|
+
# Register commands
|
25
|
+
cli.add_command(create)
|
26
|
+
|
27
|
+
|
28
|
+
def main():
|
29
|
+
"""Main entry point for the CLI application."""
|
30
|
+
cli(obj={})
|
31
|
+
|
32
|
+
|
33
|
+
if __name__ == "__main__":
|
34
|
+
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
"""CLI commands for fips-agents-cli."""
|
@@ -0,0 +1,182 @@
|
|
1
|
+
"""Create command for generating new projects from templates."""
|
2
|
+
|
3
|
+
import sys
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
import click
|
7
|
+
from rich.console import Console
|
8
|
+
from rich.panel import Panel
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
10
|
+
|
11
|
+
from fips_agents_cli.tools.filesystem import resolve_target_path, validate_target_directory
|
12
|
+
from fips_agents_cli.tools.git import clone_template, init_repository, is_git_installed
|
13
|
+
from fips_agents_cli.tools.project import (
|
14
|
+
cleanup_template_files,
|
15
|
+
to_module_name,
|
16
|
+
update_project_name,
|
17
|
+
validate_project_name,
|
18
|
+
)
|
19
|
+
|
20
|
+
console = Console()
|
21
|
+
|
22
|
+
# Template URL for MCP server projects
|
23
|
+
MCP_SERVER_TEMPLATE_URL = "https://github.com/rdwj/mcp-server-template"
|
24
|
+
|
25
|
+
|
26
|
+
@click.group()
|
27
|
+
def create():
|
28
|
+
"""Create new projects from templates."""
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
@create.command("mcp-server")
|
33
|
+
@click.argument("project_name")
|
34
|
+
@click.option(
|
35
|
+
"--target-dir",
|
36
|
+
"-t",
|
37
|
+
default=None,
|
38
|
+
help="Target directory for the project (default: current directory)",
|
39
|
+
)
|
40
|
+
@click.option(
|
41
|
+
"--no-git",
|
42
|
+
is_flag=True,
|
43
|
+
default=False,
|
44
|
+
help="Skip git repository initialization",
|
45
|
+
)
|
46
|
+
def mcp_server(project_name: str, target_dir: Optional[str], no_git: bool):
|
47
|
+
"""
|
48
|
+
Create a new MCP server project from template.
|
49
|
+
|
50
|
+
PROJECT_NAME must start with a lowercase letter and contain only
|
51
|
+
lowercase letters, numbers, hyphens, and underscores.
|
52
|
+
|
53
|
+
Example:
|
54
|
+
fips-agents create mcp-server my-mcp-server
|
55
|
+
fips-agents create mcp-server my-mcp-server --target-dir ~/projects
|
56
|
+
"""
|
57
|
+
try:
|
58
|
+
# Step 1: Validate project name
|
59
|
+
console.print("\n[bold cyan]Creating MCP Server Project[/bold cyan]\n")
|
60
|
+
|
61
|
+
is_valid, error_msg = validate_project_name(project_name)
|
62
|
+
if not is_valid:
|
63
|
+
console.print(f"[red]✗[/red] Invalid project name: {error_msg}")
|
64
|
+
sys.exit(1)
|
65
|
+
|
66
|
+
console.print(f"[green]✓[/green] Project name '{project_name}' is valid")
|
67
|
+
|
68
|
+
# Step 2: Resolve and validate target directory
|
69
|
+
target_path = resolve_target_path(project_name, target_dir)
|
70
|
+
|
71
|
+
is_valid, error_msg = validate_target_directory(target_path, allow_existing=False)
|
72
|
+
if not is_valid:
|
73
|
+
console.print(f"[red]✗[/red] {error_msg}")
|
74
|
+
console.print(
|
75
|
+
"\n[yellow]Hint:[/yellow] Choose a different name or remove the existing directory"
|
76
|
+
)
|
77
|
+
sys.exit(1)
|
78
|
+
|
79
|
+
console.print(f"[green]✓[/green] Target directory: {target_path}")
|
80
|
+
|
81
|
+
# Step 3: Check if git is installed
|
82
|
+
if not is_git_installed():
|
83
|
+
console.print(
|
84
|
+
"[yellow]⚠[/yellow] Git is not installed. This is required for cloning templates."
|
85
|
+
)
|
86
|
+
console.print("[yellow]Hint:[/yellow] Install git from https://git-scm.com/downloads")
|
87
|
+
sys.exit(1)
|
88
|
+
|
89
|
+
# Step 4: Clone template repository
|
90
|
+
with Progress(
|
91
|
+
SpinnerColumn(),
|
92
|
+
TextColumn("[progress.description]{task.description}"),
|
93
|
+
console=console,
|
94
|
+
) as progress:
|
95
|
+
progress.add_task(description="Cloning template repository...", total=None)
|
96
|
+
try:
|
97
|
+
clone_template(MCP_SERVER_TEMPLATE_URL, target_path)
|
98
|
+
except Exception as e:
|
99
|
+
console.print("\n[red]✗[/red] Failed to clone template repository")
|
100
|
+
console.print(f"[red]Error:[/red] {e}")
|
101
|
+
console.print(
|
102
|
+
f"\n[yellow]Hint:[/yellow] Check your internet connection and verify "
|
103
|
+
f"the template URL is accessible:\n{MCP_SERVER_TEMPLATE_URL}"
|
104
|
+
)
|
105
|
+
sys.exit(1)
|
106
|
+
|
107
|
+
# Step 5: Customize project
|
108
|
+
with Progress(
|
109
|
+
SpinnerColumn(),
|
110
|
+
TextColumn("[progress.description]{task.description}"),
|
111
|
+
console=console,
|
112
|
+
) as progress:
|
113
|
+
progress.add_task(description="Customizing project...", total=None)
|
114
|
+
try:
|
115
|
+
update_project_name(target_path, project_name)
|
116
|
+
cleanup_template_files(target_path)
|
117
|
+
except Exception as e:
|
118
|
+
console.print("\n[red]✗[/red] Failed to customize project")
|
119
|
+
console.print(f"[red]Error:[/red] {e}")
|
120
|
+
sys.exit(1)
|
121
|
+
|
122
|
+
# Step 6: Initialize git repository
|
123
|
+
if not no_git:
|
124
|
+
with Progress(
|
125
|
+
SpinnerColumn(),
|
126
|
+
TextColumn("[progress.description]{task.description}"),
|
127
|
+
console=console,
|
128
|
+
) as progress:
|
129
|
+
progress.add_task(description="Initializing git repository...", total=None)
|
130
|
+
try:
|
131
|
+
init_repository(target_path, initial_commit=True)
|
132
|
+
except Exception as e:
|
133
|
+
console.print("\n[yellow]⚠[/yellow] Failed to initialize git repository")
|
134
|
+
console.print(f"[yellow]Warning:[/yellow] {e}")
|
135
|
+
console.print(
|
136
|
+
"[yellow]You can initialize git manually later with:[/yellow] git init"
|
137
|
+
)
|
138
|
+
|
139
|
+
# Step 7: Success message with next steps
|
140
|
+
module_name = to_module_name(project_name)
|
141
|
+
|
142
|
+
success_message = f"""
|
143
|
+
[bold green]✓ Successfully created MCP server project![/bold green]
|
144
|
+
|
145
|
+
[bold cyan]Project Details:[/bold cyan]
|
146
|
+
• Name: {project_name}
|
147
|
+
• Module: {module_name}
|
148
|
+
• Location: {target_path}
|
149
|
+
|
150
|
+
[bold cyan]Next Steps:[/bold cyan]
|
151
|
+
|
152
|
+
1. Navigate to your project:
|
153
|
+
[dim]cd {target_path.name}[/dim]
|
154
|
+
|
155
|
+
2. Create and activate a virtual environment:
|
156
|
+
[dim]python -m venv venv[/dim]
|
157
|
+
[dim]source venv/bin/activate[/dim] # On Windows: venv\\Scripts\\activate
|
158
|
+
|
159
|
+
3. Install the project in editable mode:
|
160
|
+
[dim]pip install -e .[dev][/dim]
|
161
|
+
|
162
|
+
4. Start developing your MCP server!
|
163
|
+
[dim]Edit src/{module_name}/ files to add your functionality[/dim]
|
164
|
+
|
165
|
+
5. Run tests:
|
166
|
+
[dim]pytest[/dim]
|
167
|
+
|
168
|
+
[bold cyan]Documentation:[/bold cyan]
|
169
|
+
• Check the README.md in your project for detailed instructions
|
170
|
+
• MCP Protocol docs: https://modelcontextprotocol.io/
|
171
|
+
|
172
|
+
Happy coding! 🚀
|
173
|
+
"""
|
174
|
+
|
175
|
+
console.print(Panel(success_message, border_style="green", padding=(1, 2)))
|
176
|
+
|
177
|
+
except KeyboardInterrupt:
|
178
|
+
console.print("\n[yellow]⚠[/yellow] Operation cancelled by user")
|
179
|
+
sys.exit(130)
|
180
|
+
except Exception as e:
|
181
|
+
console.print(f"\n[red]✗[/red] Unexpected error: {e}")
|
182
|
+
sys.exit(1)
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Tools and utilities for fips-agents-cli."""
|
@@ -0,0 +1,125 @@
|
|
1
|
+
"""Filesystem utilities for project operations."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from rich.console import Console
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
|
11
|
+
def ensure_directory_exists(path: Path, create: bool = False) -> bool:
|
12
|
+
"""
|
13
|
+
Check if a directory exists, optionally creating it.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
path: The directory path to check
|
17
|
+
create: Whether to create the directory if it doesn't exist
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
bool: True if directory exists (or was created), False otherwise
|
21
|
+
"""
|
22
|
+
if path.exists():
|
23
|
+
return path.is_dir()
|
24
|
+
|
25
|
+
if create:
|
26
|
+
try:
|
27
|
+
path.mkdir(parents=True, exist_ok=True)
|
28
|
+
return True
|
29
|
+
except Exception as e:
|
30
|
+
console.print(f"[red]✗[/red] Failed to create directory {path}: {e}")
|
31
|
+
return False
|
32
|
+
|
33
|
+
return False
|
34
|
+
|
35
|
+
|
36
|
+
def check_directory_empty(path: Path) -> bool:
|
37
|
+
"""
|
38
|
+
Check if a directory is empty.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
path: The directory path to check
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
bool: True if directory is empty, False otherwise
|
45
|
+
"""
|
46
|
+
if not path.exists():
|
47
|
+
return True
|
48
|
+
|
49
|
+
if not path.is_dir():
|
50
|
+
return False
|
51
|
+
|
52
|
+
return not any(path.iterdir())
|
53
|
+
|
54
|
+
|
55
|
+
def validate_target_directory(
|
56
|
+
target_path: Path, allow_existing: bool = False
|
57
|
+
) -> tuple[bool, Optional[str]]:
|
58
|
+
"""
|
59
|
+
Validate that a target directory is suitable for project creation.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
target_path: The target directory path
|
63
|
+
allow_existing: Whether to allow existing directories (must be empty)
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
tuple: (is_valid, error_message)
|
67
|
+
is_valid is True if valid, False otherwise
|
68
|
+
error_message is None if valid, otherwise contains the error description
|
69
|
+
"""
|
70
|
+
# Check if parent directory exists
|
71
|
+
parent = target_path.parent
|
72
|
+
if not parent.exists():
|
73
|
+
return False, f"Parent directory does not exist: {parent}"
|
74
|
+
|
75
|
+
# Check if target already exists
|
76
|
+
if target_path.exists():
|
77
|
+
if not allow_existing:
|
78
|
+
return False, f"Directory already exists: {target_path}"
|
79
|
+
|
80
|
+
if not target_path.is_dir():
|
81
|
+
return False, f"Path exists but is not a directory: {target_path}"
|
82
|
+
|
83
|
+
if not check_directory_empty(target_path):
|
84
|
+
return False, f"Directory is not empty: {target_path}"
|
85
|
+
|
86
|
+
return True, None
|
87
|
+
|
88
|
+
|
89
|
+
def resolve_target_path(project_name: str, target_dir: Optional[str] = None) -> Path:
|
90
|
+
"""
|
91
|
+
Resolve the target path for project creation.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
project_name: The name of the project
|
95
|
+
target_dir: Optional target directory (defaults to current directory)
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
Path: The resolved absolute path for the project
|
99
|
+
"""
|
100
|
+
if target_dir:
|
101
|
+
base = Path(target_dir).resolve()
|
102
|
+
else:
|
103
|
+
base = Path.cwd()
|
104
|
+
|
105
|
+
return base / project_name
|
106
|
+
|
107
|
+
|
108
|
+
def get_relative_path(path: Path, base: Optional[Path] = None) -> str:
|
109
|
+
"""
|
110
|
+
Get a relative path string for display purposes.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
path: The path to convert
|
114
|
+
base: Base path for relative calculation (defaults to current directory)
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
str: Relative path string, or absolute if relative calculation fails
|
118
|
+
"""
|
119
|
+
if base is None:
|
120
|
+
base = Path.cwd()
|
121
|
+
|
122
|
+
try:
|
123
|
+
return str(path.relative_to(base))
|
124
|
+
except ValueError:
|
125
|
+
return str(path)
|
@@ -0,0 +1,100 @@
|
|
1
|
+
"""Git operations for cloning and initializing repositories."""
|
2
|
+
|
3
|
+
import shutil
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import git
|
7
|
+
from rich.console import Console
|
8
|
+
|
9
|
+
console = Console()
|
10
|
+
|
11
|
+
|
12
|
+
def clone_template(repo_url: str, target_path: Path, branch: str = "main") -> None:
|
13
|
+
"""
|
14
|
+
Clone a git repository template to a target path.
|
15
|
+
|
16
|
+
Performs a shallow clone (depth=1) for faster cloning and removes the .git
|
17
|
+
directory to allow fresh initialization.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
repo_url: The URL of the git repository to clone
|
21
|
+
target_path: The local path where the repository should be cloned
|
22
|
+
branch: The branch to clone (default: "main")
|
23
|
+
|
24
|
+
Raises:
|
25
|
+
git.GitCommandError: If the clone operation fails
|
26
|
+
OSError: If there are filesystem permission issues
|
27
|
+
"""
|
28
|
+
try:
|
29
|
+
# Perform shallow clone for faster operation
|
30
|
+
console.print(f"[cyan]Cloning template from {repo_url}...[/cyan]")
|
31
|
+
git.Repo.clone_from(
|
32
|
+
repo_url,
|
33
|
+
str(target_path),
|
34
|
+
branch=branch,
|
35
|
+
depth=1,
|
36
|
+
single_branch=True,
|
37
|
+
)
|
38
|
+
|
39
|
+
# Remove .git directory to allow fresh initialization
|
40
|
+
git_dir = target_path / ".git"
|
41
|
+
if git_dir.exists():
|
42
|
+
shutil.rmtree(git_dir)
|
43
|
+
console.print("[green]✓[/green] Template cloned successfully")
|
44
|
+
|
45
|
+
except git.GitCommandError as e:
|
46
|
+
console.print(f"[red]✗[/red] Failed to clone template: {e}")
|
47
|
+
raise
|
48
|
+
except Exception as e:
|
49
|
+
console.print(f"[red]✗[/red] Unexpected error during clone: {e}")
|
50
|
+
raise
|
51
|
+
|
52
|
+
|
53
|
+
def init_repository(project_path: Path, initial_commit: bool = True) -> None:
|
54
|
+
"""
|
55
|
+
Initialize a new git repository in the project directory.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
project_path: Path to the project directory
|
59
|
+
initial_commit: Whether to create an initial commit (default: True)
|
60
|
+
|
61
|
+
Raises:
|
62
|
+
git.GitCommandError: If git operations fail
|
63
|
+
"""
|
64
|
+
try:
|
65
|
+
console.print("[cyan]Initializing git repository...[/cyan]")
|
66
|
+
|
67
|
+
# Initialize repository
|
68
|
+
repo = git.Repo.init(str(project_path))
|
69
|
+
|
70
|
+
if initial_commit:
|
71
|
+
# Add all files
|
72
|
+
repo.index.add("*")
|
73
|
+
|
74
|
+
# Create initial commit
|
75
|
+
repo.index.commit("Initial commit from fips-agents-cli")
|
76
|
+
|
77
|
+
console.print("[green]✓[/green] Git repository initialized with initial commit")
|
78
|
+
else:
|
79
|
+
console.print("[green]✓[/green] Git repository initialized")
|
80
|
+
|
81
|
+
except git.GitCommandError as e:
|
82
|
+
console.print(f"[red]✗[/red] Failed to initialize git repository: {e}")
|
83
|
+
raise
|
84
|
+
except Exception as e:
|
85
|
+
console.print(f"[red]✗[/red] Unexpected error during git init: {e}")
|
86
|
+
raise
|
87
|
+
|
88
|
+
|
89
|
+
def is_git_installed() -> bool:
|
90
|
+
"""
|
91
|
+
Check if git is installed and available in the system PATH.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
bool: True if git is installed, False otherwise
|
95
|
+
"""
|
96
|
+
try:
|
97
|
+
git.Git().version()
|
98
|
+
return True
|
99
|
+
except (git.GitCommandNotFound, git.GitCommandError):
|
100
|
+
return False
|
@@ -0,0 +1,163 @@
|
|
1
|
+
"""Project customization and configuration utilities."""
|
2
|
+
|
3
|
+
import re
|
4
|
+
import shutil
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
import tomlkit
|
9
|
+
from rich.console import Console
|
10
|
+
|
11
|
+
console = Console()
|
12
|
+
|
13
|
+
|
14
|
+
def validate_project_name(name: str) -> tuple[bool, Optional[str]]:
|
15
|
+
"""
|
16
|
+
Validate project name according to Python package naming conventions.
|
17
|
+
|
18
|
+
Project names must:
|
19
|
+
- Start with a lowercase letter
|
20
|
+
- Contain only lowercase letters, numbers, hyphens, and underscores
|
21
|
+
- Not be empty
|
22
|
+
|
23
|
+
Args:
|
24
|
+
name: The project name to validate
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
tuple: (is_valid, error_message)
|
28
|
+
is_valid is True if valid, False otherwise
|
29
|
+
error_message is None if valid, otherwise contains the error description
|
30
|
+
"""
|
31
|
+
if not name:
|
32
|
+
return False, "Project name cannot be empty"
|
33
|
+
|
34
|
+
if not re.match(r"^[a-z][a-z0-9\-_]*$", name):
|
35
|
+
return False, (
|
36
|
+
"Project name must start with a lowercase letter and contain only "
|
37
|
+
"lowercase letters, numbers, hyphens, and underscores"
|
38
|
+
)
|
39
|
+
|
40
|
+
return True, None
|
41
|
+
|
42
|
+
|
43
|
+
def to_module_name(project_name: str) -> str:
|
44
|
+
"""
|
45
|
+
Convert a project name to a valid Python module name.
|
46
|
+
|
47
|
+
Replaces hyphens with underscores to ensure compatibility with Python imports.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
project_name: The project name (may contain hyphens)
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
str: A valid Python module name (hyphens replaced with underscores)
|
54
|
+
"""
|
55
|
+
return project_name.replace("-", "_")
|
56
|
+
|
57
|
+
|
58
|
+
def update_project_name(project_path: Path, new_name: str) -> None:
|
59
|
+
"""
|
60
|
+
Update the project name in pyproject.toml and rename the source directory.
|
61
|
+
|
62
|
+
This function:
|
63
|
+
1. Updates the 'name' field in pyproject.toml
|
64
|
+
2. Updates the entry point script name if present
|
65
|
+
3. Renames the source directory from the template name to the new module name
|
66
|
+
|
67
|
+
Args:
|
68
|
+
project_path: Path to the project root directory
|
69
|
+
new_name: The new project name (with hyphens allowed)
|
70
|
+
|
71
|
+
Raises:
|
72
|
+
FileNotFoundError: If pyproject.toml or source directory doesn't exist
|
73
|
+
ValueError: If pyproject.toml is malformed
|
74
|
+
"""
|
75
|
+
try:
|
76
|
+
console.print(f"[cyan]Customizing project for '{new_name}'...[/cyan]")
|
77
|
+
|
78
|
+
pyproject_path = project_path / "pyproject.toml"
|
79
|
+
|
80
|
+
if not pyproject_path.exists():
|
81
|
+
raise FileNotFoundError(f"pyproject.toml not found at {pyproject_path}")
|
82
|
+
|
83
|
+
# Read and parse pyproject.toml
|
84
|
+
with open(pyproject_path) as f:
|
85
|
+
pyproject = tomlkit.parse(f.read())
|
86
|
+
|
87
|
+
# Get the old project name from pyproject.toml
|
88
|
+
old_name = pyproject.get("project", {}).get("name", "mcp-server-template")
|
89
|
+
old_module_name = to_module_name(old_name)
|
90
|
+
new_module_name = to_module_name(new_name)
|
91
|
+
|
92
|
+
# Update project name
|
93
|
+
if "project" in pyproject:
|
94
|
+
pyproject["project"]["name"] = new_name
|
95
|
+
|
96
|
+
# Update entry point script name if it exists
|
97
|
+
if "scripts" in pyproject["project"]:
|
98
|
+
# Find and update any entry points that reference the old name
|
99
|
+
scripts = pyproject["project"]["scripts"]
|
100
|
+
old_scripts = dict(scripts)
|
101
|
+
|
102
|
+
for script_name, script_path in old_scripts.items():
|
103
|
+
# Replace old module name with new module name in the path
|
104
|
+
new_script_path = script_path.replace(old_module_name, new_module_name)
|
105
|
+
|
106
|
+
# If the script name was based on the old project name, update it
|
107
|
+
if script_name == old_name or script_name == old_module_name:
|
108
|
+
del scripts[script_name]
|
109
|
+
scripts[new_name] = new_script_path
|
110
|
+
else:
|
111
|
+
scripts[script_name] = new_script_path
|
112
|
+
|
113
|
+
# Write updated pyproject.toml
|
114
|
+
with open(pyproject_path, "w") as f:
|
115
|
+
f.write(tomlkit.dumps(pyproject))
|
116
|
+
|
117
|
+
console.print("[green]✓[/green] Updated pyproject.toml")
|
118
|
+
|
119
|
+
# Rename source directory
|
120
|
+
src_dir = project_path / "src"
|
121
|
+
old_src_path = src_dir / old_module_name
|
122
|
+
new_src_path = src_dir / new_module_name
|
123
|
+
|
124
|
+
if old_src_path.exists() and old_src_path != new_src_path:
|
125
|
+
shutil.move(str(old_src_path), str(new_src_path))
|
126
|
+
console.print(f"[green]✓[/green] Renamed source directory to '{new_module_name}'")
|
127
|
+
elif not old_src_path.exists():
|
128
|
+
# Template might have a different structure, log a warning
|
129
|
+
console.print(
|
130
|
+
f"[yellow]⚠[/yellow] Source directory 'src/{old_module_name}' not found, "
|
131
|
+
"skipping rename"
|
132
|
+
)
|
133
|
+
|
134
|
+
console.print("[green]✓[/green] Project customization complete")
|
135
|
+
|
136
|
+
except FileNotFoundError as e:
|
137
|
+
console.print(f"[red]✗[/red] {e}")
|
138
|
+
raise
|
139
|
+
except Exception as e:
|
140
|
+
console.print(f"[red]✗[/red] Failed to customize project: {e}")
|
141
|
+
raise
|
142
|
+
|
143
|
+
|
144
|
+
def cleanup_template_files(project_path: Path) -> None:
|
145
|
+
"""
|
146
|
+
Remove template-specific files that shouldn't be in the new project.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
project_path: Path to the project root directory
|
150
|
+
"""
|
151
|
+
# Files to remove (commonly found in templates)
|
152
|
+
files_to_remove = [
|
153
|
+
".github/workflows/template-cleanup.yml", # Template-specific workflows
|
154
|
+
]
|
155
|
+
|
156
|
+
for file_path in files_to_remove:
|
157
|
+
full_path = project_path / file_path
|
158
|
+
if full_path.exists():
|
159
|
+
try:
|
160
|
+
full_path.unlink()
|
161
|
+
console.print(f"[green]✓[/green] Removed template file: {file_path}")
|
162
|
+
except Exception as e:
|
163
|
+
console.print(f"[yellow]⚠[/yellow] Could not remove {file_path}: {e}")
|
@@ -0,0 +1,317 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: fips-agents-cli
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: CLI tool for creating and managing FIPS-compliant AI agent projects
|
5
|
+
Project-URL: Homepage, https://github.com/rdwj/fips-agents-cli
|
6
|
+
Project-URL: Repository, https://github.com/rdwj/fips-agents-cli
|
7
|
+
Project-URL: Issues, https://github.com/rdwj/fips-agents-cli/issues
|
8
|
+
Author-email: Wes Jackson <wjackson@redhat.com>
|
9
|
+
License: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Keywords: agents,ai,cli,code-generator,fips,mcp,scaffolding
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
22
|
+
Requires-Python: >=3.9
|
23
|
+
Requires-Dist: click>=8.1.0
|
24
|
+
Requires-Dist: gitpython>=3.1.0
|
25
|
+
Requires-Dist: rich>=13.0.0
|
26
|
+
Requires-Dist: tomlkit>=0.12.0
|
27
|
+
Provides-Extra: dev
|
28
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
29
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
30
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
32
|
+
Description-Content-Type: text/markdown
|
33
|
+
|
34
|
+
# FIPS Agents CLI
|
35
|
+
|
36
|
+
A command-line tool for creating and managing FIPS-compliant AI agent projects, with a focus on MCP (Model Context Protocol) server development.
|
37
|
+
|
38
|
+
## Features
|
39
|
+
|
40
|
+
- 🚀 Quick project scaffolding from templates
|
41
|
+
- 📦 MCP server project generation
|
42
|
+
- 🔧 Automatic project customization
|
43
|
+
- 🎨 Beautiful CLI output with Rich
|
44
|
+
- ✅ Git repository initialization
|
45
|
+
- 🧪 Comprehensive test coverage
|
46
|
+
|
47
|
+
## Installation
|
48
|
+
|
49
|
+
### Using pipx (Recommended)
|
50
|
+
|
51
|
+
```bash
|
52
|
+
pipx install fips-agents-cli
|
53
|
+
```
|
54
|
+
|
55
|
+
### Using pip
|
56
|
+
|
57
|
+
```bash
|
58
|
+
pip install fips-agents-cli
|
59
|
+
```
|
60
|
+
|
61
|
+
### From Source (Development)
|
62
|
+
|
63
|
+
```bash
|
64
|
+
# Clone the repository
|
65
|
+
git clone https://github.com/rdwj/fips-agents-cli.git
|
66
|
+
cd fips-agents-cli
|
67
|
+
|
68
|
+
# Create virtual environment
|
69
|
+
python -m venv venv
|
70
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
71
|
+
|
72
|
+
# Install in editable mode with dev dependencies
|
73
|
+
pip install -e .[dev]
|
74
|
+
```
|
75
|
+
|
76
|
+
## Quick Start
|
77
|
+
|
78
|
+
### Create a new MCP server project
|
79
|
+
|
80
|
+
```bash
|
81
|
+
fips-agents create mcp-server my-awesome-server
|
82
|
+
```
|
83
|
+
|
84
|
+
This will:
|
85
|
+
1. Clone the MCP server template
|
86
|
+
2. Customize the project with your chosen name
|
87
|
+
3. Initialize a git repository
|
88
|
+
4. Provide next steps for development
|
89
|
+
|
90
|
+
### Specify a target directory
|
91
|
+
|
92
|
+
```bash
|
93
|
+
fips-agents create mcp-server my-server --target-dir ~/projects
|
94
|
+
```
|
95
|
+
|
96
|
+
### Skip git initialization
|
97
|
+
|
98
|
+
```bash
|
99
|
+
fips-agents create mcp-server my-server --no-git
|
100
|
+
```
|
101
|
+
|
102
|
+
## Usage
|
103
|
+
|
104
|
+
### Basic Commands
|
105
|
+
|
106
|
+
```bash
|
107
|
+
# Display version
|
108
|
+
fips-agents --version
|
109
|
+
|
110
|
+
# Get help
|
111
|
+
fips-agents --help
|
112
|
+
fips-agents create --help
|
113
|
+
fips-agents create mcp-server --help
|
114
|
+
```
|
115
|
+
|
116
|
+
### Create MCP Server
|
117
|
+
|
118
|
+
```bash
|
119
|
+
fips-agents create mcp-server <project-name> [OPTIONS]
|
120
|
+
```
|
121
|
+
|
122
|
+
**Arguments:**
|
123
|
+
- `project-name`: Name for your MCP server project (must start with lowercase letter, contain only lowercase letters, numbers, hyphens, and underscores)
|
124
|
+
|
125
|
+
**Options:**
|
126
|
+
- `--target-dir, -t PATH`: Target directory for the project (default: current directory)
|
127
|
+
- `--no-git`: Skip git repository initialization
|
128
|
+
- `--help`: Show help message
|
129
|
+
|
130
|
+
**Examples:**
|
131
|
+
|
132
|
+
```bash
|
133
|
+
# Create in current directory
|
134
|
+
fips-agents create mcp-server my-mcp-server
|
135
|
+
|
136
|
+
# Create in specific directory
|
137
|
+
fips-agents create mcp-server my-server -t ~/projects
|
138
|
+
|
139
|
+
# Create without git initialization
|
140
|
+
fips-agents create mcp-server my-server --no-git
|
141
|
+
```
|
142
|
+
|
143
|
+
## Project Name Requirements
|
144
|
+
|
145
|
+
Project names must follow these rules:
|
146
|
+
- Start with a lowercase letter
|
147
|
+
- Contain only lowercase letters, numbers, hyphens (`-`), and underscores (`_`)
|
148
|
+
- Not be empty
|
149
|
+
|
150
|
+
**Valid examples:** `my-server`, `test_mcp`, `server123`, `my-awesome-mcp-server`
|
151
|
+
|
152
|
+
**Invalid examples:** `MyServer` (uppercase), `123server` (starts with number), `my@server` (special characters)
|
153
|
+
|
154
|
+
## After Creating a Project
|
155
|
+
|
156
|
+
Once your project is created, follow these steps:
|
157
|
+
|
158
|
+
```bash
|
159
|
+
# 1. Navigate to your project
|
160
|
+
cd my-mcp-server
|
161
|
+
|
162
|
+
# 2. Create and activate virtual environment
|
163
|
+
python -m venv venv
|
164
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
165
|
+
|
166
|
+
# 3. Install the project
|
167
|
+
pip install -e .[dev]
|
168
|
+
|
169
|
+
# 4. Run tests
|
170
|
+
pytest
|
171
|
+
|
172
|
+
# 5. Start developing!
|
173
|
+
# Edit src/my_mcp_server/ files to add your functionality
|
174
|
+
```
|
175
|
+
|
176
|
+
## Development
|
177
|
+
|
178
|
+
### Setup Development Environment
|
179
|
+
|
180
|
+
```bash
|
181
|
+
# Clone the repository
|
182
|
+
git clone https://github.com/rdwj/fips-agents-cli.git
|
183
|
+
cd fips-agents-cli
|
184
|
+
|
185
|
+
# Create virtual environment
|
186
|
+
python -m venv venv
|
187
|
+
source venv/bin/activate
|
188
|
+
|
189
|
+
# Install with dev dependencies
|
190
|
+
pip install -e .[dev]
|
191
|
+
```
|
192
|
+
|
193
|
+
### Running Tests
|
194
|
+
|
195
|
+
```bash
|
196
|
+
# Run all tests
|
197
|
+
pytest
|
198
|
+
|
199
|
+
# Run with coverage
|
200
|
+
pytest --cov=fips_agents_cli --cov-report=html
|
201
|
+
|
202
|
+
# Run specific test file
|
203
|
+
pytest tests/test_create.py
|
204
|
+
|
205
|
+
# Run specific test
|
206
|
+
pytest tests/test_create.py::TestCreateMcpServer::test_successful_creation
|
207
|
+
```
|
208
|
+
|
209
|
+
### Code Quality
|
210
|
+
|
211
|
+
```bash
|
212
|
+
# Format code with Black
|
213
|
+
black src tests
|
214
|
+
|
215
|
+
# Lint with Ruff
|
216
|
+
ruff check src tests
|
217
|
+
|
218
|
+
# Type checking (if using mypy)
|
219
|
+
mypy src
|
220
|
+
```
|
221
|
+
|
222
|
+
### Project Structure
|
223
|
+
|
224
|
+
```
|
225
|
+
fips-agents-cli/
|
226
|
+
├── pyproject.toml # Project configuration
|
227
|
+
├── README.md # This file
|
228
|
+
├── src/
|
229
|
+
│ └── fips_agents_cli/
|
230
|
+
│ ├── __init__.py # Package initialization
|
231
|
+
│ ├── __main__.py # Entry point for python -m
|
232
|
+
│ ├── cli.py # Main CLI application
|
233
|
+
│ ├── version.py # Version information
|
234
|
+
│ ├── commands/ # CLI command implementations
|
235
|
+
│ │ ├── __init__.py
|
236
|
+
│ │ └── create.py # Create command
|
237
|
+
│ └── tools/ # Utility modules
|
238
|
+
│ ├── __init__.py
|
239
|
+
│ ├── filesystem.py # Filesystem operations
|
240
|
+
│ ├── git.py # Git operations
|
241
|
+
│ └── project.py # Project customization
|
242
|
+
└── tests/ # Test suite
|
243
|
+
├── __init__.py
|
244
|
+
├── conftest.py # Pytest fixtures
|
245
|
+
├── test_create.py # Create command tests
|
246
|
+
├── test_filesystem.py # Filesystem utilities tests
|
247
|
+
└── test_project.py # Project utilities tests
|
248
|
+
```
|
249
|
+
|
250
|
+
## Requirements
|
251
|
+
|
252
|
+
- Python 3.9 or higher
|
253
|
+
- Git (for cloning templates and initializing repositories)
|
254
|
+
|
255
|
+
## Dependencies
|
256
|
+
|
257
|
+
- **click** (>=8.1.0): Command-line interface creation
|
258
|
+
- **rich** (>=13.0.0): Terminal output formatting
|
259
|
+
- **gitpython** (>=3.1.0): Git operations
|
260
|
+
- **tomlkit** (>=0.12.0): TOML file manipulation
|
261
|
+
|
262
|
+
## Contributing
|
263
|
+
|
264
|
+
Contributions are welcome! Please follow these steps:
|
265
|
+
|
266
|
+
1. Fork the repository
|
267
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
268
|
+
3. Make your changes
|
269
|
+
4. Run tests (`pytest`)
|
270
|
+
5. Format code (`black src tests`)
|
271
|
+
6. Commit your changes (`git commit -m 'Add amazing feature'`)
|
272
|
+
7. Push to the branch (`git push origin feature/amazing-feature`)
|
273
|
+
8. Open a Pull Request
|
274
|
+
|
275
|
+
## Troubleshooting
|
276
|
+
|
277
|
+
### Git not found
|
278
|
+
|
279
|
+
If you see "Git is not installed" error:
|
280
|
+
- **macOS**: `brew install git`
|
281
|
+
- **Linux**: `sudo apt-get install git` or `sudo yum install git`
|
282
|
+
- **Windows**: Download from https://git-scm.com/downloads
|
283
|
+
|
284
|
+
### Directory already exists
|
285
|
+
|
286
|
+
If you see "Directory already exists" error:
|
287
|
+
- Choose a different project name
|
288
|
+
- Remove the existing directory: `rm -rf project-name`
|
289
|
+
- Use a different target directory with `--target-dir`
|
290
|
+
|
291
|
+
### Template clone fails
|
292
|
+
|
293
|
+
If template cloning fails:
|
294
|
+
- Check your internet connection
|
295
|
+
- Verify the template repository is accessible: https://github.com/rdwj/mcp-server-template
|
296
|
+
- Try again later if GitHub is experiencing issues
|
297
|
+
|
298
|
+
## License
|
299
|
+
|
300
|
+
MIT License - see LICENSE file for details
|
301
|
+
|
302
|
+
## Links
|
303
|
+
|
304
|
+
- **Repository**: https://github.com/rdwj/fips-agents-cli
|
305
|
+
- **Issues**: https://github.com/rdwj/fips-agents-cli/issues
|
306
|
+
- **MCP Protocol**: https://modelcontextprotocol.io/
|
307
|
+
|
308
|
+
## Changelog
|
309
|
+
|
310
|
+
### Version 0.1.0 (MVP)
|
311
|
+
|
312
|
+
- Initial release
|
313
|
+
- `fips-agents create mcp-server` command
|
314
|
+
- Template cloning and customization
|
315
|
+
- Git repository initialization
|
316
|
+
- Comprehensive test suite
|
317
|
+
- Beautiful CLI output with Rich
|
@@ -0,0 +1,15 @@
|
|
1
|
+
fips_agents_cli/__init__.py,sha256=Jfo6y6T4HIQ-BeeF89w7F-F4BED63KyCIc1yoFGn9OM,167
|
2
|
+
fips_agents_cli/__main__.py,sha256=rUeQY3jrV6hQVAI2IE0qZCcUnvXDMj5LiCjhlXsc9PQ,130
|
3
|
+
fips_agents_cli/cli.py,sha256=e2QGiAH73pHAjpdf7JdovyNZtc2nyb83LaVaLbLqS5o,718
|
4
|
+
fips_agents_cli/version.py,sha256=EeROn-Cy9mXSPq-OrZ--hVR0oQvwMNqIyAgy4w-YowU,70
|
5
|
+
fips_agents_cli/commands/__init__.py,sha256=AGxi2-Oc-gKE3sR9F39nIxwnzz-4bcfkkJzaP1qhMvU,40
|
6
|
+
fips_agents_cli/commands/create.py,sha256=GvGm8VgsIfqZV_yIXXYzeV-nasLIwuD85_l831YjAho,6403
|
7
|
+
fips_agents_cli/tools/__init__.py,sha256=ah4OrYFuyQavuhwguFwFS0o-7ZLGIm5ZWWQK5ZTZZSs,47
|
8
|
+
fips_agents_cli/tools/filesystem.py,sha256=G9wzHrgQpoMw3z5EslsyEL91VLlUT6Cf5Rt80DtkwOw,3313
|
9
|
+
fips_agents_cli/tools/git.py,sha256=-z2EO7vNZhN6Vuzh34ZbqNteZE4vNnLG8oysgzhmypk,3042
|
10
|
+
fips_agents_cli/tools/project.py,sha256=d_qIhsEzPOM55ONSLzheTozXuyuiEEf9OG4hVXISAbk,5730
|
11
|
+
fips_agents_cli-0.1.0.dist-info/METADATA,sha256=j3-wAEIftpKnjwj4BVKykYPQh4eDXGva_RNIKh55Qiw,8220
|
12
|
+
fips_agents_cli-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
13
|
+
fips_agents_cli-0.1.0.dist-info/entry_points.txt,sha256=srO4LAGNp6zcB9zuPW1toLGPyLbcsad9YWsfNxgz20s,57
|
14
|
+
fips_agents_cli-0.1.0.dist-info/licenses/LICENSE,sha256=dQJIqi2t9SinZu0yALTYJ8juzosu29KPbjU8WhyboRc,1068
|
15
|
+
fips_agents_cli-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Wes Jackson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|