---
phase: 01-foundation-configuration
plan: 01
type: execute
---

<objective>
Set up the foundational project structure with modern Python tooling, implement a type-safe configuration system, and create the basic CLI entry point.

Purpose: Establish the core infrastructure that all future features will build upon, using modern Python patterns (src layout, Typer, Pydantic).
Output: Working project structure with configuration loading and a minimal CLI that can be installed and run.
</objective>

<execution_context>
~/.claude/get-shit-done/workflows/execute-phase.md
./summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/01-foundation-configuration/01-CONTEXT.md
@.planning/phases/01-foundation-configuration/01-RESEARCH.md
@REQUIREMENTS.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Initialize project structure with src layout</name>
  <files>pyproject.toml, src/n8n_cli/__init__.py, src/n8n_cli/__main__.py, src/n8n_cli/cli.py, .gitignore</files>
  <action>
Create modern Python project structure using src layout:

1. Create pyproject.toml with:
   - Build system: hatchling
   - Python version: >=3.12
   - Dependencies: typer[all]>=0.13.0, pydantic>=2.0, pydantic-settings>=2.0, httpx>=0.27.0, python-dotenv>=1.0
   - Dev dependencies: pytest>=8.0, pytest-cov>=7.0.0, pytest-mock>=3.12, ruff>=0.8.0, pre-commit>=6.0
   - Entry point: [project.scripts] n8n = "n8n_cli.cli:main"
   - Package config: [tool.hatch.build.targets.wheel] packages = ["src/n8n_cli"]

2. Create src/n8n_cli/ directory structure:
   - __init__.py: Empty for now (version will be added later)
   - __main__.py: Entry point for `python -m n8n_cli` that calls cli.main()
   - cli.py: Stub with basic imports (Typer app will be implemented in Task 3)

3. Create .gitignore with standard Python entries:
   - __pycache__/, *.py[cod], *$py.class
   - .pytest_cache/, .coverage, htmlcov/
   - dist/, build/, *.egg-info/
   - .env, .venv/, venv/
   - .ruff_cache/

DO NOT use Click - use Typer (user chose modern alternative).
DO NOT add Black/isort/Flake8 - use Ruff only (user chose modern tooling).
DO NOT use flat layout - use src layout (industry standard per research).
  </action>
  <verify>ls -la shows pyproject.toml, src/n8n_cli/ structure exists, all specified files created</verify>
  <done>Project structure follows src layout, pyproject.toml has all modern dependencies, entry points configured</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Implement type-safe configuration system with Pydantic</name>
  <files>src/n8n_cli/config.py, tests/test_config.py</files>
  <test-first>
Test: Config loads from .env with precedence
Cases:
  - Load from local .env: N8N_API_KEY and N8N_INSTANCE_URL set in .env → config.api_key and config.instance_url populated
  - Load from global config: ~/.n8n-cli/config exists → values loaded
  - Precedence: local .env takes priority over global config
  - Missing config: no .env or global → fields are None (no error)
  </test-first>
  <action>
Create src/n8n_cli/config.py with Pydantic BaseSettings:

```python
from pydantic_settings import BaseSettings, SettingsConfigDict
from pathlib import Path
from typing import Optional

class N8nConfig(BaseSettings):
    """n8n CLI configuration with hierarchy:
    1. Environment variables (highest priority)
    2. Local .env file
    3. Global config file (~/.n8n-cli/config)
    4. Defaults (lowest priority)
    """
    model_config = SettingsConfigDict(
        env_file='.env',
        env_file_encoding='utf-8',
        env_prefix='N8N_',
        extra='ignore'
    )

    api_key: Optional[str] = None
    instance_url: Optional[str] = None

    @classmethod
    def load(cls) -> "N8nConfig":
        """Load config with proper precedence."""
        # 1. Check local .env first
        if Path('.env').exists():
            return cls(_env_file='.env')

        # 2. Check global config
        global_config = Path.home() / '.config' / 'n8n-cli' / 'config'
        if global_config.exists():
            return cls(_env_file=str(global_config))

        # 3. Environment variables only
        return cls()
```

