I want to create a Statamic v6 addon called "Statamic AI Agent" (package name: reachweb/statamic-ai-agent) that provides an AI-powered terminal interface in the Control Panel for making site changes using natural language.
A Claude Code-like interface accessible via Cmd+K (or a toolbar button) that lets users type instructions like:
- "Rename the 'days' field to 'days_before' in the cruises blueprint and update all entries"
- "Translate all blog entries to French"
- "Add a 'featured' toggle field to all collection blueprints"
The AI agent should execute these operations, show its progress, and ask for confirmations when needed.
| Requirement | Version/Details |
|---|---|
| Statamic | v6 |
| Laravel | 12+ |
| PHP | 8.2+ |
| Styling | TailwindCSS v4 |
| Frontend | Vue 3 with Composition API |
| AI Backend | Multi-driver architecture (Claude default, extensible to OpenAI, Google, etc.) |
| Default Driver | Claude API (Anthropic) with tool use - default model: claude-sonnet-4-5 |
| Streaming | Server-Sent Events (SSE) |
| Testing | Pest v4 |
Create the addon with this structure:
statamic-ai-agent/
├── src/
│ ├── ServiceProvider.php
│ ├── UserConfig.php # User settings management (YAML-based)
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── AgentController.php # Handles chat requests via SSE
│ │ └── Middleware/
│ │ └── CheckBudgetLimit.php # Blocks requests if budget exceeded
│ ├── Agent/
│ │ ├── Agent.php # Main orchestrator - driver-agnostic
│ │ ├── Conversation.php # Manages conversation state/history
│ │ ├── ToolRegistry.php # Registers and manages available tools
│ │ ├── BudgetTracker.php # Tracks API spending against limits (driver-aware)
│ │ ├── Drivers/ # AI Driver implementations
│ │ │ ├── Contracts/
│ │ │ │ └── AiDriver.php # Interface for all AI drivers
│ │ │ ├── DriverFactory.php # Creates driver instances
│ │ │ ├── DriverResponse.php # Unified response DTO
│ │ │ └── Claude/
│ │ │ └── ClaudeDriver.php # Claude API implementation
│ │ └── Tools/ # Individual tool implementations
│ │ ├── Contracts/
│ │ │ └── AgentTool.php # Interface for all tools
│ │ ├── Blueprint/
│ │ │ ├── ListBlueprintsTool.php
│ │ │ ├── GetBlueprintTool.php
│ │ │ ├── UpdateBlueprintTool.php
│ │ │ └── RenameFieldTool.php
│ │ ├── Entry/
│ │ │ ├── ListEntriesTool.php
│ │ │ ├── GetEntryTool.php
│ │ │ ├── UpdateEntryTool.php
│ │ │ ├── BulkUpdateEntriesTool.php
│ │ │ └── TranslateEntryTool.php # For multisite translation
│ │ ├── Collection/
│ │ │ ├── ListCollectionsTool.php
│ │ │ └── UpdateCollectionTool.php
│ │ ├── View/
│ │ │ ├── FindViewsTool.php # Search Antlers/Blade for field usage
│ │ │ ├── UpdateViewTool.php
│ │ │ └── PreviewViewChangesTool.php
│ │ ├── Asset/
│ │ │ ├── ListAssetsTool.php
│ │ │ └── UpdateAssetTool.php
│ │ ├── Taxonomy/
│ │ │ ├── ListTaxonomiesTool.php
│ │ │ └── UpdateTermTool.php
│ │ ├── Global/
│ │ │ ├── ListGlobalsTool.php
│ │ │ └── UpdateGlobalTool.php
│ │ ├── Navigation/
│ │ │ └── ListNavsTool.php
│ │ └── Safety/
│ │ ├── GitCommitTool.php # Create safety commit before changes
│ │ ├── GitDiffTool.php # Show what changed
│ │ └── BackupTool.php
│ ├── Settings/
│ │ └── AgentSettings.php # Addon configuration
│ ├── Console/
│ │ └── Commands/
│ │ └── PruneUsageFilesCommand.php # Cleanup old usage JSON files
│ └── Facades/
│ └── Agent.php
├── resources/
│ ├── js/
│ │ ├── addon.js # Main entry point
│ │ └── components/
│ │ ├── AgentTerminal.vue # Main terminal/chat UI
│ │ ├── AgentMessage.vue # Individual message component
│ │ ├── ToolExecution.vue # Shows tool being executed
│ │ ├── ConfirmationPrompt.vue # Yes/No confirmations
│ │ ├── DiffPreview.vue # Show file diffs before applying
│ │ ├── BudgetIndicator.vue # Shows remaining budget
│ │ └── AgentToggle.vue # Toolbar button to open terminal
│ ├── css/
│ │ └── agent.css
│ └── views/
│ └── settings.blade.php # Addon settings page
├── routes/
│ └── cp.php
├── config/
│ └── statamic-ai-agent.php
├── storage/
│ └── ai-agent/ # Flat-file storage for usage tracking
│ ├── usage/
│ │ └── 2024-01-15.json # Daily usage files
│ └── config.json # Runtime config cache
├── lang/
│ └── en/
│ └── messages.php
├── composer.json
├── package.json
├── vite.config.js
└── README.md
The addon MUST have two operation modes configurable in settings:
| Allowed | Not Allowed |
|---|---|
| Modify entry content/data | Modify blueprints |
| Translate entries | Modify collection/taxonomy settings |
| Bulk update field values in entries | Modify views/templates |
| Modify navigation structures |
Everything in Content Only, plus:
- Can modify blueprints (add/remove/rename fields)
- Can modify collection and taxonomy settings
- Can search and modify Antlers/Blade views
- Can modify navigation structures
- Can modify global sets structure
Implementation Note: Store this setting in the config and check it before executing any tool. The Agent should be aware of its current mode and tell the user if they ask for something outside its permissions.
Implement a spending limit system to control API costs:
// config/statamic-ai-agent.php
return [
// Default AI driver to use
'default_driver' => env('AI_AGENT_DRIVER', 'claude'),
// AI Driver configurations with per-model pricing
'drivers' => [
'claude' => [
'api_key' => env('ANTHROPIC_API_KEY'),
'default_model' => 'claude-sonnet-4-5',
'models' => [
'claude-haiku-4-5' => ['input' => 1.00, 'output' => 5.00],
'claude-sonnet-4-5' => ['input' => 3.00, 'output' => 15.00],
'claude-opus-4-5' => ['input' => 15.00, 'output' => 75.00],
],
],
// Future drivers (OpenAI, Google, etc.) can be added here
],
// Budget limits (in USD)
'budget' => [
// Maximum spend per day in USD
'daily_limit' => env('AI_AGENT_DAILY_LIMIT', 10.00),
// Maximum spend per month in USD (0 = unlimited)
'monthly_limit' => env('AI_AGENT_MONTHLY_LIMIT', 100.00),
// Maximum spend per single conversation/session (0 = unlimited)
'per_session_limit' => env('AI_AGENT_SESSION_LIMIT', 5.00),
// Warning threshold (percentage) - warn user when they hit this
'warning_threshold' => env('AI_AGENT_WARNING_THRESHOLD', 80),
// Action when limit reached: 'block' or 'warn'
'limit_action' => env('AI_AGENT_LIMIT_ACTION', 'block'),
],
];Create src/Agent/BudgetTracker.php that uses JSON files instead of a database:
-
Storage Structure:
storage/statamic-ai-agent/ ├── usage/ │ ├── 2024-01-15.json # One file per day │ ├── 2024-01-16.json │ └── ... └── monthly/ ├── 2024-01.json # Monthly aggregates (optional, for performance) └── ... -
Daily Usage File Format (
2024-01-15.json):{ "date": "2024-01-15", "total_cost": 2.45, "total_input_tokens": 150000, "total_output_tokens": 45000, "requests": [ { "id": "req_abc123", "timestamp": "2024-01-15T10:30:00Z", "user_id": "1", "session_id": "sess_xyz", "input_tokens": 1500, "output_tokens": 800, "cost": 0.0165, "model": "claude-sonnet-4-5-20250514", "tools_used": ["list_collections", "get_blueprint"] } ] } -
Calculates costs based on driver pricing configuration:
public function calculateCost(int $inputTokens, int $outputTokens, string $model, string $driver = 'claude'): float { // Pricing is now loaded from config per-driver $driverConfig = config("statamic-ai-agent.drivers.{$driver}", []); $models = $driverConfig['models'] ?? []; $rates = $models[$model] ?? [ 'input' => 3.00, // Default: $3 per 1M input tokens 'output' => 15.00, // Default: $15 per 1M output tokens ]; return (($inputTokens / 1_000_000) * $rates['input']) + (($outputTokens / 1_000_000) * $rates['output']); }
-
File-based storage methods:
class BudgetTracker { protected string $storagePath; public function __construct() { $this->storagePath = storage_path('statamic-ai-agent'); $this->ensureDirectoriesExist(); } protected function ensureDirectoriesExist(): void { $directories = [ $this->storagePath, $this->storagePath . '/usage', $this->storagePath . '/monthly', ]; foreach ($directories as $dir) { if (!is_dir($dir)) { mkdir($dir, 0755, true); } } } protected function getDailyFilePath(?string $date = null): string { $date = $date ?? now()->format('Y-m-d'); return $this->storagePath . "/usage/{$date}.json"; } protected function getDailyData(?string $date = null): array { $path = $this->getDailyFilePath($date); if (!file_exists($path)) { return [ 'date' => $date ?? now()->format('Y-m-d'), 'total_cost' => 0, 'total_input_tokens' => 0, 'total_output_tokens' => 0, 'requests' => [], ]; } return json_decode(file_get_contents($path), true); } protected function saveDailyData(array $data): void { $path = $this->getDailyFilePath($data['date']); file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT)); } public function recordUsage( string $userId, string $sessionId, int $inputTokens, int $outputTokens, string $model, string $driver = 'claude', array $metadata = [] ): void { $cost = $this->calculateCost($inputTokens, $outputTokens, $model, $driver); $data = $this->getDailyData(); $data['requests'][] = [ 'id' => 'req_' . uniqid(), 'timestamp' => now()->toISOString(), 'user_id' => $userId, 'session_id' => $sessionId, 'input_tokens' => $inputTokens, 'output_tokens' => $outputTokens, 'cost' => $cost, 'model' => $model, 'driver' => $driver, 'tools_used' => $metadata['tools_used'] ?? [], ]; $data['total_cost'] += $cost; $data['total_input_tokens'] += $inputTokens; $data['total_output_tokens'] += $outputTokens; $this->saveDailyData($data); } public function getDailyUsage(): float { return $this->getDailyData()['total_cost']; } public function getMonthlyUsage(): float { $total = 0; $month = now()->format('Y-m'); $pattern = $this->storagePath . "/usage/{$month}-*.json"; foreach (glob($pattern) as $file) { $data = json_decode(file_get_contents($file), true); $total += $data['total_cost'] ?? 0; } return $total; } public function getSessionUsage(string $sessionId): float { $data = $this->getDailyData(); $total = 0; foreach ($data['requests'] as $request) { if ($request['session_id'] === $sessionId) { $total += $request['cost']; } } return $total; } public function canMakeRequest(): bool { $dailyLimit = config('statamic-ai-agent.budget.daily_limit'); if ($dailyLimit <= 0) { return true; // Unlimited } return $this->getDailyUsage() < $dailyLimit; } public function getRemainingDailyBudget(): float { $limit = config('statamic-ai-agent.budget.daily_limit'); return max(0, $limit - $this->getDailyUsage()); } public function getUsagePercentage(): float { $limit = config('statamic-ai-agent.budget.daily_limit'); if ($limit <= 0) { return 0; } return min(100, ($this->getDailyUsage() / $limit) * 100); } public function isApproachingLimit(): bool { $threshold = config('statamic-ai-agent.budget.warning_threshold', 80); return $this->getUsagePercentage() >= $threshold; } /** * Clean up old usage files (optional maintenance) */ public function pruneOldUsageFiles(int $keepDays = 90): int { $cutoff = now()->subDays($keepDays)->format('Y-m-d'); $deleted = 0; foreach (glob($this->storagePath . '/usage/*.json') as $file) { $date = basename($file, '.json'); if ($date < $cutoff) { unlink($file); $deleted++; } } return $deleted; } }
-
Provides checking methods:
public function canMakeRequest(): bool; public function getDailyUsage(): float; public function getMonthlyUsage(): float; public function getSessionUsage(string $sessionId): float; public function getRemainingDailyBudget(): float; public function getRemainingMonthlyBudget(): float; public function getUsagePercentage(): float; public function isApproachingLimit(): bool; // True if > warning_threshold
Create a command to prune old usage files:
// src/Console/Commands/PruneUsageFilesCommand.php
<?php
namespace MyVendor\StatamicAiAgent\Console\Commands;
use Illuminate\Console\Command;
use MyVendor\StatamicAiAgent\Agent\BudgetTracker;
class PruneUsageFilesCommand extends Command
{
protected $signature = 'ai-agent:prune-usage {--days=90 : Number of days to keep}';
protected $description = 'Remove old AI Agent usage tracking files';
public function handle(BudgetTracker $tracker)
{
$days = (int) $this->option('days');
$deleted = $tracker->pruneOldUsageFiles($days);
$this->info("Deleted {$deleted} old usage file(s).");
return Command::SUCCESS;
}
}Register in ServiceProvider:
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
Commands\PruneUsageFilesCommand::class,
]);
}
}Create BudgetIndicator.vue that shows:
- Daily spending:
$X.XX / $10.00 - Visual progress bar (green → yellow → red)
- Warning message when approaching limit
- "Budget exceeded" overlay when limit reached
In the Agent.php class:
public function run(string $message, Conversation $conversation): Generator
{
$budgetTracker = app(BudgetTracker::class);
// Check budget before making request
if (!$budgetTracker->canMakeRequest()) {
yield [
'type' => 'error',
'code' => 'budget_exceeded',
'message' => 'Daily API budget has been exceeded. Resets at midnight UTC.',
'usage' => [
'daily' => $budgetTracker->getDailyUsage(),
'limit' => config('statamic-ai-agent.budget.daily_limit'),
]
];
return;
}
// Warn if approaching limit
if ($budgetTracker->isApproachingLimit()) {
yield [
'type' => 'warning',
'code' => 'budget_warning',
'message' => sprintf(
'Warning: You have used %s%% of your daily budget ($%.2f remaining)',
$budgetTracker->getUsagePercentage(),
$budgetTracker->getRemainingDailyBudget()
),
];
}
// Make API call...
$response = $this->claude->messages()->create([...]);
// Record usage from response
$budgetTracker->recordUsage(
userId: auth()->id(),
sessionId: $conversation->getId(),
inputTokens: $response->usage->input_tokens,
outputTokens: $response->usage->output_tokens,
model: $this->model,
metadata: ['tools_used' => $this->extractToolNames($response)]
);
// ... rest of the loop
}Add to the settings page:
<div class="budget-settings">
<h2>Budget & Spending Limits</h2>
<div class="form-group">
<label>Daily Limit (USD)</label>
<input type="number" step="0.01" min="0" name="daily_limit" value="{{ $daily_limit }}">
<p class="help-text">Maximum amount to spend per day. Set to 0 for unlimited.</p>
</div>
<div class="form-group">
<label>Monthly Limit (USD)</label>
<input type="number" step="0.01" min="0" name="monthly_limit" value="{{ $monthly_limit }}">
</div>
<div class="form-group">
<label>Per-Session Limit (USD)</label>
<input type="number" step="0.01" min="0" name="per_session_limit" value="{{ $per_session_limit }}">
<p class="help-text">Maximum spend for a single conversation session.</p>
</div>
<div class="form-group">
<label>Warning Threshold (%)</label>
<input type="number" min="0" max="100" name="warning_threshold" value="{{ $warning_threshold }}">
<p class="help-text">Show warning when usage exceeds this percentage of daily limit.</p>
</div>
<div class="current-usage">
<h3>Current Usage</h3>
<p>Today: ${{ number_format($daily_usage, 2) }} / ${{ number_format($daily_limit, 2) }}</p>
<p>This Month: ${{ number_format($monthly_usage, 2) }} / ${{ number_format($monthly_limit, 2) }}</p>
</div>
</div>When Statamic multisite is enabled, the agent should be able to:
- Detect available sites/locales
- Translate entry content from one locale to another
- Support bulk translation (e.g., "Translate all blog posts to French and German")
- Use Claude itself for translation (no external translation API needed)
The TranslateEntryTool should:
- Check if multisite is enabled
- Get the source entry content
- Ask Claude to translate (can be done in a nested call or separate tool)
- Save to the target site's entry file
class TranslateEntryTool implements AgentTool
{
public function name(): string
{
return 'translate_entry';
}
public function description(): string
{
return 'Translates an entry from one locale to another. Uses AI to translate the content while preserving structure and formatting.';
}
public function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'collection' => [
'type' => 'string',
'description' => 'The collection handle'
],
'entry_id' => [
'type' => 'string',
'description' => 'The entry ID or slug'
],
'source_site' => [
'type' => 'string',
'description' => 'Source site handle (e.g., "default", "english")'
],
'target_site' => [
'type' => 'string',
'description' => 'Target site handle (e.g., "french", "german")'
],
'fields_to_translate' => [
'type' => 'array',
'items' => ['type' => 'string'],
'description' => 'Specific fields to translate. If empty, translates all text fields.'
]
],
'required' => ['collection', 'entry_id', 'target_site']
];
}
}Create a comprehensive system prompt that explains:
-
Statamic's Architecture:
- Collections, blueprints, entries
- Taxonomies and terms
- Globals and global sets
- Navigation structures
- Assets and asset containers
- The flat-file structure and how content is stored in YAML/Markdown
-
Available Tools and When to Use Them
-
Behavioral Instructions:
- Always explain what it's about to do before doing it
- Ask for confirmation before destructive/bulk operations
- Create a git commit before making changes (if git is available)
- After renaming fields in blueprints, proactively ask about updating views
- When translating, preserve the structure and only translate text content
- Be aware of its permission mode and explain limitations
- Handle errors gracefully and suggest fixes
- Be mindful of the budget and warn about expensive operations
-
Budget Awareness:
- Inform user about remaining budget when relevant
- Suggest more efficient approaches for expensive operations
- Warn before bulk operations that might consume significant budget
The AgentTerminal.vue component should:
- Open via
Cmd+K(Mac) /Ctrl+K(Windows) keyboard shortcut - Also be accessible via a button in the CP toolbar
- Have a dark theme terminal-like aesthetic (similar to Claude Code)
- Support markdown rendering in responses
- Show typing/thinking indicators during streaming
- Display tool executions in a collapsible format showing:
- Tool name
- Input parameters
- Output/result
- Time taken
- Handle confirmation prompts inline with Yes/No buttons
- Show diffs in a readable format before applying file changes
- Maintain conversation history within the session
- Have a "Stop" button to cancel ongoing operations
- Support clearing conversation with a command or button
- Show budget indicator in the header/footer
<template>
<Teleport to="body">
<Transition name="slide-up">
<div v-if="isOpen" class="ai-agent-terminal">
<!-- Header with budget indicator -->
<div class="terminal-header">
<h3>AI Agent</h3>
<BudgetIndicator
:daily-usage="budget.daily"
:daily-limit="budget.dailyLimit"
:warning="budget.isWarning"
/>
<button @click="close" class="close-btn">×</button>
</div>
<!-- Messages area -->
<div class="terminal-messages" ref="messagesContainer">
<AgentMessage
v-for="msg in messages"
:key="msg.id"
:message="msg"
@confirm="handleConfirmation"
/>
<div v-if="isThinking" class="thinking-indicator">
<span class="pulse">●</span> Thinking...
</div>
</div>
<!-- Input area -->
<div class="terminal-input">
<input
v-model="input"
@keydown.enter="send"
@keydown.escape="close"
placeholder="Ask the AI to make changes..."
:disabled="isThinking || budgetExceeded"
ref="inputField"
/>
<button
v-if="isThinking"
@click="stop"
class="stop-btn"
>
Stop
</button>
<button
v-else
@click="send"
:disabled="!input.trim() || budgetExceeded"
class="send-btn"
>
Send
</button>
</div>
<!-- Budget exceeded overlay -->
<div v-if="budgetExceeded" class="budget-exceeded-overlay">
<p>Daily budget exceeded</p>
<p>Resets at midnight UTC</p>
</div>
</div>
</Transition>
</Teleport>
</template>Create a settings page at /cp/statamic-ai-agent/settings with:
| Setting | Type | Description |
|---|---|---|
| AI Driver | Select | Choose AI provider (Claude, OpenAI, etc.) |
| Model | Select | Model selection (dynamic per driver) |
| Permission Mode | Toggle | Content Only / Full Access |
| Git Integration | Toggle | Auto-commit before changes |
| Default Confirmation | Select | Always ask / Only destructive / Never |
| Keyboard Shortcut | Input | Customize the trigger shortcut |
| Daily Limit | Number | Max USD per day |
| Monthly Limit | Number | Max USD per month |
| Per-Session Limit | Number | Max USD per conversation |
| Warning Threshold | Number | Percentage to trigger warning |
Note: API keys are configured via environment variables in .env, not in the UI for security.
The AgentController should stream responses using SSE:
public function chat(Request $request)
{
// Check budget via middleware or here
$budgetTracker = app(BudgetTracker::class);
if (!$budgetTracker->canMakeRequest()) {
return response()->json([
'error' => 'budget_exceeded',
'message' => 'Daily API budget exceeded',
'usage' => $budgetTracker->getDailyUsage(),
'limit' => config('statamic-ai-agent.budget.daily_limit'),
], 429);
}
return response()->stream(function () use ($request, $budgetTracker) {
$agent = new Agent($budgetTracker);
foreach ($agent->run($request->input('message'), $request->session()) as $event) {
echo "data: " . json_encode($event) . "\n\n";
ob_flush();
flush();
}
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no',
]);
}| Requirement | Implementation |
|---|---|
| Git Integration | Before any write operation, offer to create a git commit |
| Dry Run | For bulk operations, first show what WOULD change |
| Confirmation Flow | Agent yields a 'confirm' event and waits for user response |
| Rate Limiting | Prevent accidental loops or runaway operations |
| Budget Limits | Block requests when spending limits are reached |
| Audit Log | Log all operations to storage/logs/ai-agent.log |
Here's how a tool should be structured:
<?php
namespace MyVendor\StatamicAiAgent\Agent\Tools\Blueprint;
use MyVendor\StatamicAiAgent\Agent\Tools\Contracts\AgentTool;
use Statamic\Facades\Blueprint;
class RenameFieldTool implements AgentTool
{
public function name(): string
{
return 'rename_blueprint_field';
}
public function description(): string
{
return 'Renames a field in a blueprint. This will update the blueprint YAML file. Note: This does NOT automatically update entries or views - use separate tools for that.';
}
public function requiredMode(): string
{
return 'full'; // 'content' or 'full'
}
public function requiresConfirmation(): bool
{
return true;
}
public function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'blueprint_handle' => [
'type' => 'string',
'description' => 'The handle of the blueprint (e.g., "article" for a collection blueprint)'
],
'blueprint_namespace' => [
'type' => 'string',
'description' => 'The namespace - usually "collections.{collection_handle}" or "taxonomies.{taxonomy_handle}"'
],
'old_field_handle' => [
'type' => 'string',
'description' => 'The current field handle to rename'
],
'new_field_handle' => [
'type' => 'string',
'description' => 'The new field handle'
],
],
'required' => ['blueprint_handle', 'blueprint_namespace', 'old_field_handle', 'new_field_handle']
];
}
public function execute(array $input): array
{
$blueprint = Blueprint::find($input['blueprint_namespace'] . '.' . $input['blueprint_handle']);
if (!$blueprint) {
return ['success' => false, 'error' => 'Blueprint not found'];
}
// Implementation here...
return [
'success' => true,
'message' => "Renamed field '{$input['old_field_handle']}' to '{$input['new_field_handle']}' in blueprint '{$input['blueprint_handle']}'",
'next_steps' => [
'You should now update all entries that use this field',
'You may also want to update any views that reference this field'
]
];
}
}Look at https://github.com/cboxdk/statamic-mcp for reference on how to interact with Statamic's APIs for various operations. You can borrow patterns from there but implement them as tools for the Claude agent.
- Scaffold the addon structure with all the files
- Implement the ServiceProvider with proper registration
- Create the BudgetTracker class with JSON file storage and all budget logic
- Create the Agent class with the Claude API loop and budget integration
- Implement 4-5 core tools to prove the concept:
ListCollectionsToolListBlueprintsToolGetBlueprintToolUpdateEntryToolTranslateEntryTool(if multisite)
- Create the Vue terminal component with basic chat and budget indicator
- Add SSE streaming
- Create the settings page with all configuration options
- Expand with more tools and polish the UI
- Use Statamic v6 APIs and patterns (check their upgrade guide if needed)
- Use Vue 3 Composition API with
<script setup>syntax - Use Statamic's built-in permission system for CP access control
- Store the Claude API key securely using Laravel's encryption
- The conversation state should be stored in the session, not persisted
- Usage/budget data is stored as JSON files in
storage/statamic-ai-agent/(no database required!) - Make sure the terminal works well on both desktop and tablet CP views
- All monetary values should be stored and calculated in USD
- Consider timezone handling for daily resets (use UTC as default)
- Add a scheduled command or manual button to prune old usage files (keep last 90 days)
Add these to the .env.example:
# Statamic AI Agent Configuration
AI_AGENT_DRIVER=claude # AI driver: claude, openai, google (when implemented)
AI_AGENT_MODE=content # 'content' or 'full'
# Claude API (default driver)
ANTHROPIC_API_KEY=
# Future drivers (add when implementing)
# OPENAI_API_KEY=
# GOOGLE_AI_API_KEY=
# Budget Limits (in USD)
AI_AGENT_DAILY_LIMIT=10.00
AI_AGENT_MONTHLY_LIMIT=100.00
AI_AGENT_SESSION_LIMIT=5.00
AI_AGENT_WARNING_THRESHOLD=80
AI_AGENT_LIMIT_ACTION=block # 'block' or 'warn'Please start by creating the complete addon structure with all files, then implement the core functionality. Focus on getting a working proof-of-concept with:
- The budget tracking system using JSON flat-file storage (no database!)
- 4-5 working tools
- The Vue terminal with budget indicator
- SSE streaming
Remember: Statamic is a flat-file CMS, so this addon should also be flat-file friendly. All usage tracking is stored in storage/statamic-ai-agent/ as JSON files.
Then we can expand from there.