2026-01-18 21:00:09 +00:00
|
|
|
|
APP_NAME="TryPost"
|
2026-01-15 01:13:44 +00:00
|
|
|
|
APP_ENV=local
|
|
|
|
|
|
APP_KEY=
|
|
|
|
|
|
APP_DEBUG=true
|
|
|
|
|
|
APP_URL=http://localhost
|
|
|
|
|
|
|
2026-06-14 12:31:00 +00:00
|
|
|
|
# Public base URL inbound webhooks (e.g. Telegram) are registered on.
|
|
|
|
|
|
# Defaults to APP_URL; set a tunnel URL (e.g. ngrok) for local development.
|
|
|
|
|
|
WEBHOOK_URL=
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Self-hosted mode (skips payment requirements)
|
|
|
|
|
|
SELF_HOSTED=true
|
|
|
|
|
|
|
MCP: workspace settings, viewer read access, and token access (#241)
* Add workspace MCP settings and token access controls.
Ship MCP settings UI, OAuth revoke/list helpers, Passport deploy wiring,
and workspace.token:mcp gating so assistants can connect without pulling
in welcome/onboarding from the parent epic.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Type MCP client config shapes instead of string checks.
Encode http/config-root on each advanced client and tighten primary
client ids so snippet generation does not branch on magic strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Polish MCP settings follow-ups from review.
Translate Ukrainian MCP copy, deep-link ChatGPT into connector
creation, drop an unused asset and revoke arg, and assert PATs are
rejected on the MCP endpoint.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden MCP connected clients, revoke scope, and OAuth consent.
List recoverable sessions with live refresh tokens, revoke only PATs,
throttle registration alone, and block viewers from authorizing MCP.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify MCP OAuth route throttling to a single middleware group.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Allow workspace viewers read-only MCP access with web policy writes.
Mirror the web app: MCP connects on view + OAuth mcp:use, write tools
enforce createPost/update/delete/manageAccounts/manageTeam, and demotion
to Viewer keeps grants. Cover role denials, consent, and disconnect.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden MCP tool authz with shared workspace helpers.
Route ApiKey tools through AuthorizesMcpTool, fail closed on null user
or policy argument, and resolve the current workspace before mutating.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop redundant string casts on validated request data.
Enum::from and validated() fields are already strings, so the casts
add noise without changing behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Show only the current user's MCP connections in settings.
Match API keys privacy: list and disconnect your own OAuth clients,
not teammates' across the account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Cover LoadWorkspaceFromToken gaps and harden AuthorizesMcpTool tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop redundant is_string guard before UpdatePostTool find.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor AppSidebar to always show MCP link and simplify route middleware definition in ai.php. The MCP link is now consistently displayed regardless of the current workspace state, and the route middleware syntax has been streamlined.
* Refresh MCP connected clients with Inertia usePoll.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Bump laravel/mcp to 0.9.1 and add the TryPost server icon.
Requires laravel/boost 2.5 for the Icon attribute; expose images/trypost/icon.png on TryPostServer.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop no-op ReflectionClass import in TryPostServerTest.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:54:51 +00:00
|
|
|
|
# Passport OAuth keys (API tokens / MCP). Prefer env vars over key files so
|
|
|
|
|
|
# every node behind a load balancer shares the same key pair. Use literal \n
|
|
|
|
|
|
# for newlines in the PEM. When unset, Passport falls back to storage/oauth-*.key
|
|
|
|
|
|
# (generate with: php artisan passport:keys).
|
|
|
|
|
|
# PASSPORT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
|
|
|
|
# PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
|
|
|
|
|
fix: Stripe webhook setup completion, notifications routing, billing UI
- Fix StripeEventListener: mark setup as Completed when subscription.created webhook fires
- Fix notifications routes: move outside subscribed middleware (non-critical feature)
- Fix NotificationBell: guard response.ok before parsing JSON
- Fix StripeEventListener: use data_get() for payload access
- Rewrite billing page to use SettingsLayout with download buttons on invoices
- Remove duplicate Unit/Listeners/StripeEventListenerTest (consolidated into Feature)
- Add comprehensive Stripe webhook tests (14 scenarios: happy path, setup transitions, edge cases, error handling)
- Update billing i18n: remove "no credit card" text, add status key
- Move auth layout logo to top-left corner
- Redesign onboarding Role page with compact list layout
- Add persona i18n translations (EN, PT-BR, ES)
- Update README with API, MCP, tech stack, quick start
2026-03-31 16:52:36 +00:00
|
|
|
|
TELESCOPE_ENABLED=false
|
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
|
APP_LOCALE=en
|
|
|
|
|
|
APP_FALLBACK_LOCALE=en
|
|
|
|
|
|
APP_FAKER_LOCALE=en_US
|
|
|
|
|
|
|
|
|
|
|
|
APP_MAINTENANCE_DRIVER=file
|
|
|
|
|
|
|
|
|
|
|
|
BCRYPT_ROUNDS=12
|
|
|
|
|
|
|
|
|
|
|
|
LOG_CHANNEL=stack
|
|
|
|
|
|
LOG_STACK=single
|
|
|
|
|
|
LOG_DEPRECATIONS_CHANNEL=null
|
|
|
|
|
|
LOG_LEVEL=debug
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Database (PostgreSQL or MySQL)
|
|
|
|
|
|
# PostgreSQL: DB_CONNECTION=pgsql, DB_PORT=5432, DB_USERNAME=postgres
|
|
|
|
|
|
# MySQL: DB_CONNECTION=mysql, DB_PORT=3306, DB_USERNAME=root
|
|
|
|
|
|
DB_CONNECTION=pgsql
|
|
|
|
|
|
DB_HOST=127.0.0.1
|
|
|
|
|
|
DB_PORT=5432
|
|
|
|
|
|
DB_DATABASE=trypost
|
|
|
|
|
|
DB_USERNAME=postgres
|
|
|
|
|
|
DB_PASSWORD=
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Session
|
2026-01-15 01:13:44 +00:00
|
|
|
|
SESSION_DRIVER=database
|
2026-01-18 21:00:09 +00:00
|
|
|
|
SESSION_LIFETIME=1440
|
2026-01-15 01:13:44 +00:00
|
|
|
|
SESSION_ENCRYPT=false
|
|
|
|
|
|
SESSION_PATH=/
|
|
|
|
|
|
SESSION_DOMAIN=null
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Broadcasting, Queue, Cache
|
|
|
|
|
|
BROADCAST_CONNECTION=reverb
|
|
|
|
|
|
QUEUE_CONNECTION=redis
|
|
|
|
|
|
CACHE_STORE=redis
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# File Storage
|
|
|
|
|
|
# Options: local, s3, r2 (or any S3-compatible: MinIO, DigitalOcean Spaces, etc.)
|
|
|
|
|
|
FILESYSTEM_DISK=local
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Redis
|
2026-01-15 01:13:44 +00:00
|
|
|
|
REDIS_HOST=127.0.0.1
|
|
|
|
|
|
REDIS_PASSWORD=null
|
|
|
|
|
|
REDIS_PORT=6379
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Mail
|
|
|
|
|
|
MAIL_MAILER=smtp
|
2026-01-15 01:13:44 +00:00
|
|
|
|
MAIL_HOST=127.0.0.1
|
|
|
|
|
|
MAIL_PORT=2525
|
|
|
|
|
|
MAIL_USERNAME=null
|
|
|
|
|
|
MAIL_PASSWORD=null
|
2026-01-18 21:00:09 +00:00
|
|
|
|
MAIL_ENCRYPTION=null
|
2026-01-15 01:13:44 +00:00
|
|
|
|
MAIL_FROM_ADDRESS="hello@example.com"
|
|
|
|
|
|
MAIL_FROM_NAME="${APP_NAME}"
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Reverb (WebSockets)
|
|
|
|
|
|
REVERB_APP_ID=1001
|
|
|
|
|
|
REVERB_APP_KEY=your-reverb-key
|
|
|
|
|
|
REVERB_APP_SECRET=your-reverb-secret
|
|
|
|
|
|
REVERB_HOST="localhost"
|
|
|
|
|
|
REVERB_PORT=8080
|
|
|
|
|
|
REVERB_SCHEME=http
|
|
|
|
|
|
|
|
|
|
|
|
# AWS S3 (set FILESYSTEM_DISK=s3)
|
2026-01-15 01:13:44 +00:00
|
|
|
|
AWS_ACCESS_KEY_ID=
|
|
|
|
|
|
AWS_SECRET_ACCESS_KEY=
|
|
|
|
|
|
AWS_DEFAULT_REGION=us-east-1
|
|
|
|
|
|
AWS_BUCKET=
|
2026-01-18 21:00:09 +00:00
|
|
|
|
AWS_URL=
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Cloudflare R2 (set FILESYSTEM_DISK=r2)
|
|
|
|
|
|
R2_ACCESS_KEY_ID=
|
|
|
|
|
|
R2_SECRET_ACCESS_KEY=
|
|
|
|
|
|
R2_ENDPOINT=
|
|
|
|
|
|
R2_REGION=auto
|
|
|
|
|
|
R2_BUCKET=
|
|
|
|
|
|
R2_URL=
|
|
|
|
|
|
|
2026-05-19 15:38:29 +00:00
|
|
|
|
# DigitalOcean Spaces (set FILESYSTEM_DISK=spaces)
|
|
|
|
|
|
SPACES_ACCESS_KEY_ID=
|
|
|
|
|
|
SPACES_SECRET_ACCESS_KEY=
|
|
|
|
|
|
SPACES_ENDPOINT=
|
|
|
|
|
|
SPACES_REGION=
|
|
|
|
|
|
SPACES_BUCKET=
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# ============================================
|
|
|
|
|
|
# Social Platform Credentials
|
|
|
|
|
|
# ============================================
|
|
|
|
|
|
# Get your API keys from each platform's developer portal
|
|
|
|
|
|
|
|
|
|
|
|
# LinkedIn (https://developer.linkedin.com)
|
|
|
|
|
|
LINKEDIN_CLIENT_ID=
|
|
|
|
|
|
LINKEDIN_CLIENT_SECRET=
|
|
|
|
|
|
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
|
2026-06-10 18:02:00 +00:00
|
|
|
|
# LINKEDIN_SCOPES="openid,profile,email,w_member_social"
|
|
|
|
|
|
# LINKEDIN_PAGE_SCOPES="openid,profile,email,w_organization_social,r_organization_social,rw_organization_admin,w_member_social"
|
2026-01-17 02:46:30 +00:00
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# X / Twitter (https://developer.twitter.com)
|
|
|
|
|
|
X_CLIENT_ID=
|
|
|
|
|
|
X_CLIENT_SECRET=
|
|
|
|
|
|
X_CLIENT_REDIRECT="${APP_URL}/accounts/x/callback"
|
|
|
|
|
|
|
|
|
|
|
|
# TikTok (https://developers.tiktok.com)
|
|
|
|
|
|
TIKTOK_CLIENT_ID=
|
|
|
|
|
|
TIKTOK_CLIENT_SECRET=
|
|
|
|
|
|
TIKTOK_CLIENT_REDIRECT="${APP_URL}/accounts/tiktok/callback"
|
|
|
|
|
|
|
|
|
|
|
|
# Facebook (https://developers.facebook.com)
|
|
|
|
|
|
FACEBOOK_CLIENT_ID=
|
|
|
|
|
|
FACEBOOK_CLIENT_SECRET=
|
|
|
|
|
|
FACEBOOK_CLIENT_REDIRECT="${APP_URL}/accounts/facebook/callback"
|
|
|
|
|
|
|
|
|
|
|
|
# Instagram (https://developers.facebook.com)
|
|
|
|
|
|
INSTAGRAM_CLIENT_ID=
|
|
|
|
|
|
INSTAGRAM_CLIENT_SECRET=
|
|
|
|
|
|
INSTAGRAM_CLIENT_REDIRECT="${APP_URL}/accounts/instagram/callback"
|
|
|
|
|
|
|
|
|
|
|
|
# Threads (https://developers.facebook.com)
|
|
|
|
|
|
THREADS_CLIENT_ID=
|
|
|
|
|
|
THREADS_CLIENT_SECRET=
|
|
|
|
|
|
THREADS_CLIENT_REDIRECT="${APP_URL}/accounts/threads/callback"
|
|
|
|
|
|
|
2026-03-31 00:18:07 +00:00
|
|
|
|
# Google (https://console.cloud.google.com)
|
|
|
|
|
|
# Used for YouTube social account connection AND Google login/signup
|
2026-05-04 21:53:21 +00:00
|
|
|
|
GOOGLE_AUTH_ENABLED=false
|
2026-01-18 21:00:09 +00:00
|
|
|
|
GOOGLE_CLIENT_ID=
|
|
|
|
|
|
GOOGLE_CLIENT_SECRET=
|
|
|
|
|
|
GOOGLE_CLIENT_REDIRECT="${APP_URL}/accounts/youtube/callback"
|
2026-03-31 00:18:07 +00:00
|
|
|
|
GOOGLE_AUTH_CALLBACK="${APP_URL}/auth/google/callback"
|
2026-01-18 21:00:09 +00:00
|
|
|
|
|
2026-05-04 21:53:21 +00:00
|
|
|
|
# GitHub (https://github.com/settings/developers)
|
|
|
|
|
|
# Used for GitHub login/signup
|
|
|
|
|
|
GITHUB_AUTH_ENABLED=false
|
|
|
|
|
|
GITHUB_CLIENT_ID=
|
|
|
|
|
|
GITHUB_CLIENT_SECRET=
|
|
|
|
|
|
GITHUB_AUTH_CALLBACK="${APP_URL}/auth/github/callback"
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Pinterest (https://developers.pinterest.com)
|
|
|
|
|
|
PINTEREST_CLIENT_ID=
|
|
|
|
|
|
PINTEREST_CLIENT_SECRET=
|
|
|
|
|
|
PINTEREST_CLIENT_REDIRECT="${APP_URL}/accounts/pinterest/callback"
|
|
|
|
|
|
|
2026-06-14 01:01:37 +00:00
|
|
|
|
# Telegram (single shared bot — create one via https://t.me/BotFather)
|
|
|
|
|
|
# After setting these, run: php artisan telegram:set-webhook
|
|
|
|
|
|
TELEGRAM_BOT_TOKEN=
|
|
|
|
|
|
TELEGRAM_BOT_USERNAME=
|
|
|
|
|
|
TELEGRAM_WEBHOOK_SECRET=
|
|
|
|
|
|
|
2026-06-16 18:28:58 +00:00
|
|
|
|
# Discord (single shared app+bot — create one via https://discord.com/developers/applications)
|
|
|
|
|
|
# OAuth2 → add the redirect below; Bot → enable "Server Members Intent" for mention search.
|
|
|
|
|
|
DISCORD_CLIENT_ID=
|
|
|
|
|
|
DISCORD_CLIENT_SECRET=
|
|
|
|
|
|
DISCORD_BOT_TOKEN=
|
|
|
|
|
|
DISCORD_CLIENT_REDIRECT="${APP_URL}/accounts/discord/callback"
|
|
|
|
|
|
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
# AI Services
|
|
|
|
|
|
OPENAI_API_KEY=
|
2026-05-06 22:46:28 +00:00
|
|
|
|
ANTHROPIC_API_KEY=
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
GEMINI_API_KEY=
|
Fix OpenRouter via laravel/ai default provider (revises #216) (#220)
* Fix OpenRouter support by using laravel/ai default provider config.
PR #216 patched Lab::OpenRouter into every agent match, but laravel/ai
already resolves config('ai.default') — including openrouter — when agents
omit provider(). Those matches also forced unknown providers to Gemini and
BrandAnalyzerRunner checked a non-existent services.openrouter key.
Remove the duplicated provider() overrides, gate availability on
ai.providers.*.key, and document OPENROUTER_API_KEY.
Co-authored-by: Paulo Castellano <hello@paulocastellano.com>
* chore(deps): bump laravel/ai to v0.10.3 for native OpenRouter support
v0.5.1's OpenRouter driver only covered text/embeddings via the legacy
Prism gateway. v0.10.3 ships a native OpenRouter gateway with image,
audio (TTS/STT), and web search support, and drops prism-php/prism as
a dependency. ai-sdk-development skill docs refreshed via boost:update
to match the installed version.
* fix: honor AI_IMAGE_PROVIDER instead of hardcoding OpenAI's gpt-image-2
AiImageClient always passed model: 'gpt-image-2' to Image::of()->generate(),
so AI_IMAGE_PROVIDER silently did nothing for any provider other than
OpenAI — gemini/xai/openrouter would fail against an OpenAI-only model id
and quietly fall back to a stock photo. Usage recording was hardcoded to
provider 'openai' too, so credits were billed against the wrong model
whenever a different provider actually ran.
Drop the hardcoded model so generation falls through to the SDK's own
config('ai.default_for_images') + per-provider default model, and read
the actual provider/model back off the response's meta for usage
recording and source_meta instead of assuming OpenAI.
* refactor: extract AiImageClient into single-purpose steps, fix uncaught exception
generate() built the prompt, called the SDK, and unpacked the response all
in one block, with bytes extraction happening after the try/catch — so a
response with an empty images collection threw an uncaught RuntimeException
from ImageResponse::firstImage() instead of returning null as documented.
Split into cleanKeywords(), buildPrompt(), resolveBrandContext(), and
toResult(), and moved response unpacking inside the try block so any
malformed response is treated as a failure like everything else. Added a
regression test with an empty-images fake response.
* fix: drop hardcoded default_text_model, resolve model per provider
default_text_model was the only per-modality model override in ai.php —
image, audio, transcription, embeddings, and reranking all just pick a
provider and let it use its own default model. Text had a config-pinned
model on top, forced into every agent's model() and into every usage
log's model field regardless of which provider actually ran. Switching
AI_TEXT_PROVIDER (e.g. to openrouter) kept sending OpenAI's model id to
whichever provider ended up handling the request.
Removed model() from all six agents so laravel/ai resolves the model
from the active provider's own default (OpenAI's default is already
'gpt-5.4', so no behavior change there). Usage-recording call sites now
read the actual provider/model back off the response's meta instead of
assuming config('ai.default')/default_text_model. StreamPostContent
needed the then() callback since broadcast()'s StreamableAgentResponse
doesn't expose meta directly.
* refactor: drop AiConfiguration wrapper, use data_get() for array reads
AiConfiguration was a one-line static helper used by only two call
sites, with no laravel/ai equivalent to lean on (confirmed AiManager
and the Provider base class expose no isConfigured()/hasKey() check —
the package's model is try-then-catch, not pre-flight checks). Inlined
the filled(config(...)) check directly into HandleInertiaRequests and
BrandAnalyzerRunner instead of keeping a class around one line of logic.
Also swapped direct array-key reads for data_get() per project
convention across every file touched by the recent AI provider/model
fixes (agents' budget arrays, RunGenerateNode/StreamPostCreation's
humanizer merge, RegeneratePostMediaImage's baseContext/copy/rendered
access). Write/assignment sites (`$x['key'] = ...`) are left as-is —
data_get() only reads.
* feat: enhance AI configuration with new providers and options
Added strict types declaration and updated Azure OpenAI API version. Introduced new 'bedrock' provider configuration with AWS credentials and role assumptions. Enhanced existing providers with additional options, including image deployment for Azure and OpenAI, and updated URLs for Gemini and Ollama. Added support for an 'openai-compatible' driver to broaden integration capabilities.
* fix: remove trailing newline in AI configuration file
* feat: add support for OpenRouter and ElevenLabs API keys in configuration
Updated the production Docker Compose file and example environment file to include commented-out entries for OPENROUTER_API_KEY and ELEVENLABS_API_KEY. This enhances the configuration options for AI providers, allowing for easier integration of additional services.
* feat: allow per-provider model overrides for every AI modality
Adds a `models` array to each provider block, wired to env vars, so
self-hosted operators can pin a specific text/image/audio/transcription/
embeddings/reranking model instead of relying on the package's built-in
default for that provider. Only added for the modalities each provider
actually implements (verified against laravel/ai's Provider classes).
Azure is left untouched — it resolves models via deployment names
(AZURE_OPENAI_DEPLOYMENT etc.), not raw model strings, which was already
wired before this change.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-11 00:38:50 +00:00
|
|
|
|
OPENROUTER_API_KEY=
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
ELEVENLABS_API_KEY=
|
|
|
|
|
|
|
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs
Mentions in post comments
- @mention autocomplete (workspace members, current user excluded) with
marker syntax @[uuid] persisted, display names rendered via CommentBody
chips; live edit replaces markers with names and converts back on save.
- NotifyMentions action with workspace-scoped membership check, dedupes
same user, only newly-added mentions on update.
- Email + in-app via SendNotification job, respecting per-user
notification_preferences.mentioned_in_comment.
- Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients
get only the in-app notification — no email noise.
- Real-time bell on workspace.{id}.user.{id} private channel
(NotificationCreated event), scoped channel name avoids client-side
filtering and lays out a convention for future workspace channels.
- Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source
template for the email is committed and built into resources/views/mail.
AI generation refactor (Action layer + MCP)
- Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException
so agent tools and MCP tools share a single domain entry point.
- Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both
return MediaResource payloads.
- Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2.
- config/ai.php is now the single source of truth driven by env, removing
the trypost.ai shim. Default text/image providers flipped to OpenAI.
Settings/UX
- /settings/workspace split into shadcn Tabs (Workspace / Brand / Users)
with three components.
- /assets and the in-editor MediaPicker open the ImagePreviewDialog
lightbox on image click while preserving action button behaviour.
- Comments tab landed via ?tab=comments&comment=<id> from notification
click (scroll-to + temporary highlight).
- Mention autocomplete popover flips above when near the viewport bottom.
- Real social platform PNGs replace Tabler brand glyphs in schedule
pills and post list, with hover tooltip carrying display_name + handle.
Bug fixes
- AcceptInvite: controller now passes workspace + role payload that the
Vue page expects; login/register CTAs preselect the invite email.
- WorkspaceInvite mailable: stopped referencing nonexistent
$invite->workspace and $invite->role; column added to the migration,
Invite model casts role to WorkspaceRole, CreateInvite persists it.
- PostCommentCreated: added broadcastAs so .PostCommentCreated actually
matches the Echo listener; payload now includes mentioned_users so
receivers render the chip correctly without a refetch.
- Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/
TikTok/YouTube switched from item.type === 'image' to
!isVideoMedia(item) so media without a persisted type still renders.
- UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the
posts.media JSON keeps the metadata that the previews need.
- Removed throttle:6,1 from social connect routes (was 429ing legitimate
OAuth retries).
- Used MediaType enum cases instead of literal 'image'/'video' strings
when creating media rows.
Tests
- MentionParser unit tests, NotifyMentions feature tests including
online/offline channel selection and preference gating, MCP AI tool
happy paths, MentionedInComment mailable rendering, AcceptInvite +
search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
|
|
|
|
# AI Provider Selection
|
Fix OpenRouter via laravel/ai default provider (revises #216) (#220)
* Fix OpenRouter support by using laravel/ai default provider config.
PR #216 patched Lab::OpenRouter into every agent match, but laravel/ai
already resolves config('ai.default') — including openrouter — when agents
omit provider(). Those matches also forced unknown providers to Gemini and
BrandAnalyzerRunner checked a non-existent services.openrouter key.
Remove the duplicated provider() overrides, gate availability on
ai.providers.*.key, and document OPENROUTER_API_KEY.
Co-authored-by: Paulo Castellano <hello@paulocastellano.com>
* chore(deps): bump laravel/ai to v0.10.3 for native OpenRouter support
v0.5.1's OpenRouter driver only covered text/embeddings via the legacy
Prism gateway. v0.10.3 ships a native OpenRouter gateway with image,
audio (TTS/STT), and web search support, and drops prism-php/prism as
a dependency. ai-sdk-development skill docs refreshed via boost:update
to match the installed version.
* fix: honor AI_IMAGE_PROVIDER instead of hardcoding OpenAI's gpt-image-2
AiImageClient always passed model: 'gpt-image-2' to Image::of()->generate(),
so AI_IMAGE_PROVIDER silently did nothing for any provider other than
OpenAI — gemini/xai/openrouter would fail against an OpenAI-only model id
and quietly fall back to a stock photo. Usage recording was hardcoded to
provider 'openai' too, so credits were billed against the wrong model
whenever a different provider actually ran.
Drop the hardcoded model so generation falls through to the SDK's own
config('ai.default_for_images') + per-provider default model, and read
the actual provider/model back off the response's meta for usage
recording and source_meta instead of assuming OpenAI.
* refactor: extract AiImageClient into single-purpose steps, fix uncaught exception
generate() built the prompt, called the SDK, and unpacked the response all
in one block, with bytes extraction happening after the try/catch — so a
response with an empty images collection threw an uncaught RuntimeException
from ImageResponse::firstImage() instead of returning null as documented.
Split into cleanKeywords(), buildPrompt(), resolveBrandContext(), and
toResult(), and moved response unpacking inside the try block so any
malformed response is treated as a failure like everything else. Added a
regression test with an empty-images fake response.
* fix: drop hardcoded default_text_model, resolve model per provider
default_text_model was the only per-modality model override in ai.php —
image, audio, transcription, embeddings, and reranking all just pick a
provider and let it use its own default model. Text had a config-pinned
model on top, forced into every agent's model() and into every usage
log's model field regardless of which provider actually ran. Switching
AI_TEXT_PROVIDER (e.g. to openrouter) kept sending OpenAI's model id to
whichever provider ended up handling the request.
Removed model() from all six agents so laravel/ai resolves the model
from the active provider's own default (OpenAI's default is already
'gpt-5.4', so no behavior change there). Usage-recording call sites now
read the actual provider/model back off the response's meta instead of
assuming config('ai.default')/default_text_model. StreamPostContent
needed the then() callback since broadcast()'s StreamableAgentResponse
doesn't expose meta directly.
* refactor: drop AiConfiguration wrapper, use data_get() for array reads
AiConfiguration was a one-line static helper used by only two call
sites, with no laravel/ai equivalent to lean on (confirmed AiManager
and the Provider base class expose no isConfigured()/hasKey() check —
the package's model is try-then-catch, not pre-flight checks). Inlined
the filled(config(...)) check directly into HandleInertiaRequests and
BrandAnalyzerRunner instead of keeping a class around one line of logic.
Also swapped direct array-key reads for data_get() per project
convention across every file touched by the recent AI provider/model
fixes (agents' budget arrays, RunGenerateNode/StreamPostCreation's
humanizer merge, RegeneratePostMediaImage's baseContext/copy/rendered
access). Write/assignment sites (`$x['key'] = ...`) are left as-is —
data_get() only reads.
* feat: enhance AI configuration with new providers and options
Added strict types declaration and updated Azure OpenAI API version. Introduced new 'bedrock' provider configuration with AWS credentials and role assumptions. Enhanced existing providers with additional options, including image deployment for Azure and OpenAI, and updated URLs for Gemini and Ollama. Added support for an 'openai-compatible' driver to broaden integration capabilities.
* fix: remove trailing newline in AI configuration file
* feat: add support for OpenRouter and ElevenLabs API keys in configuration
Updated the production Docker Compose file and example environment file to include commented-out entries for OPENROUTER_API_KEY and ELEVENLABS_API_KEY. This enhances the configuration options for AI providers, allowing for easier integration of additional services.
* feat: allow per-provider model overrides for every AI modality
Adds a `models` array to each provider block, wired to env vars, so
self-hosted operators can pin a specific text/image/audio/transcription/
embeddings/reranking model instead of relying on the package's built-in
default for that provider. Only added for the modalities each provider
actually implements (verified against laravel/ai's Provider classes).
Azure is left untouched — it resolves models via deployment names
(AZURE_OPENAI_DEPLOYMENT etc.), not raw model strings, which was already
wired before this change.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-11 00:38:50 +00:00
|
|
|
|
# text: openai | anthropic | gemini | openrouter | xai | groq | mistral | deepseek | ...
|
|
|
|
|
|
# image: openai | gemini | xai | openrouter
|
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs
Mentions in post comments
- @mention autocomplete (workspace members, current user excluded) with
marker syntax @[uuid] persisted, display names rendered via CommentBody
chips; live edit replaces markers with names and converts back on save.
- NotifyMentions action with workspace-scoped membership check, dedupes
same user, only newly-added mentions on update.
- Email + in-app via SendNotification job, respecting per-user
notification_preferences.mentioned_in_comment.
- Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients
get only the in-app notification — no email noise.
- Real-time bell on workspace.{id}.user.{id} private channel
(NotificationCreated event), scoped channel name avoids client-side
filtering and lays out a convention for future workspace channels.
- Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source
template for the email is committed and built into resources/views/mail.
AI generation refactor (Action layer + MCP)
- Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException
so agent tools and MCP tools share a single domain entry point.
- Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both
return MediaResource payloads.
- Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2.
- config/ai.php is now the single source of truth driven by env, removing
the trypost.ai shim. Default text/image providers flipped to OpenAI.
Settings/UX
- /settings/workspace split into shadcn Tabs (Workspace / Brand / Users)
with three components.
- /assets and the in-editor MediaPicker open the ImagePreviewDialog
lightbox on image click while preserving action button behaviour.
- Comments tab landed via ?tab=comments&comment=<id> from notification
click (scroll-to + temporary highlight).
- Mention autocomplete popover flips above when near the viewport bottom.
- Real social platform PNGs replace Tabler brand glyphs in schedule
pills and post list, with hover tooltip carrying display_name + handle.
Bug fixes
- AcceptInvite: controller now passes workspace + role payload that the
Vue page expects; login/register CTAs preselect the invite email.
- WorkspaceInvite mailable: stopped referencing nonexistent
$invite->workspace and $invite->role; column added to the migration,
Invite model casts role to WorkspaceRole, CreateInvite persists it.
- PostCommentCreated: added broadcastAs so .PostCommentCreated actually
matches the Echo listener; payload now includes mentioned_users so
receivers render the chip correctly without a refetch.
- Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/
TikTok/YouTube switched from item.type === 'image' to
!isVideoMedia(item) so media without a persisted type still renders.
- UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the
posts.media JSON keeps the metadata that the previews need.
- Removed throttle:6,1 from social connect routes (was 429ing legitimate
OAuth retries).
- Used MediaType enum cases instead of literal 'image'/'video' strings
when creating media rows.
Tests
- MentionParser unit tests, NotifyMentions feature tests including
online/offline channel selection and preference gating, MCP AI tool
happy paths, MentionedInComment mailable rendering, AcceptInvite +
search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
|
|
|
|
# audio: openai | elevenlabs
|
Fix OpenRouter via laravel/ai default provider (revises #216) (#220)
* Fix OpenRouter support by using laravel/ai default provider config.
PR #216 patched Lab::OpenRouter into every agent match, but laravel/ai
already resolves config('ai.default') — including openrouter — when agents
omit provider(). Those matches also forced unknown providers to Gemini and
BrandAnalyzerRunner checked a non-existent services.openrouter key.
Remove the duplicated provider() overrides, gate availability on
ai.providers.*.key, and document OPENROUTER_API_KEY.
Co-authored-by: Paulo Castellano <hello@paulocastellano.com>
* chore(deps): bump laravel/ai to v0.10.3 for native OpenRouter support
v0.5.1's OpenRouter driver only covered text/embeddings via the legacy
Prism gateway. v0.10.3 ships a native OpenRouter gateway with image,
audio (TTS/STT), and web search support, and drops prism-php/prism as
a dependency. ai-sdk-development skill docs refreshed via boost:update
to match the installed version.
* fix: honor AI_IMAGE_PROVIDER instead of hardcoding OpenAI's gpt-image-2
AiImageClient always passed model: 'gpt-image-2' to Image::of()->generate(),
so AI_IMAGE_PROVIDER silently did nothing for any provider other than
OpenAI — gemini/xai/openrouter would fail against an OpenAI-only model id
and quietly fall back to a stock photo. Usage recording was hardcoded to
provider 'openai' too, so credits were billed against the wrong model
whenever a different provider actually ran.
Drop the hardcoded model so generation falls through to the SDK's own
config('ai.default_for_images') + per-provider default model, and read
the actual provider/model back off the response's meta for usage
recording and source_meta instead of assuming OpenAI.
* refactor: extract AiImageClient into single-purpose steps, fix uncaught exception
generate() built the prompt, called the SDK, and unpacked the response all
in one block, with bytes extraction happening after the try/catch — so a
response with an empty images collection threw an uncaught RuntimeException
from ImageResponse::firstImage() instead of returning null as documented.
Split into cleanKeywords(), buildPrompt(), resolveBrandContext(), and
toResult(), and moved response unpacking inside the try block so any
malformed response is treated as a failure like everything else. Added a
regression test with an empty-images fake response.
* fix: drop hardcoded default_text_model, resolve model per provider
default_text_model was the only per-modality model override in ai.php —
image, audio, transcription, embeddings, and reranking all just pick a
provider and let it use its own default model. Text had a config-pinned
model on top, forced into every agent's model() and into every usage
log's model field regardless of which provider actually ran. Switching
AI_TEXT_PROVIDER (e.g. to openrouter) kept sending OpenAI's model id to
whichever provider ended up handling the request.
Removed model() from all six agents so laravel/ai resolves the model
from the active provider's own default (OpenAI's default is already
'gpt-5.4', so no behavior change there). Usage-recording call sites now
read the actual provider/model back off the response's meta instead of
assuming config('ai.default')/default_text_model. StreamPostContent
needed the then() callback since broadcast()'s StreamableAgentResponse
doesn't expose meta directly.
* refactor: drop AiConfiguration wrapper, use data_get() for array reads
AiConfiguration was a one-line static helper used by only two call
sites, with no laravel/ai equivalent to lean on (confirmed AiManager
and the Provider base class expose no isConfigured()/hasKey() check —
the package's model is try-then-catch, not pre-flight checks). Inlined
the filled(config(...)) check directly into HandleInertiaRequests and
BrandAnalyzerRunner instead of keeping a class around one line of logic.
Also swapped direct array-key reads for data_get() per project
convention across every file touched by the recent AI provider/model
fixes (agents' budget arrays, RunGenerateNode/StreamPostCreation's
humanizer merge, RegeneratePostMediaImage's baseContext/copy/rendered
access). Write/assignment sites (`$x['key'] = ...`) are left as-is —
data_get() only reads.
* feat: enhance AI configuration with new providers and options
Added strict types declaration and updated Azure OpenAI API version. Introduced new 'bedrock' provider configuration with AWS credentials and role assumptions. Enhanced existing providers with additional options, including image deployment for Azure and OpenAI, and updated URLs for Gemini and Ollama. Added support for an 'openai-compatible' driver to broaden integration capabilities.
* fix: remove trailing newline in AI configuration file
* feat: add support for OpenRouter and ElevenLabs API keys in configuration
Updated the production Docker Compose file and example environment file to include commented-out entries for OPENROUTER_API_KEY and ELEVENLABS_API_KEY. This enhances the configuration options for AI providers, allowing for easier integration of additional services.
* feat: allow per-provider model overrides for every AI modality
Adds a `models` array to each provider block, wired to env vars, so
self-hosted operators can pin a specific text/image/audio/transcription/
embeddings/reranking model instead of relying on the package's built-in
default for that provider. Only added for the modalities each provider
actually implements (verified against laravel/ai's Provider classes).
Azure is left untouched — it resolves models via deployment names
(AZURE_OPENAI_DEPLOYMENT etc.), not raw model strings, which was already
wired before this change.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-11 00:38:50 +00:00
|
|
|
|
# OpenRouter is a first-class laravel/ai provider (AI_TEXT_PROVIDER=openrouter + OPENROUTER_API_KEY).
|
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs
Mentions in post comments
- @mention autocomplete (workspace members, current user excluded) with
marker syntax @[uuid] persisted, display names rendered via CommentBody
chips; live edit replaces markers with names and converts back on save.
- NotifyMentions action with workspace-scoped membership check, dedupes
same user, only newly-added mentions on update.
- Email + in-app via SendNotification job, respecting per-user
notification_preferences.mentioned_in_comment.
- Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients
get only the in-app notification — no email noise.
- Real-time bell on workspace.{id}.user.{id} private channel
(NotificationCreated event), scoped channel name avoids client-side
filtering and lays out a convention for future workspace channels.
- Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source
template for the email is committed and built into resources/views/mail.
AI generation refactor (Action layer + MCP)
- Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException
so agent tools and MCP tools share a single domain entry point.
- Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both
return MediaResource payloads.
- Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2.
- config/ai.php is now the single source of truth driven by env, removing
the trypost.ai shim. Default text/image providers flipped to OpenAI.
Settings/UX
- /settings/workspace split into shadcn Tabs (Workspace / Brand / Users)
with three components.
- /assets and the in-editor MediaPicker open the ImagePreviewDialog
lightbox on image click while preserving action button behaviour.
- Comments tab landed via ?tab=comments&comment=<id> from notification
click (scroll-to + temporary highlight).
- Mention autocomplete popover flips above when near the viewport bottom.
- Real social platform PNGs replace Tabler brand glyphs in schedule
pills and post list, with hover tooltip carrying display_name + handle.
Bug fixes
- AcceptInvite: controller now passes workspace + role payload that the
Vue page expects; login/register CTAs preselect the invite email.
- WorkspaceInvite mailable: stopped referencing nonexistent
$invite->workspace and $invite->role; column added to the migration,
Invite model casts role to WorkspaceRole, CreateInvite persists it.
- PostCommentCreated: added broadcastAs so .PostCommentCreated actually
matches the Echo listener; payload now includes mentioned_users so
receivers render the chip correctly without a refetch.
- Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/
TikTok/YouTube switched from item.type === 'image' to
!isVideoMedia(item) so media without a persisted type still renders.
- UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the
posts.media JSON keeps the metadata that the previews need.
- Removed throttle:6,1 from social connect routes (was 429ing legitimate
OAuth retries).
- Used MediaType enum cases instead of literal 'image'/'video' strings
when creating media rows.
Tests
- MentionParser unit tests, NotifyMentions feature tests including
online/offline channel selection and preference gating, MCP AI tool
happy paths, MentionedInComment mailable rendering, AcceptInvite +
search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
|
|
|
|
AI_TEXT_PROVIDER=openai
|
|
|
|
|
|
AI_IMAGE_PROVIDER=openai
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
AI_AUDIO_PROVIDER=elevenlabs
|
|
|
|
|
|
|
2026-05-06 22:46:28 +00:00
|
|
|
|
# ============================================
|
|
|
|
|
|
# Stripe (Cashier — billing)
|
|
|
|
|
|
# ============================================
|
|
|
|
|
|
# Required when SELF_HOSTED=false. Get keys at https://dashboard.stripe.com/apikeys
|
|
|
|
|
|
STRIPE_KEY=
|
|
|
|
|
|
STRIPE_SECRET=
|
|
|
|
|
|
STRIPE_WEBHOOK_SECRET=
|
2026-08-08 00:25:32 +00:00
|
|
|
|
# SaaS default (recipe A): 8-day Stripe trial with card, no coupon, no promo field.
|
|
|
|
|
|
# REQUIRE_CARD_FOR_TRIAL=true forces Checkout before app access (no generic trial).
|
2026-05-21 23:18:21 +00:00
|
|
|
|
REQUIRE_CARD_FOR_TRIAL=true
|
2026-08-08 00:25:32 +00:00
|
|
|
|
# Trial length in days. Card-required Checkout uses trialDays for first-time
|
|
|
|
|
|
# subscribers when no first-month coupon is applied; re-subscribers skip trial.
|
|
|
|
|
|
# No-card mode uses this for accounts.trial_ends_at. 0 = off.
|
feat: per-workspace pricing, onboarding, and billing overhaul
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
(workspace/social/member) and the legacy plan tiers (single Workspace plan).
Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
(Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
(users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
price. 8-day trial so Stripe displays 7.
Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.
System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
(system feature, not the user's usage).
Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
2026-06-21 23:40:03 +00:00
|
|
|
|
CASHIER_TRIAL_DAYS=8
|
2026-08-08 00:25:32 +00:00
|
|
|
|
# Optional Stripe Coupon ID (amount_off, duration=once). When set for a qualifying
|
|
|
|
|
|
# first-time single-workspace checkout, applies the coupon and SKIPS trialDays
|
|
|
|
|
|
# (e.g. TRIAL1USD for a $1 first month). Empty = trial mode above.
|
|
|
|
|
|
# XOR with CASHIER_ALLOW_PROMOTION_CODES only when the coupon would apply —
|
|
|
|
|
|
# Stripe forbids both on one session (ConfigureSubscriptionCheckout throws).
|
2026-07-09 18:18:00 +00:00
|
|
|
|
STRIPE_FIRST_MONTH_COUPON_ID=
|
2026-08-08 00:25:32 +00:00
|
|
|
|
# Show Stripe Checkout promotion-code field when no coupon is applied.
|
|
|
|
|
|
# Defaults to false (recipe A). Must be false when a first-month coupon applies.
|
|
|
|
|
|
CASHIER_ALLOW_PROMOTION_CODES=false
|
2026-05-06 22:46:28 +00:00
|
|
|
|
|
|
|
|
|
|
# Stripe Plan Price IDs (one per plan × interval). Used by PlanSeeder.
|
feat: per-workspace pricing, onboarding, and billing overhaul
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
(workspace/social/member) and the legacy plan tiers (single Workspace plan).
Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
(Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
(users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
price. 8-day trial so Stripe displays 7.
Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.
System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
(system feature, not the user's usage).
Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
2026-06-21 23:40:03 +00:00
|
|
|
|
STRIPE_WORKSPACE_MONTHLY=
|
|
|
|
|
|
STRIPE_WORKSPACE_YEARLY=
|
2026-05-06 22:46:28 +00:00
|
|
|
|
|
|
|
|
|
|
# Laravel Nightwatch (production telemetry — disabled by default in dev)
|
|
|
|
|
|
NIGHTWATCH_ENABLED=false
|
|
|
|
|
|
NIGHTWATCH_TOKEN=
|
|
|
|
|
|
|
|
|
|
|
|
# Platform feature flags (all default to true; set to false to hide a platform)
|
|
|
|
|
|
# TIKTOK_ENABLED=true
|
|
|
|
|
|
# PINTEREST_ENABLED=true
|
|
|
|
|
|
# MASTODON_ENABLED=true
|
|
|
|
|
|
# BLUESKY_ENABLED=true
|
2026-06-14 01:01:37 +00:00
|
|
|
|
# TELEGRAM_ENABLED=true
|
2026-05-06 22:46:28 +00:00
|
|
|
|
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
# Media Services
|
|
|
|
|
|
UNSPLASH_ACCESS_KEY=
|
|
|
|
|
|
UNSPLASH_SECRET_KEY=
|
|
|
|
|
|
GIPHY_API_KEY=
|
|
|
|
|
|
|
2026-03-31 00:32:43 +00:00
|
|
|
|
# Google Tag Manager (optional - analytics)
|
|
|
|
|
|
GTM_ID=
|
|
|
|
|
|
|
2026-05-07 15:42:35 +00:00
|
|
|
|
# PostHog (optional - analytics; off by default, self-hosted installs can ignore)
|
|
|
|
|
|
POSTHOG_ENABLED=false
|
2026-03-31 00:18:07 +00:00
|
|
|
|
POSTHOG_API_KEY=
|
|
|
|
|
|
POSTHOG_HOST=https://us.i.posthog.com
|
|
|
|
|
|
|
2026-01-18 21:00:09 +00:00
|
|
|
|
# Vite
|
|
|
|
|
|
VITE_APP_NAME="${APP_NAME}"
|
|
|
|
|
|
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
|
|
|
|
|
VITE_REVERB_HOST="${REVERB_HOST}"
|
|
|
|
|
|
VITE_REVERB_PORT="${REVERB_PORT}"
|
|
|
|
|
|
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
2026-05-07 15:42:35 +00:00
|
|
|
|
VITE_POSTHOG_ENABLED="${POSTHOG_ENABLED}"
|
2026-03-31 00:18:07 +00:00
|
|
|
|
VITE_POSTHOG_API_KEY="${POSTHOG_API_KEY}"
|
|
|
|
|
|
VITE_POSTHOG_HOST="${POSTHOG_HOST}"
|