Follow RESEARCH.md Pattern 1 (Configuration Hierarchy with Pydantic).
Use Optional[str] for fields (not str | None) for Pydantic v2 compatibility.
Support both .env and global config paths as specified in CONTEXT.md.
  </action>
  <verify>pytest tests/test_config.py passes all cases</verify>
  <done>Config loads from .env and global config with proper precedence, tests document all scenarios</done>
</task>

<task type="auto">
  <name>Task 3: Create basic CLI entry point with Typer</name>
  <files>src/n8n_cli/cli.py, tests/test_cli.py</files>
  <action>
Implement basic Typer CLI in src/n8n_cli/cli.py:

```python
import typer
from typing import Optional

app = typer.Typer(
    name="n8n",
    help="CLI for managing n8n Cloud workflows",
    add_completion=False  # We'll add custom completion later
)

def version_callback(value: bool):
    if value:
        typer.echo("n8n CLI version 0.1.0")
        raise typer.Exit()

@app.callback()
def main(
    version: Optional[bool] = typer.Option(
        None,
        "--version",
        callback=version_callback,
        is_eager=True,
        help="Show version and exit"
    )
):
    """n8n CLI - Manage n8n Cloud workflows from the terminal."""
    pass

if __name__ == "__main__":
    app()
```

Create basic test in tests/test_cli.py:
- Test --version flag displays version
- Test --help displays help text
- Use typer.testing.CliRunner for testing (not click.testing)

DO NOT implement command groups yet (workflow, project, etc) - that's for later phases.
DO NOT add completion yet - that's Phase 6.
DO NOT use Click decorators (@click.command) - use Typer.
  </action>
  <verify>python -m n8n_cli --version shows version, python -m n8n_cli --help shows help, pytest tests/test_cli.py passes</verify>
  <done>Typer app runs with --version and --help, tests verify basic functionality</done>
</task>

</tasks>

<verification>
Before declaring plan complete:
- [ ] `ls src/n8n_cli/` shows all source files (__init__.py, __main__.py, cli.py, config.py)
- [ ] `cat pyproject.toml` shows Typer, Pydantic, httpx, Ruff dependencies
- [ ] `pytest` passes all tests (test_config.py, test_cli.py)
- [ ] `python -m n8n_cli --version` works
- [ ] `python -m n8n_cli --help` shows help text
</verification>

<success_criteria>

- All tasks completed
- All verification checks pass
- No errors when running CLI
- Configuration system loads from .env and global config
- Tests document expected behavior
- Project follows modern Python patterns (src layout, Typer, Pydantic)
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation-configuration/01-01-SUMMARY.md`:

# Phase 1 Plan 1: Foundation Setup Summary

**Project structure initialized with modern Python tooling and type-safe configuration**

## Accomplishments

- Created src layout project structure with pyproject.toml
- Implemented Pydantic-based configuration system with .env and global config support
- Built basic Typer CLI with version command
- All tests passing

## Files Created/Modified

- `pyproject.toml` - Project configuration with modern dependencies (Typer, Pydantic, httpx, Ruff)
- `src/n8n_cli/__init__.py` - Package init
- `src/n8n_cli/__main__.py` - Entry point for python -m
- `src/n8n_cli/cli.py` - Typer CLI app with version command
- `src/n8n_cli/config.py` - Pydantic BaseSettings configuration loader
- `tests/test_config.py` - Configuration tests with TDD
- `tests/test_cli.py` - Basic CLI tests
- `.gitignore` - Python gitignore entries

## Decisions Made

- **Typer over Click**: Modern type-hint driven CLI framework (cleaner syntax, still uses Click under the hood)
- **Pydantic BaseSettings**: Type-safe configuration with validation instead of plain python-dotenv
- **src layout**: Industry standard project structure for 2026
- **httpx dependency**: Added for Phase 2 API client (modern, async-ready)

## Issues Encountered

None

## Next Step

Ready for 01-02-PLAN.md (Quality Tooling setup with Ruff and pre-commit hooks)
</output>
