Skip to content

Commit cedfeee

Browse files
authored
Merge pull request #467 from lbedner/gif-generator-changes
Docs and Media Update
2 parents 44e409c + 9d255b2 commit cedfeee

14 files changed

Lines changed: 302 additions & 56 deletions

File tree

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,20 @@ help: ## Show this help message
137137

138138
gif: ## Convert MP4 to high-quality GIF (usage: make gif INPUT=recording.mp4)
139139
ifndef INPUT
140-
@echo "Usage: make gif INPUT=path/to/video.mp4 [OUTPUT=output.gif] [FPS=15] [WIDTH=1200]"
140+
@echo "Usage: make gif INPUT=path/to/video.mp4 [OUTPUT=output.gif] [FPS=15] [WIDTH=1200] [START=0] [END=10]"
141141
@echo ""
142142
@echo "Options:"
143143
@echo " INPUT - Required. Path to input MP4 file"
144144
@echo " OUTPUT - Optional. Output GIF path (default: same name as input with .gif)"
145145
@echo " FPS - Optional. Frames per second (default: 15, max 30)"
146146
@echo " WIDTH - Optional. Output width in pixels (default: 1200)"
147+
@echo " START - Optional. Start time in seconds (default: beginning)"
148+
@echo " END - Optional. End time in seconds (default: end of video)"
147149
@exit 1
148150
endif
149151
@echo "🎬 Converting $(INPUT) to GIF..."
150152
@mkdir -p .gif-frames
151-
@ffmpeg -i "$(INPUT)" -vf "fps=$(or $(FPS),15),scale=$(or $(WIDTH),1200):-1:flags=lanczos" -y .gif-frames/frame_%04d.png
153+
@ffmpeg $(if $(START),-ss $(START)) -i "$(INPUT)" $(if $(END),-to $(END)) -vf "fps=$(or $(FPS),15),scale=$(or $(WIDTH),1200):-1:flags=lanczos" -y .gif-frames/frame_%04d.png
152154
@gifski -o "$(or $(OUTPUT),$(basename $(INPUT)).gif)" --fps $(or $(FPS),15) --quality 90 .gif-frames/*.png
153155
@rm -rf .gif-frames
154156
@echo "✅ Created: $(or $(OUTPUT),$(basename $(INPUT)).gif)"

README.md

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ No time for health checks, proper testing, or clean architecture. Just enough ti
2020

2121
![Aegis Stack Quick Start Demo](docs/images/aegis-demo.gif)
2222

23-
Aegis Stack is a CLI that scaffolds modular Python applications — start with an API, add Auth, Scheduler, Workers, or AI services when you need them.
23+
Aegis Stack is a system for creating and evolving modular Python applications over time, built on tools you already know.
2424

2525
## Prerequisites
2626

@@ -45,6 +45,42 @@ cd my-api && uv sync && cp .env.example .env && make serve
4545

4646
**Installation alternatives:** See the [Installation Guide](https://lbedner.github.io/aegis-stack/installation/) for `uv tool install`, `pip install`, and development setup.
4747

48+
## Overseer - Built-In System Visibility
49+
50+
![Overseer](docs/images/overseer-demo.gif)
51+
52+
**[Overseer](https://lbedner.github.io/aegis-stack/overseer/)** is the built-in system dashboard that ships with every Aegis Stack project.
53+
54+
It provides a live view of what your application is doing at runtime - across core components (Backend, Database, Workers, Scheduler) and services (Auth, AI, Comms) - through a web UI.
55+
56+
Overseer goes beyond simple health checks. You can inspect worker queues, scheduled jobs, database state, and AI usage, all in one place, without wiring up external tools.
57+
58+
No Datadog. No New Relic. No vendor lock-in.
59+
60+
Just a clear view of your system, included from day one.
61+
62+
## CLI - First-Class System Interface
63+
64+
![CLI Demo](docs/images/cli-demo.gif)
65+
66+
The Aegis CLI is a first-class interface to your running system.
67+
68+
It goes beyond simple health checks, exposing rich, component-specific commands for inspecting and understanding your application from the terminal.
69+
70+
Query worker queues, scheduler activity, database state, AI usage, and service configuration, all without leaving the CLI.
71+
72+
The same system intelligence that powers Overseer and Illiana is available here, optimized for terminal workflows.
73+
74+
## Illiana - Optional System Operator
75+
76+
![Illiana Demo](docs/images/illiana-demo.gif)
77+
78+
When the AI service is enabled, Aegis exposes an additional interface: **Illiana**.
79+
80+
Illiana is a conversational interface that answers questions about your running system using live telemetry and optional RAG over your codebase.
81+
82+
She is not required to use Aegis Stack, and nothing in the system depends on her being present. When enabled, she becomes another way, alongside the CLI and Overseer, to understand what your application is doing and why.
83+
4884
## Your Stack Grows With You
4985

5086
**Your choices aren't permanent.** Start with what you need today, add components when requirements change, remove what you outgrow.
@@ -75,36 +111,21 @@ aegis update
75111

76112
Most starters lock you in at `init`. Aegis Stack doesn't. See **[Evolving Your Stack](https://lbedner.github.io/aegis-stack/evolving-your-stack/)** for the complete guide.
77113

78-
## See It In Action
79-
80-
### Overseer - Built-In Health Monitoring
81-
82-
![Overseer](docs/images/overseer-demo.gif)
83-
84-
**[Overseer](https://lbedner.github.io/aegis-stack/overseer/)** is the read-only health monitoring dashboard built into every Aegis Stack project. It provides real-time visibility into all your components (Backend, Database, Worker, Scheduler) and services (Auth, AI, Comms) through a web UI and CLI commands.
85-
86-
No Datadog. No New Relic. No vendor lock-in. Just centralized monitoring you own from day one.
87-
88-
### CLI Health Monitoring
89-
90-
![CLI Health Check](docs/images/cli_health_check.png)
91-
92-
Rich terminal output showing detailed component status, health metrics, and system diagnostics.
93-
94114
## Available Components & Services
95115

96116
**Components** (infrastructure)
97117

98-
- **Core** - API + Frontend (always included)
99-
- **Database** - ORM with health monitoring
100-
- **Scheduler** - Background tasks, cron jobs
101-
- **Worker** - Async task queues
118+
- **Core** → FastAPI + Pydantic V2 + Uvicorn
119+
- **Database** → Postgres / SQLite
120+
- **Cache/Queue** → Redis
121+
- **Scheduler** → APScheduler
122+
- **Worker** → Arq / Taskiq
102123

103124
**Services** (business logic)
104125

105-
- **Auth** - User authentication & JWT
106-
- **AI** - Multi-provider AI chat
107-
- **Comms** - Email, SMS, voice calls
126+
- **Auth** → JWT authentication
127+
- **AI** → PydanticAI / LangChain
128+
- **Comms** → Resend + Twilio
108129

109130
[Components Docs →](https://lbedner.github.io/aegis-stack/components/) | [Services Docs →](https://lbedner.github.io/aegis-stack/services/)
110131

@@ -121,6 +142,6 @@ Rich terminal output showing detailed component status, health metrics, and syst
121142

122143
No reinventing the wheel. Just the tools you already know, pre-configured and ready to compose.
123144

124-
Aegis Stack respects your expertise. We maintain existing standards - FastAPI for APIs, SQLModel for databases, arq for workers. No custom abstractions or proprietary patterns to learn. Pick your components, get a production-ready foundation, and build your way.
145+
Aegis Stack respects your expertise. No custom abstractions or proprietary patterns to learn. Pick your components, get a production-ready foundation, and build your way.
125146

126-
The tool gets out of your way so you can get started.
147+
Aegis gets out of your way so you can get started.

aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ RAG_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 # Free, local embedding model
142142
# Chunking settings
143143
RAG_CHUNK_SIZE=2000
144144
RAG_CHUNK_OVERLAP=400
145-
RAG_DEFAULT_TOP_K=5
146-
RAG_CHAT_TOP_K=10
145+
RAG_DEFAULT_TOP_K=15
146+
RAG_CHAT_TOP_K=15
147147
{% endif %}
148148

149149
{% if include_comms %}

aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ from ..services.ai.providers import ProviderNotInstalledError
3535
# Initialize logging at module load
3636
setup_logging()
3737

38+
# Provider display name aliases
39+
PROVIDER_DISPLAY_NAMES: dict[str, str] = {
40+
"public": "LLM7.io",
41+
"unknown": "LLM7.io",
42+
}
43+
44+
45+
def get_provider_display_name(provider: AIProvider | str) -> str:
46+
"""Get display name for a provider, with aliases for branding."""
47+
value = provider.value if isinstance(provider, AIProvider) else provider
48+
return PROVIDER_DISPLAY_NAMES.get(value, value)
49+
50+
3851
app = typer.Typer(help="AI service management and chat commands")
3952
console = Console()
4053

@@ -58,7 +71,7 @@ def status() -> None:
5871
+ typer.style(status_text, fg=status_color)
5972
)
6073
typer.echo(
61-
typer.style("Provider: ", fg=typer.colors.CYAN) + ai_config.provider.value
74+
typer.style("Provider: ", fg=typer.colors.CYAN) + get_provider_display_name(ai_config.provider)
6275
)
6376
typer.echo(typer.style("Model: ", fg=typer.colors.CYAN) + str(ai_config.model))
6477
typer.echo(
@@ -168,7 +181,7 @@ def providers() -> None:
168181
free_tier = "Yes" if provider in free_providers else "No"
169182

170183
table.add_row(
171-
provider.value,
184+
get_provider_display_name(provider),
172185
installed_display,
173186
api_key_display,
174187
status,
@@ -532,6 +545,7 @@ def chat(
532545
{% if ai_rag %}
533546
use_rag=rag_enabled,
534547
rag_collection=rag_collection,
548+
rag_top_k=top_k,
535549
show_sources=show_sources,
536550
{% endif %}
537551
)
@@ -618,7 +632,7 @@ def history(
618632
typer.echo(f"Conversation: {conversation_id}")
619633
if conversation.title:
620634
typer.echo(f"Title: {conversation.title}")
621-
typer.echo(f"Provider: {conversation.provider.value}")
635+
typer.echo(f"Provider: {get_provider_display_name(conversation.provider)}")
622636
typer.echo(f"Messages: {conversation.get_message_count()}")
623637
typer.echo("")
624638

@@ -701,7 +715,8 @@ def usage(
701715
class RecentActivity(BaseModel):
702716
timestamp: str
703717
model: str
704-
tokens: int
718+
input_tokens: int
719+
output_tokens: int
705720
cost: float
706721
success: bool
707722
action: str
@@ -732,9 +747,15 @@ def usage(
732747
"groq": "yellow",
733748
"mistral": "cyan",
734749
"cohere": "bright_red",
750+
"public": "bright_cyan",
751+
"llm7.io": "bright_cyan",
735752
}
736753
return colors.get(vendor.lower(), fallback)
737754

755+
def get_vendor_display_name(vendor: str) -> str:
756+
"""Get display name for a vendor, with aliases for branding."""
757+
return PROVIDER_DISPLAY_NAMES.get(vendor.lower(), vendor)
758+
738759
# Display functions
739760
def display_summary_panel(stats: UsageStatsResponse) -> None:
740761
success_color = get_success_color(stats.success_rate)
@@ -791,9 +812,10 @@ def usage(
791812
table.add_column("Share", justify="right")
792813
for model in stats.models:
793814
vendor_color = get_vendor_color(model.vendor, model.vendor_color)
815+
vendor_display = get_vendor_display_name(model.vendor)
794816
table.add_row(
795817
model.model_title,
796-
f"[{vendor_color}]{model.vendor}[/{vendor_color}]",
818+
f"[{vendor_color}]{vendor_display}[/{vendor_color}]",
797819
format_number(model.requests),
798820
format_number(model.tokens),
799821
format_cost(model.cost),
@@ -820,11 +842,12 @@ def usage(
820842
except ValueError:
821843
time_str = activity.timestamp[:8]
822844
status = "[green]OK[/green]" if activity.success else "[red]FAIL[/red]"
845+
total_tokens = activity.input_tokens + activity.output_tokens
823846
table.add_row(
824847
time_str,
825848
activity.model,
826849
activity.action,
827-
format_number(activity.tokens),
850+
format_number(total_tokens),
828851
format_cost(activity.cost),
829852
status,
830853
)
@@ -1007,6 +1030,7 @@ async def _interactive_chat_session(
10071030
{% if ai_rag %}
10081031
use_rag: bool = False,
10091032
rag_collection: str | None = None,
1033+
rag_top_k: int | None = None,
10101034
show_sources: bool = False,
10111035
{% endif %}
10121036
) -> None:
@@ -1027,8 +1051,10 @@ async def _interactive_chat_session(
10271051
await asyncio.sleep(0.15)
10281052
console.print(" [green]OK[/green]")
10291053

1030-
console.print(f" [dim]>[/dim] Connecting to [cyan]{ai_config.provider.value}[/cyan]...", end="")
1031-
await asyncio.sleep(0.2)
1054+
console.print(f" [dim]>[/dim] Connecting to [cyan]{get_provider_display_name(ai_config.provider)}[/cyan]...", end="")
1055+
# Warm up the agent (lazy imports, model initialization)
1056+
from app.services.ai.providers import get_agent
1057+
_ = get_agent(ai_config, settings)
10321058
console.print(" [green]OK[/green]")
10331059

10341060
console.print(f" [dim]>[/dim] Model: [cyan]{ai_config.model}[/cyan]")
@@ -1090,7 +1116,7 @@ async def _interactive_chat_session(
10901116

10911117
# Initialize status line state for prompt_toolkit toolbar
10921118
session_state = ChatSessionState(
1093-
provider=ai_config.provider.value,
1119+
provider=get_provider_display_name(ai_config.provider),
10941120
model=ai_config.model,
10951121
{% if ai_rag %}
10961122
rag_enabled=use_rag,
@@ -1241,6 +1267,7 @@ async def _interactive_chat_session(
12411267
{% if ai_rag %}
12421268
use_rag=use_rag,
12431269
rag_collection=rag_collection,
1270+
rag_top_k=rag_top_k,
12441271
show_sources=show_sources,
12451272
{% endif %}
12461273
session_state=session_state,
@@ -1274,6 +1301,7 @@ async def _interactive_chat_session(
12741301
{% if ai_rag %}
12751302
use_rag=use_rag,
12761303
rag_collection=rag_collection,
1304+
rag_top_k=rag_top_k,
12771305
{% endif %}
12781306
)
12791307
finally:

aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ class Settings(BaseSettings):
205205
RAG_MODEL_CACHE_DIR: str | None = None # None = system default, "./data/models" for local dev
206206
RAG_CHUNK_SIZE: int = 2000
207207
RAG_CHUNK_OVERLAP: int = 400
208-
RAG_DEFAULT_TOP_K: int = 5
209-
RAG_CHAT_TOP_K: int = 10 # Number of chunks for AI chat context
208+
RAG_DEFAULT_TOP_K: int = 15
209+
RAG_CHAT_TOP_K: int = 15 # Number of chunks for AI chat context
210210
{% endif %}
211211

212212
{% if include_redis or database_engine == "postgres" %}

aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/fixtures/llm_fixtures.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
Contains seed data for LLM vendors, models, deployments, and pricing (Dec 2024 rates).
44
55
Architecture:
6-
- LLMVendor: API providers (OpenAI, Anthropic, public/LLM7.io, etc.)
6+
- LLMVendor: API providers (OpenAI, Anthropic, LLM7.io, etc.)
77
- LargeLanguageModel: Unique models (gpt-4o-mini exists ONCE, owned by OpenAI)
8-
- LLMDeployment: Which vendors offer which models (public deploys gpt-4o-mini via proxy)
9-
- LLMPrice: Per vendor-model pricing (OpenAI charges $0.15, public charges $0.00)
8+
- LLMDeployment: Which vendors offer which models (LLM7.io deploys gpt-4o-mini via proxy)
9+
- LLMPrice: Per vendor-model pricing (OpenAI charges $0.15, LLM7.io charges $0.00)
1010
"""
1111

1212
from datetime import UTC, datetime
@@ -69,9 +69,9 @@
6969
"auth_method": "api-key",
7070
},
7171
{
72-
"name": "public",
73-
"description": "Public free endpoints via LLM7.io (no API key required)",
74-
"color": "#6B7280",
72+
"name": "LLM7.io",
73+
"description": "Free public endpoints via LLM7.io (no API key required)",
74+
"color": "#00D4AA",
7575
"api_base": "https://api.llm7.io/v1",
7676
"auth_method": "none",
7777
},
@@ -294,8 +294,8 @@
294294
"family": "command",
295295
},
296296
],
297-
# Public vendor's "auto" model - represents dynamic model selection via LLM7.io
298-
"public": [
297+
# LLM7.io's "auto" model - represents dynamic model selection
298+
"LLM7.io": [
299299
{
300300
"model_id": "auto",
301301
"title": "Auto (LLM7.io)",
@@ -418,8 +418,8 @@
418418
{"model_id": "command-r", "speed": 75, "intelligence": 75, "reasoning": 72},
419419
{"model_id": "command-light", "speed": 90, "intelligence": 55, "reasoning": 50},
420420
],
421-
# Public deploys models via LLM7.io proxy (free but slower, no streaming)
422-
"public": [
421+
# LLM7.io deploys models via proxy (free but slower, no streaming)
422+
"LLM7.io": [
423423
{"model_id": "gpt-4o-mini", "speed": 40, "intelligence": 75, "reasoning": 70},
424424
{"model_id": "auto", "speed": 40, "intelligence": 75, "reasoning": 70},
425425
],
@@ -457,9 +457,9 @@
457457
("cohere", "command-r-plus"): {"input": 2.50, "output": 10.00},
458458
("cohere", "command-r"): {"input": 0.15, "output": 0.60},
459459
("cohere", "command-light"): {"input": 0.03, "output": 0.06},
460-
# Public pricing (free via LLM7.io)
461-
("public", "gpt-4o-mini"): {"input": 0.00, "output": 0.00},
462-
("public", "auto"): {"input": 0.00, "output": 0.00},
460+
# LLM7.io pricing (free)
461+
("LLM7.io", "gpt-4o-mini"): {"input": 0.00, "output": 0.00},
462+
("LLM7.io", "auto"): {"input": 0.00, "output": 0.00},
463463
}
464464

465465

aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/rag/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class RAGServiceConfig(BaseModel):
5454
description="Overlap between chunks in characters",
5555
)
5656
default_top_k: int = Field(
57-
default=5,
57+
default=15,
5858
gt=0,
5959
le=50,
6060
description="Default number of search results",

0 commit comments

Comments
 (0)