Skip to content
Merged
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
53 changes: 53 additions & 0 deletions .claude/skills/slide-theme-generator/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
name: slide-theme-generator
description: Generate a cohesive slide theme (color palette, typography, layout tokens) from a user-provided keyword, brand color, or mood. Outputs a reusable CSS variable block that can be injected into every slide.
---

# Slide Theme Generator

When the user asks to create a presentation theme, set a color scheme, or define a visual style, generate a CSS custom-properties block that all subsequent slides can reference.

## Input

Accept any of the following:
- A keyword or mood (e.g. "corporate", "playful", "dark tech")
- A hex brand color (e.g. "#FF6B00")
- A reference image description

## Output Format

Return a single `<style>` block containing CSS custom properties on `:root`. The block must include **all** of the following tokens:

```css
:root {
/* Palette */
--slide-bg: <background>;
--slide-bg-accent: <secondary background>;
--slide-fg: <primary text>;
--slide-fg-muted: <secondary text>;
--slide-accent: <accent / highlight>;
--slide-accent-hover: <accent variant>;

/* Typography */
--slide-font-title: <font stack for titles>;
--slide-font-body: <font stack for body>;
--slide-fs-title: 48px;
--slide-fs-subtitle: 32px;
--slide-fs-body: 22px;
--slide-fs-caption: 16px;
--slide-lh: 1.6;

/* Spacing & Layout */
--slide-pad: 60px;
--slide-radius: 12px;
--slide-shadow: 0 4px 24px rgba(0,0,0,0.1);
}
```

## Guidelines

1. Ensure WCAG AA contrast between `--slide-fg` and `--slide-bg` (ratio >= 4.5:1).
2. Pick complementary or analogous colors — avoid clashing hues.
3. Use Google Fonts that are commonly available; include `@import` if needed.
4. After outputting the theme block, briefly explain the palette choices (2-3 sentences).
5. If the user provides a brand color, derive the rest of the palette from it.
4 changes: 4 additions & 0 deletions backend/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""API endpoint routers."""

from .env_vars import router as env_vars_router
from .invocations import router as invocations_router
from .mcp_servers import router as mcp_servers_router
from .messages import router as messages_router
from .permissions import router as permissions_router
from .sessions import router as sessions_router
Expand All @@ -10,4 +12,6 @@
"messages_router",
"permissions_router",
"invocations_router",
"env_vars_router",
"mcp_servers_router",
]
159 changes: 159 additions & 0 deletions backend/api/env_vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
Environment variables management endpoints.

Provides API endpoints for reading and managing environment variables
stored in ~/.claude/settings.json under the "env" key.
"""

import json
import logging
from pathlib import Path

from fastapi import APIRouter, HTTPException

from ..models.schemas import (
DeleteEnvVarResponse,
GetEnvVarsResponse,
SetAllEnvVarsRequest,
SetAllEnvVarsResponse,
SetEnvVarRequest,
SetEnvVarResponse,
)

logger = logging.getLogger(__name__)

router = APIRouter()

CLAUDE_SETTINGS_PATH = str(Path.home() / ".claude" / "settings.json")


def _get_settings_path() -> Path:
"""Get the path to the Claude settings file."""
return Path(CLAUDE_SETTINGS_PATH)


def _read_settings() -> dict:
"""Read the Claude settings file."""
settings_path = _get_settings_path()

if not settings_path.exists():
return {}

try:
with open(settings_path, "r") as f:
return json.load(f)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON in settings file: {e}")
raise HTTPException(
status_code=500,
detail=f"Invalid JSON in settings file: {e}",
)
except Exception as e:
logger.error(f"Error reading settings file: {e}")
raise HTTPException(
status_code=500,
detail=f"Failed to read settings file: {e}",
)


def _write_settings(settings: dict) -> None:
"""Write the Claude settings file."""
settings_path = _get_settings_path()

try:
settings_path.parent.mkdir(parents=True, exist_ok=True)

with open(settings_path, "w") as f:
json.dump(settings, f, indent=2)
except Exception as e:
logger.error(f"Error writing settings file: {e}")
raise HTTPException(
status_code=500,
detail=f"Failed to write settings file: {e}",
)


@router.get("/env-vars", response_model=GetEnvVarsResponse)
async def get_env_vars():
"""Get all environment variables from ~/.claude/settings.json."""
settings_path = _get_settings_path()

if not settings_path.exists():
return GetEnvVarsResponse(
env_vars={},
settings_path=CLAUDE_SETTINGS_PATH,
exists=False,
)

settings = _read_settings()
env_vars = settings.get("env", {})

return GetEnvVarsResponse(
env_vars=env_vars,
settings_path=CLAUDE_SETTINGS_PATH,
exists=True,
)


@router.post("/env-vars", response_model=SetEnvVarResponse)
async def set_env_var(request: SetEnvVarRequest):
"""Set a single environment variable in ~/.claude/settings.json."""
if not request.key or not request.key.strip():
raise HTTPException(
status_code=400,
detail="Environment variable key cannot be empty",
)

settings = _read_settings()

if "env" not in settings:
settings["env"] = {}

settings["env"][request.key] = request.value
_write_settings(settings)

return SetEnvVarResponse(
status="success",
message=f"Environment variable '{request.key}' set successfully",
key=request.key,
)


@router.delete("/env-vars/{key}", response_model=DeleteEnvVarResponse)
async def delete_env_var(key: str):
"""Delete an environment variable from ~/.claude/settings.json."""
settings_path = _get_settings_path()

if not settings_path.exists():
raise HTTPException(status_code=404, detail="Settings file not found")

settings = _read_settings()

if "env" not in settings or key not in settings["env"]:
raise HTTPException(
status_code=404,
detail=f"Environment variable '{key}' not found",
)

del settings["env"][key]
_write_settings(settings)

return DeleteEnvVarResponse(
status="success",
message=f"Environment variable '{key}' deleted successfully",
key=key,
)


@router.put("/env-vars", response_model=SetAllEnvVarsResponse)
async def set_all_env_vars(request: SetAllEnvVarsRequest):
"""Replace all environment variables in ~/.claude/settings.json."""
settings = _read_settings()
settings["env"] = request.env_vars
_write_settings(settings)

return SetAllEnvVarsResponse(
status="success",
message=f"Successfully set {len(request.env_vars)} environment variables",
count=len(request.env_vars),
)
Loading
Loading