Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,27 @@ The CLI will check if you have Claude Code or Gemini CLI installed. If you do no
specify init <project_name> --ai claude --ignore-agent-tools
```

#### Gemini CLI Extension Setup

When you select Gemini as your AI assistant (`--ai gemini`), the CLI automatically creates a Gemini CLI extension folder structure in your project:

```
<workspace>/.gemini/extensions/spec-kit/
├── gemini-extension.json
└── GEMINI.md
```

The `gemini-extension.json` file contains the extension configuration:
```json
{
"name": "spec-kit",
"version": "1.0.0",
"description": "Spec-kit Gemini CLI extension"
}
```

The `GEMINI.md` file provides documentation for using Spec Kit with Gemini CLI.

### **STEP 1:** Bootstrap the project

Go to the project folder and run your AI agent. In our example, we're using `claude`.
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ dependencies = [
"readchar",
]

[project.optional-dependencies]
test = [
"pytest>=7.0.0",
]

[project.scripts]
specify = "specify_cli:main"

Expand Down
56 changes: 56 additions & 0 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,53 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
os.chdir(original_cwd)


def setup_gemini_extension(workspace: Path) -> bool:
"""Set up Gemini CLI extension folder and files.

Creates:
- <workspace>/.gemini/extensions/spec-kit/
- gemini-extension.json with default contents
- GEMINI.md with stub contents

Args:
workspace: Path to the project workspace

Returns:
bool: True if setup was successful, False otherwise
"""
try:
# Create the extension directory structure
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
extension_dir.mkdir(parents=True, exist_ok=True)

# Create gemini-extension.json
extension_config = {
"name": "spec-kit",
"version": "1.0.0",
"description": "Spec-kit Gemini CLI extension"
}

extension_json_path = extension_dir / "gemini-extension.json"
with open(extension_json_path, 'w', encoding='utf-8') as f:
json.dump(extension_config, f, indent=2)

# Create GEMINI.md
gemini_md_content = """# GEMINI Extension for Spec Kit

This extension integrates Spec Kit with Gemini CLI.
"""

gemini_md_path = extension_dir / "GEMINI.md"
with open(gemini_md_path, 'w', encoding='utf-8') as f:
f.write(gemini_md_content)

return True

except Exception as e:
console.print(f"[red]Error setting up Gemini extension:[/red] {e}")
return False


def download_template_from_github(ai_assistant: str, download_dir: Path, *, verbose: bool = True, show_progress: bool = True):
"""Download the latest template release from GitHub using HTTP requests.
Returns (zip_path, metadata_dict)
Expand Down Expand Up @@ -787,6 +834,15 @@ def init(
else:
tracker.skip("git", "--no-git flag")

# Gemini extension setup
if selected_ai == "gemini":
tracker.add("gemini-ext", "Setup Gemini extension")
tracker.start("gemini-ext")
if setup_gemini_extension(project_path):
tracker.complete("gemini-ext", "extension files created")
else:
tracker.error("gemini-ext", "setup failed")

tracker.complete("final", "project ready")
except Exception as e:
tracker.error("final", str(e))
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test package for specify-cli
127 changes: 127 additions & 0 deletions tests/test_gemini_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Tests for Gemini extension setup functionality."""

import json
import tempfile
from pathlib import Path
import pytest

# Import the function we want to test
from specify_cli import setup_gemini_extension


def test_setup_gemini_extension_creates_directory_structure():
"""Test that setup_gemini_extension creates the correct directory structure."""
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)

# Call the function
result = setup_gemini_extension(workspace)

# Check that it returned True
assert result is True

# Check that the directory structure was created
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
assert extension_dir.exists()
assert extension_dir.is_dir()


def test_setup_gemini_extension_creates_json_file():
"""Test that setup_gemini_extension creates gemini-extension.json with correct contents."""
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)

# Call the function
result = setup_gemini_extension(workspace)

# Check that it returned True
assert result is True

# Check that the JSON file was created
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
json_file = extension_dir / "gemini-extension.json"
assert json_file.exists()
assert json_file.is_file()

# Check the contents of the JSON file
with open(json_file, 'r', encoding='utf-8') as f:
config = json.load(f)

expected_config = {
"name": "spec-kit",
"version": "1.0.0",
"description": "Spec-kit Gemini CLI extension"
}
assert config == expected_config


def test_setup_gemini_extension_creates_markdown_file():
"""Test that setup_gemini_extension creates GEMINI.md with correct contents."""
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)

# Call the function
result = setup_gemini_extension(workspace)

# Check that it returned True
assert result is True

# Check that the markdown file was created
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
md_file = extension_dir / "GEMINI.md"
assert md_file.exists()
assert md_file.is_file()

# Check the contents of the markdown file
with open(md_file, 'r', encoding='utf-8') as f:
content = f.read()

expected_content = """# GEMINI Extension for Spec Kit

This extension integrates Spec Kit with Gemini CLI.
"""
assert content == expected_content


def test_setup_gemini_extension_handles_existing_directory():
"""Test that setup_gemini_extension works when directory already exists."""
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)

# Create the directory structure first
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
extension_dir.mkdir(parents=True, exist_ok=True)

# Call the function
result = setup_gemini_extension(workspace)

# Check that it returned True
assert result is True

# Check that the files were still created
json_file = extension_dir / "gemini-extension.json"
md_file = extension_dir / "GEMINI.md"
assert json_file.exists()
assert md_file.exists()


def test_setup_gemini_extension_handles_permission_error():
"""Test that setup_gemini_extension handles permission errors gracefully."""
with tempfile.TemporaryDirectory() as temp_dir:
workspace = Path(temp_dir)

# Create a file with the same name as the directory we want to create
# This will cause a permission error when trying to create the directory
extension_dir = workspace / ".gemini" / "extensions" / "spec-kit"
extension_dir.parent.mkdir(parents=True, exist_ok=True)
extension_dir.touch() # Create a file instead of directory

# Call the function
result = setup_gemini_extension(workspace)

# Check that it returned False due to the error
assert result is False


if __name__ == "__main__":
pytest.main([__file__])