Merge branch 'use_case_agent' into 'main'

Use case agent

See merge request aliasrobotics/alias_research/cai!210
This commit is contained in:
Víctor Mayoral Vilches 2025-06-04 12:33:41 +00:00
parent cef6ac531b
commit 7611a9bb6e
5 changed files with 485 additions and 321 deletions

2
.gitignore vendored
View File

@ -34,7 +34,7 @@ MANIFEST
# PyInstaller
*.manifest
*.spec
alias_web*
# Installer logs
pip-log.txt
pip-delete-this-directory.txt

View File

@ -3,11 +3,7 @@ import os
from dotenv import load_dotenv
from cai.sdk.agents import Agent, OpenAIChatCompletionsModel
from openai import AsyncOpenAI
from cai.tools.reconnaissance.generic_linux_command import (
generic_linux_command
)
from cai.tools.reconnaissance.generic_linux_command import null_tool
from cai.util import load_prompt_template
load_dotenv()
@ -30,8 +26,7 @@ use_case_agent_system_prompt = load_prompt_template("prompts/system_use_cases.md
# find_file,
# execute_code,
# ]
tools = [generic_linux_command]
tools = [null_tool]
# Create the agent
use_case_agent = Agent(
name="Use Case Agent",

View File

@ -1,5 +1,5 @@
<%doc>
# Cybersecurity Case Study Agent
Q# Cybersecurity Case Study Agent
# System prompt for agent that creates cybersecurity case studies
</%doc>
@ -54,6 +54,43 @@ if not cai_paper_context or "Error" in cai_paper_context:
oversight (HITL) functionality. It implements a multi-agent architecture with specialized
agents for different cybersecurity tasks, allowing for efficient and comprehensive security testing.
"""
# Get template from file
import os
import sys
# Try multiple possible template locations
# First try to get the base directory from environment or use a fallback
base_dir = os.environ.get('CAI_BASE_DIR', '/Users/luijait/cai_gitlab')
# Build paths more carefully to avoid issues with undefined __file__
template_paths = [
os.path.join(base_dir, "tools", "templates", "case-study.php"),
"/Users/luijait/cai_gitlab/tools/templates/case-study.php",
"tools/templates/case-study.php"
]
# Try to add relative path if we can determine current location
try:
# Get the directory where prompts are stored
prompts_dir = os.path.join(base_dir, "src", "cai", "prompts")
relative_template = os.path.join(prompts_dir, "..", "..", "..", "tools", "templates", "case-study.php")
template_paths.append(os.path.normpath(relative_template))
except:
pass
template_content = ""
for path in template_paths:
try:
with open(path, 'r') as template_file:
template_content = template_file.read()
break
except:
continue
if not template_content:
# If file not found, use a placeholder message
template_content = "<!-- Template file not found. Please check paths: " + str(template_paths) + " -->"
%>
You are a specialized AI assistant designed to help create cybersecurity case studies based on CAI (Cybersecurity AI) capabilities. Your task is to complete the TEMPLATE TODO sections in the case study template file.
@ -65,6 +102,7 @@ Your primary purpose is to:
2. Fill in all TEMPLATE TODO sections in the case-study.php.template file
3. Create a complete, professional case study that demonstrates CAI's capabilities
4. Save the completed case study as a new file in the same directory
5. Ensure your text doesn't contain special characters that could break JSON formatting
## TEMPLATE STRUCTURE
@ -102,318 +140,24 @@ You have access to:
## TEMPLATE TO FOLLOW
${template_content}
Remember to only fill in the TEMPLATE TODO sections while preserving all other HTML PHP JS and formatting from the template file. Create a new file with the completed content.
## OUTPUT FORMAT
When generating a case study, you must:
1. Output ONLY the complete PHP code
2. Wrap the PHP code in markdown code blocks: ```php ... ```
3. Do not include any explanatory text before or after the code
4. The PHP code should be complete and ready to save to a file
5. Fill in ALL TEMPLATE-TODO sections with relevant information from the loaded context
Example output format:
```php
<!doctype html>
<html lang="en">
<head>
<?php include("inc/metas.php"); ?>
<title>Case Study - CAI-powered API vulnerability discovery at Mercado Libre</title>
<link rel="stylesheet" href="css/use-case.css">
<link rel="stylesheet" href="css/about.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Wruczek/Bootstrap-Cookie-Alert@gh-pages/cookiealert.css">
<link href="css/worksans.css" rel="stylesheet">
<meta name="description" content="CAI (Cybersecurity AI) framework discovers and demonstrates API vulnerabilities at Mercado Libre through automated enumeration attacks. AI-powered security testing reveals user data exposure risks." />
<meta name="keywords" content="MercadoLibre, AI, robots, cybersecurity" />
</head>
<body class="research certification">
<?php include("inc/menu.php"); ?>
<div class="use-case-b">
<div class="container">
<div class="row">
<div class="">
<img class="img-responsive d-none d-sm-block" src="img/header-mercado-libre.png" alt="Card image">
<!-- <img class="img-responsive d-sm-none" src="img/header_ris_jr_mobile.jpg" alt="Card image"> -->
</div>
</div>
</div>
</div>
<!-- PROMPT: Search throughout the document and find all comments wherein TEMPLATE-TODO is present. In those cases, replace the content below as appropriate by using the following content from the jsonl file using only the last two lines. -->
<div class="container-fluid">
<div class="row">
<div class="container">
<div class="row py-4 pb-2">
<div class="col-12 col-lg-6 col-xl-6">
<h4 class="linkotherservices"><a href="case-studies-robot-cybersecurity.php"><i class="fa fa-chevron-left"></i> Other case studies</a></h4>
<h2 class="pb-4">The use case</h2>
<!-- TEMPLATE-TODO: the following text describe what the exercise from the JSONL is all about, including a short description of the target. For that, search on the internet for information -->
<p class="pb-2">
Mercado Libre's public API exposed user profiles without any form of authentication or rate limiting. This misconfiguration made it possible to conduct large-scale user enumeration attacks. Using only sequential user IDs, attackers could extract personal information such as usernames, account types, geographic locations, and profile permalinks. Alias Robotics, through its CAI (Cybersecurity AI) framework, created an automated testing methodology to explore this vulnerability and assess its potential impact.
</p>
<p class="pb-4">
The CAI-driven exercise included rapid API requests using concurrent threads to emulate an adversary collecting user data at scale. The exercise successfully retrieved detailed account metadata from hundreds of users, proving the viability of mass data harvesting. The resulting insights serve as a compelling demonstration of a bug bounty exercise.
</p>
</div>
<div class="col-12 col-lg-6 col-xl-6">
<img class="img-responsive d-none d-sm-block attack" src="img/white-hack-case.png">
<img class="img-responsive d-sm-none attack-m" src="img/white-hack-case.png">
</div>
<div class="col-xl-1 col-lg-1"></div>
<div class="col-12 col-xl-7 col-lg-6 col-texto"></div>
</div>
</div>
</div>
</div>
<!-- multimedia section -->
<div class="container-fluid supported2">
<div class="row">
<div class="container">
<div class="row py-5">
<div class="col-12 col-lg-9 col-xl-9 video">
<video controls style="width: 100%; height: auto; max-width: 100%;">
<source src="videos/mercadolibre.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<!-- TEMPLATE-TODO: provide a title and a short description of what happens in the JSONL file -->
<div class="col-12 col-lg-3 col-xl-3 bullet-video align-self-end">
<h5 class="" style="color:#1c1c1c; font-weight:700">
Demonstration of Mass Enumeration Against Mercado Libre's Public API
</h5>
<p style="color:grey" class="pb-4">
In this video, we leverage CAI (Cybersecurity AI) to automate the enumeration of Mercado Libre user accounts. The footage captures the real-time gathering of user data by issuing thousands of requests against the unauthenticated API. The lack of rate limiting or CAPTCHA enforcement is highlighted, showcasing how attackers could exploit this behavior to scrape user profiles for malicious purposes or competitive intelligence.
</p>
<a href="https://youtu.be/5JJ_X6dRv70" class="btn-flecha btn-readmore">
<span class="circle"><span class="icon arrow"></span></span><span class="button-text">Video</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row py-4">
<div class="col-12 col-lg-12 col-xl-12">
<div class="row">
<div class="container">
<div class="row">
<div class="col-12 col-sm-12 col-lg-12 col-xl-12 pt-5">
<h2 class="py-4"><a style="color:#63bfab">Cybersecurity AI (CAI)</a>, the <i>de facto</i> scaffolding for building AI security</h2>
<p class="pb-2">
CAI represents the first open-source framework specifically designed to democratize advanced security testing through specialized AI agents. By 2028, most cybersecurity actions will be autonomous, with humans teleoperating, making CAI's approach to AI-powered vulnerability discovery increasingly critical for organizational security.
The framework transcends theoretical benchmarks by enabling practical security outcomes. CAI achieved first place among AI teams and secured a top-20 position worldwide in the "AI vs Human" CTF live Challenge, earning a monetary reward and various other prizes and bounties ever since then. This performance demonstrates that AI-powered security testing can compete with and often exceed human capabilities in vulnerability discovery.
</p>
<a href="https://github.com/aliasrobotics/CAI" class="btn btn-relleno find-more">Explore CAI's source code <span>&#10095;</span></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- actors section -->
<div class="container-fluid py-4 supported-rpk">
<div class="row">
<div class="container">
<div class="row justify-content-center">
<div class="col-12">
<h2 class="py-4 " style="color:#333333; font-weight: bold;">Actors</h2>
<div class="separator"></div>
</div>
<!-- User -->
<div class="col-6 col-lg-2 col-xl-2">
<img class="icono-us-4" src="img/icono-us-1-black.svg">
<p style="color:#333333">
User: </br>
<a style="color:#63bfab">Researcher, hacker or you (yourself)</a>
</p>
</div>
<!-- Tool (CAI) -->
<div class="col-6 col-lg-2 col-xl-2">
<img class="icono-us-4" src="img/icono-us-3-black.svg">
<p style="color:#333333">
Tool:<br>
<a style="color:#63bfab">CAI</a>
<!-- <br>(<a style="color:#63bfab">C</a>ybersecurity <a style="color:#63bfab">AI</a>) -->
</p>
</div>
<!-- LLM Model -->
<div class="col-6 col-lg-2 col-xl-2">
<img class="icono-us-4" src="img/si-black.svg">
<p style="color:#333333">
LLM Model </br>
<!-- TEMPLATE-TODO: the following text should be adapted to the model used throughout the JSONL file -->
<a style="color:#63bfab; font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;">alias0</a>
</p>
</div>
<!-- Target -->
<div class="col-6 col-lg-2 col-xl-2">
<img class="icono-us-4" src="img/manufacturer-black.svg">
<p style="color:#333333">
Target:</br>
<!-- TEMPLATE-TODO: the following text should be adapted to the target of the JSONL file -->
<a style="color:#63bfab">Mercado Libre</a>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="container">
<div class="row py-5 pb-5 top">
<div class="col-12 col-lg-12 col-xl-12">
<!-- TEMPLATE-TODO: the following text should be adapted to the target of the JSONL file -->
<h2 class="py-2"> About Mercado Libre</h2>
<p>
Mercado Libre is Latin America's largest e-commerce and fintech ecosystem, operating in over 18 countries including Argentina, Brazil, Mexico, and Chile. Founded in 1999, the platform facilitates millions of daily transactions by providing a wide range of services including online retail, digital payments (via Mercado Pago), logistics solutions, and advertising. With over 100 million active users, Mercado Libre handles sensitive user data and financial transactions at massive scale.
</p>
<p>
Given its expansive user base and integration with banking infrastructure, Mercado Libre represents a high-value target for cybersecurity threats. As the platform continues to scale and innovate, ensuring robust API security and data privacy has become critical. This case study explores a vulnerability in their user-facing API that could expose personal information at scale if left unmitigated.
</p>
</div>
</div>
</div>
</div>
</div>
<div class="about-map">
<img src="img/top-map.jpg" alt="">
<div class="value-about">
<div class="container">
<div class="row">
<div class="col-xl-4 col-lg-12">
<div class="col-12 col-md-5 col-lg-5 col-xl-12 col-number1">
<h2>Time for the exercise</h2>
<h3>minutes</h3>
<div class="frame-style"></div>
<!-- TEMPLATE-TODO: the following text should be adapted to the time for the exercise of the JSONL file -->
<p class="angle-up"><strong><span class="counter">10</span></strong></p>
<br><br>
</div>
<div class="col-12 col-md-5 col-lg-5 col-xl-12 col-number2">
<h2>Cost</h2>
<h3>in EUR</h3>
<div class="frame-style"></div>
<!-- TEMPLATE-TODO: the following text should be adapted to the cost of the exercise of the JSONL file -->
<p><strong><span class="counter">2.38 €</span></strong></p>
</div>
</div>
<div class="col-xl-8 col-lg-12">
<div class="case-study-summary px-4 py-5">
<div class="row">
<div class="col-12 col-lg-6 mb-4">
<div class="summary-card p-4" style="background: rgba(255,255,255,0.9); border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); height: 100%;">
<h4 style="color: #254450; font-weight: bold; margin-bottom: 15px;">🎯 THE CHALLENGE</h4>
<!-- TEMPLATE-TODO: the following text should be adapted to the challenge of the JSONL file -->
<p style="color: #333; font-size: 1.1rem; line-height: 1.6;">
Mercado Libre's publicly exposed user API permitted unrestricted access to user profile data without requiring authentication tokens or anti-bot controls. This misconfiguration enabled attackers to enumerate user accounts using sequential IDs. During the test, Alias Robotics simulated a real-world adversary, collecting over 100 user records in under a minute. The absence of rate limiting, along with exposed metadata, highlighted the potential for large-scale user data harvesting and increased phishing campaign risk.
</p>
</div>
</div>
<div class="col-12 col-lg-6 mb-4">
<div class="summary-card p-4" style="background: rgba(255,255,255,0.9); border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); height: 100%;">
<h4 style="color: #254450; font-weight: bold; margin-bottom: 15px;">🛡️ THE SOLUTION</h4>
<!-- TEMPLATE-TODO: the following text should be adapted to the solution of the JSONL file -->
<p style="color: #333; font-size: 1.1rem; line-height: 1.6;">
To validate and demonstrate the risk, the CAI framework was deployed to script and run enumeration attacks in a controlled environment. By using multi-threaded concurrent requests and dynamic progress tracking, CAI provided concrete evidence of the vulnerability's scale and impact. Key metrics such as enumeration success rate, location-based user distributions, and response latencies were recorded. These results provided actionable intelligence for reporting the issue to Mercado Libre's security response team.
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-lg-6 mb-4">
<div class="summary-card p-4" style="background: rgba(255,255,255,0.9); border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); height: 100%;">
<h4 style="color: #254450; font-weight: bold; margin-bottom: 15px;">🔬 KEY ARTIFACTS</h4>
<!-- TEMPLATE-TODO: the following text should be adapted to the key artifacts of the JSONL file -->
<ul style="color: #333; font-size: 1.1rem; line-height: 1.6; margin: 0; padding-left: 20px;">
<li>Custom-built CAI automation script for user profile enumeration</li>
<li>Detailed analysis of user types and geographic dispersion</li>
<li>Rate-limiting probe tests showing absence of throttling or blocking</li>
</ul>
</div>
</div>
<div class="col-12 col-lg-6 mb-4">
<div class="summary-card p-4" style="background: rgba(255,255,255,0.9); border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); height: 100%;">
<h4 style="color: #254450; font-weight: bold; margin-bottom: 15px;">✅ RESULTS ACHIEVED</h4>
<!-- TEMPLATE-TODO: the following text should be adapted to the results achieved of the JSONL file -->
<ul style="color: #333; font-size: 1.1rem; line-height: 1.6; margin: 0; padding-left: 20px;">
<li>Exposed vulnerability in Mercado Libre's user API</li>
<li>Documented enumeration of >100 user profiles in less than 60 seconds</li>
<li>Gathered compelling evidence for bug bounty submission and coordinated disclosure</li>
</ul>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
<div class="key-benefits p-4" style="background: linear-gradient(135deg, #63bfab, #529d86); border-radius: 15px; color: white;">
<h4 style="color: white; font-weight: bold; margin-bottom: 20px;">KEY BENEFITS</h4>
<div class="row">
<div class="col-12 col-md-4 mb-2">
<span style="font-size: 1.2rem; font-weight: 600;">🔒 AI-powered Security</span>
</div>
<div class="col-12 col-md-4 mb-2">
<span style="font-size: 1.2rem; font-weight: 600;">⚡ Cost-effective and fast</span>
</div>
<div class="col-12 col-md-4 mb-2">
<span style="font-size: 1.2rem; font-weight: 600;">🤖 Robot Protection</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<img src="img/bottom-map.jpg" alt="">
</div>
<div class="container-fluid top" style="text-align:center">
<div class="row">
<div class="container">
<div class="row">
<div class="col-12 col-lg-12 col-xl-12 pb-5" style="margin-bottom:3em;">
<a href="https://github.com/aliasrobotics/CAI" class="btn btn-relleno find-more">Use CAI <span>&#10095;</span></a>
<!-- TEMPLATE: the following button should be shown only if alias0 is the model used -->
<a href="https://github.com/aliasrobotics/CAI" class="btn btn-linea find-more">Learn about <b style="color:#63bfab; font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;">alias0</b> <span>&#10095;</span></a>
</div>
</div>
</div>
</div>
</div>
<?php include("inc/footer.php"); ?>
</body>
... (complete PHP case study code) ...
</html>
Remember to only fill in the TEMPLATE TODO sections while preserving all other HTML and formatting from the template file.And create a new file
```

View File

@ -146,3 +146,11 @@ def generic_linux_command(command: str = "",
tool_name="generic_linux_command")
return result
@function_tool
def null_tool() -> str:
"""
This is a null tool that does nothing.
NEVER USE THIS TOOL
"""
return "Null tool"

View File

@ -0,0 +1,417 @@
#!/usr/bin/env python3
"""
Example:
CAI_MODEL="claude-sonnet-4-20250514" CAI_STREAM=True python3 case_study_generator.py --jsonl_file logs/cai_b97af8fc-3d51-45d3-8393-6c3341d33807_20250602_201144_luijait_darwin_24.5.0_81_38_189_27.jsonl --output_php_file alias_web/case_study_test.php
CAI Case Study Generator - Generate PHP case studies from JSONL files.
This script loads context from JSONL files using the same mechanism as CAI's /load command,
runs the UseCase agent with streaming output, and generates PHP case studies.
Usage:
python case_study_generator.py --jsonl_file logs/session.jsonl --output_php_file output.php
python case_study_generator.py --jsonl_file logs/last --output_php_file case_studies/latest.php
"""
import os
from dotenv import load_dotenv
load_dotenv()
import sys
import asyncio
import argparse
from pathlib import Path
import json
import re
from typing import List, Dict, Any, Optional
# Import CAI SDK components
from cai.sdk.agents import Runner
from cai.sdk.agents.models.openai_chatcompletions import message_history, add_to_message_history
from cai.sdk.agents.run_to_jsonl import load_history_from_jsonl
from cai.sdk.agents.stream_events import RunItemStreamEvent
from cai.sdk.agents.items import ToolCallOutputItem
# Import UseCase agent
from src.cai.agents.usecase import use_case_agent
# Rich console for better output
from rich.console import Console
from rich.panel import Panel
from rich.live import Live
from rich.text import Text
from rich.progress import Progress, SpinnerColumn, TextColumn
console = Console()
def extract_php_code(text: str) -> Optional[str]:
"""Extract PHP code from markdown code blocks."""
if not text:
return None
# Try to extract PHP code between ```php and ```
php_matches = re.findall(r'```php\n(.*?)```', text, re.DOTALL)
if php_matches:
return php_matches[0].strip()
# If no code blocks, check if the entire text looks like PHP
if text.strip().startswith('<?php') or text.strip().startswith('<!doctype'):
return text.strip()
return None
async def generate_case_study(jsonl_file: str, output_php_file: str) -> Optional[str]:
"""
Generate a PHP case study from a JSONL file using streaming output.
Args:
jsonl_file: Path to the JSONL file to load context from
output_php_file: Path to save the PHP output to
Returns:
Path to the saved PHP file or None if failed
"""
# Clear any existing messages in message_history to start fresh
message_history.clear()
# Load context from JSONL file (simulating /load command)
try:
console.print(f"[yellow]Loading JSONL file: {jsonl_file}[/yellow]")
messages = load_history_from_jsonl(jsonl_file)
if not messages:
console.print("[red]Error: No messages found in JSONL file[/red]")
return None
console.print(f"[green]✓ Loaded {len(messages)} messages from JSONL[/green]")
# Add messages to message_history (exactly like /load command does)
for message in messages:
message_history.append(message)
# Display loaded context summary
user_messages = sum(1 for msg in messages if msg.get("role") == "user")
assistant_messages = sum(1 for msg in messages if msg.get("role") == "assistant")
tool_messages = sum(1 for msg in messages if msg.get("role") == "tool")
console.print(Panel(
f"Context loaded:\n"
f"• User messages: {user_messages}\n"
f"• Assistant messages: {assistant_messages}\n"
f"• Tool messages: {tool_messages}",
title="[bold]JSONL Context Summary[/bold]",
border_style="blue"
))
except Exception as e:
console.print(f"[red]Error loading JSONL file: {str(e)}[/red]")
return None
# Analyze the loaded context to provide better guidance
context_summary = []
if messages:
# Find the main topic/challenge from user messages
for msg in messages:
if msg.get("role") == "user" and msg.get("content"):
content = msg.get("content", "")[:300] # First 300 chars
if content and len(content) > 20: # Skip very short messages
context_summary.append(content.strip())
if len(context_summary) >= 5: # Get first few meaningful messages
break
# Generate case study prompt with context
prompt = "Generate the PHP code for a cybersecurity case study based on the template. "
prompt += "Analyze the conversation context that has been loaded and create a comprehensive case study. "
prompt += "Fill in all TEMPLATE-TODO sections with relevant information from the session. "
prompt += "Explain step by step the problem and the solution in this escenario"
prompt += "The output should be complete PHP code ready to save to a file."
# Add a summary of the JSONL conversation to the prompt
if messages:
prompt += "\n\n## Conversation Context from JSONL:\n"
# Get key information from the conversation
user_msgs = [msg for msg in messages if msg.get("role") == "user"]
assistant_msgs = [msg for msg in messages if msg.get("role") == "assistant"]
tool_msgs = [msg for msg in messages if msg.get("role") == "tool"]
# Add user messages
if user_msgs:
prompt += "\n### User Messages:\n"
for i, msg in enumerate(user_msgs[:5], 1):
content = msg.get("content", "")[:500]
if content:
prompt += f"{i}. {content}\n"
# Add key assistant responses
if assistant_msgs:
prompt += "\n### Key Assistant Responses:\n"
for i, msg in enumerate(assistant_msgs[:3], 1):
content = msg.get("content", "")[:500]
if content and "I'll help" not in content: # Skip generic responses
prompt += f"{i}. {content}\n"
# Add tool outputs that might contain important data
if tool_msgs:
prompt += "\n### Tool Outputs (key findings):\n"
important_tools = []
for msg in tool_msgs:
content = msg.get("content", "")
# Look for important patterns in tool output
if any(keyword in content.lower() for keyword in ["map", "credential", "password", "auth", "endpoint", "192.168", "http"]):
important_tools.append(content[:500])
for i, content in enumerate(important_tools[:5], 1):
prompt += f"{i}. {content}\n"
console.print(f"\n[cyan]Generating case study with UseCase agent...[/cyan]")
# Configure streaming mode based on environment variable
stream_mode = os.getenv('CAI_STREAM', 'true').lower() != 'false'
try:
if stream_mode:
# Streaming mode - similar to CLI implementation
console.print("[dim]Using streaming mode...[/dim]")
# Track if we've seen any output
has_output = False
accumulated_text = []
php_code = None
# Run the streaming process like CLI does
async def process_streamed_response():
try:
result_stream = Runner.run_streamed(use_case_agent, prompt)
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
transient=True
) as progress:
task = progress.add_task("[cyan]Processing with UseCase agent...", total=None)
# Consume events so the async generator is executed
async for event in result_stream.stream_events():
if isinstance(event, RunItemStreamEvent):
# Handle tool outputs
if event.name == "tool_output" and isinstance(event.item, ToolCallOutputItem):
progress.update(task, description=f"[cyan]Tool: {event.item.raw_item.get('name', 'unknown')}...")
# Add tool message to history (like CLI does)
tool_msg = {
"role": "tool",
"tool_call_id": event.item.raw_item["call_id"],
"content": event.item.output,
}
add_to_message_history(tool_msg)
progress.update(task, description="[green]Finalizing output...")
# The result is available after streaming completes
# But we need to extract the output from message_history
# since streaming doesn't provide direct access to final output
# Get the last assistant message from message_history
for msg in reversed(message_history):
if msg.get("role") == "assistant" and msg.get("content"):
return msg.get("content")
return None
except Exception as e:
console.print(f"[red]Error in streaming: {str(e)}[/red]")
import traceback
console.print(f"[red]{traceback.format_exc()}[/red]")
return None
# Run the streaming process
final_output = await process_streamed_response()
if final_output:
php_code = extract_php_code(final_output)
if not php_code:
php_code = final_output
if php_code:
console.print(f"[green]✓ Generated {len(php_code)} characters of output[/green]")
else:
console.print("[red]Error: No output from UseCase agent[/red]")
return None
else:
# Non-streaming mode (simpler, like in examples)
console.print("[dim]Using non-streaming mode...[/dim]")
# Show progress
with console.status("[bold green]Generating case study...") as status:
# Instead of passing the conversation history directly,
# just use the prompt with all the context embedded in it
# This avoids issues with incomplete tool call/response pairs
# Run with just the prompt
result = await Runner.run(use_case_agent, prompt)
# Extract PHP code from result
if hasattr(result, 'final_output') and result.final_output:
output_text = result.final_output
# Process the output to handle tool outputs
for item in result.new_items:
if isinstance(item, ToolCallOutputItem):
# Add tool messages to history
tool_msg = {
"role": "tool",
"tool_call_id": item.raw_item["call_id"],
"content": item.output,
}
add_to_message_history(tool_msg)
php_code = extract_php_code(output_text)
if not php_code:
# If extraction failed, use the raw output
php_code = output_text
console.print(f"[green]✓ Generated {len(php_code)} characters of output[/green]")
else:
console.print("[red]Error: No output from UseCase agent[/red]")
return None
except Exception as e:
console.print(f"[red]Error generating case study: {str(e)}[/red]")
import traceback
console.print(f"[red]{traceback.format_exc()}[/red]")
return None
# Validate PHP code
if not php_code or len(php_code) < 100:
console.print("[red]Error: Generated output is too short or invalid[/red]")
return None
# Save PHP code to file
try:
output_path = Path(output_php_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(php_code)
console.print(f"\n[green]✓ PHP case study saved to: {output_php_file}[/green]")
# Display file size and preview
file_size = output_path.stat().st_size
console.print(f"[dim]File size: {file_size:,} bytes[/dim]")
# Show first few lines as preview
lines = php_code.split('\n')[:15]
preview = '\n'.join(lines)
if len(php_code.split('\n')) > 15:
preview += '\n...'
console.print(Panel(
preview,
title="[bold]PHP File Preview[/bold]",
border_style="blue"
))
return str(output_path)
except Exception as e:
console.print(f"[red]Error saving PHP file: {str(e)}[/red]")
return None
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description='Generate PHP case studies from JSONL files using CAI UseCase agent.',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate case study from a specific JSONL file
python case_study_generator.py --jsonl_file logs/session_20240102_123456.jsonl --output_php_file case_studies/ctf_writeup.php
# Use the last session log (default behavior like /load command)
python case_study_generator.py --jsonl_file logs/last --output_php_file case_studies/latest.php
# Generate with custom output directory
python case_study_generator.py --jsonl_file logs/last --output_php_file ~/Documents/case_studies/analysis.php
# Override the model
python case_study_generator.py --jsonl_file logs/last --output_php_file output.php --model gpt-4o
# Disable streaming
CAI_STREAM=false python case_study_generator.py --jsonl_file logs/last --output_php_file output.php
"""
)
parser.add_argument(
'--jsonl_file',
type=str,
default='logs/last',
help='Path to the JSONL file containing conversation context (default: logs/last)'
)
parser.add_argument(
'--output_php_file',
type=str,
required=True,
help='Path where the generated PHP file will be saved'
)
parser.add_argument(
'--model',
type=str,
default=None,
help='Override the model to use (e.g., claude-sonnet-4-20250514, gpt-4o)'
)
return parser.parse_args()
async def main():
"""Main entry point for the script."""
args = parse_args()
# Display banner
console.print(Panel(
"[bold cyan]CAI Case Study Generator[/bold cyan]\n"
"Generate professional cybersecurity case studies from JSONL session logs\n\n"
"[dim]This tool uses the CAI UseCase agent to analyze session context and generate\n"
"comprehensive PHP case studies based on the conversation history.[/dim]",
border_style="cyan"
))
# Override model if specified
if args.model:
os.environ["CAI_MODEL"] = args.model
console.print(f"[yellow]Using model override: {args.model}[/yellow]")
current_model = os.getenv("CAI_MODEL", "alias0")
console.print(f"[yellow]Model: {current_model}[/yellow]")
# Check if JSONL file exists
jsonl_path = Path(args.jsonl_file)
if not jsonl_path.exists() and args.jsonl_file != "logs/last":
console.print(f"[red]Error: JSONL file not found: {args.jsonl_file}[/red]")
return 1
# Generate the case study
result = await generate_case_study(args.jsonl_file, args.output_php_file)
if result:
console.print("\n[bold green]✨ Case study generation completed successfully![/bold green]")
console.print(f"[dim]You can now open {result} in your browser or editor[/dim]")
return 0
else:
console.print("\n[bold red]❌ Case study generation failed[/bold red]")
console.print("[dim]Please check the error messages above and ensure:[/dim]")
console.print("[dim]1. The JSONL file contains valid session data[/dim]")
console.print("[dim]2. The UseCase agent has access to the template file[/dim]")
console.print("[dim]3. Your API keys are properly configured[/dim]")
return 1
if __name__ == "__main__":
# Run the async main function
exit_code = asyncio.run(main())
sys.exit(exit_code)