'use client'; import { useState, useCallback } from 'react'; import { useSWRConfig } from 'swr'; import { useUser } from '../layout/user.context'; import copy from 'copy-to-clipboard'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { useDecisionModal } from '@gitroom/frontend/components/layout/new-modal'; import { DeveloperComponent } from '@gitroom/frontend/components/developer/developer.component'; import clsx from 'clsx'; const mcpClients = [ 'Claude Code', 'Cursor', 'VS Code / Copilot', 'Windsurf', 'Amp', 'Codex', 'Gemini CLI', 'Warp', ] as const; type McpClient = (typeof mcpClients)[number]; const getMcpConfig = ( client: McpClient, method: 'header' | 'path', mcpBase: string, apiKey: string ): { config: string; hint: string } => { const urlWithKey = `${mcpBase}/mcp/${apiKey}`; const urlBase = `${mcpBase}/mcp`; const bearer = `Bearer ${apiKey}`; const json = (obj: object) => JSON.stringify(obj, null, 2); if (method === 'path') { switch (client) { case 'Claude Code': return { config: `claude mcp add postiz --transport http "${urlWithKey}"`, hint: 'Run this command in your terminal.', }; case 'Cursor': return { config: json({ mcpServers: { postiz: { url: urlWithKey } } }), hint: 'Add to .cursor/mcp.json in your project root.', }; case 'VS Code / Copilot': return { config: json({ servers: { postiz: { type: 'http', url: urlWithKey } }, }), hint: 'Add to .vscode/mcp.json in your project root.', }; case 'Windsurf': return { config: json({ mcpServers: { postiz: { serverUrl: urlWithKey } }, }), hint: 'Add to ~/.codeium/windsurf/mcp_config.json', }; case 'Amp': return { config: `amp mcp add postiz ${urlWithKey}`, hint: 'Run this command in your terminal.', }; case 'Codex': return { config: `# ~/.codex/config.toml\n\n[mcp_servers.postiz]\nurl = "${urlWithKey}"`, hint: 'Add to ~/.codex/config.toml', }; case 'Gemini CLI': return { config: json({ mcpServers: { postiz: { url: urlWithKey } } }), hint: 'Add to ~/.gemini/settings.json', }; case 'Warp': return { config: json({ postiz: { url: urlWithKey } }), hint: 'Settings > MCP Servers > + Add, then paste this config.', }; } } switch (client) { case 'Claude Code': return { config: `claude mcp add --transport http postiz ${urlBase} --header "Authorization: ${bearer}"`, hint: 'Run this command in your terminal.', }; case 'Cursor': return { config: json({ mcpServers: { postiz: { url: urlBase, headers: { Authorization: bearer } }, }, }), hint: 'Add to .cursor/mcp.json in your project root.', }; case 'VS Code / Copilot': return { config: json({ servers: { postiz: { type: 'http', url: urlBase, headers: { Authorization: bearer }, }, }, }), hint: 'Add to .vscode/mcp.json in your project root.', }; case 'Windsurf': return { config: json({ mcpServers: { postiz: { serverUrl: urlBase, headers: { Authorization: bearer }, }, }, }), hint: 'Add to ~/.codeium/windsurf/mcp_config.json', }; case 'Amp': return { config: json({ 'amp.mcpServers': { postiz: { url: urlBase, headers: { Authorization: bearer } }, }, }), hint: 'Add to your Amp settings.json', }; case 'Codex': return { config: `# ~/.codex/config.toml\n\n[mcp_servers.postiz]\nurl = "${urlBase}"\nhttp_headers = { "Authorization" = "${bearer}" }`, hint: 'Add to ~/.codex/config.toml', }; case 'Gemini CLI': return { config: json({ mcpServers: { postiz: { url: urlBase, headers: { Authorization: bearer } }, }, }), hint: 'Add to ~/.gemini/settings.json', }; case 'Warp': return { config: json({ postiz: { url: urlBase, headers: { Authorization: bearer } }, }), hint: 'Settings > MCP Servers > + Add, then paste this config.', }; } }; const CopyButton = ({ text, label, }: { text: string; label: string; }) => { const toaster = useToaster(); return ( ); }; const McpSection = ({ user, mcpBase, }: { user: { publicApi: string }; mcpBase: string; }) => { const t = useT(); const [activeClient, setActiveClient] = useState('Claude Code'); const [method, setMethod] = useState<'header' | 'path'>('header'); const [revealed, setRevealed] = useState(false); const { config, hint } = getMcpConfig( activeClient, method, mcpBase, user.publicApi ); const remoteUrl = `${mcpBase}/mcp/${user.publicApi}`; const cliUrl = `${mcpBase}/mcp`; const maskedConfig = revealed ? config : config.replace(new RegExp(user.publicApi.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '*'.repeat(user.publicApi.length)); const maskedRemoteUrl = revealed ? remoteUrl : remoteUrl.replace(user.publicApi, '*'.repeat(user.publicApi.length)); return (
{t('mcp_client_configuration', 'MCP Client Configuration')}
{t( 'connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster', 'Connect Postiz MCP server to your client (Http streaming) to schedule your posts faster.' )}
{t('read_the_docs', 'Docs')}
{t('auth_method', 'Authentication')}
{(['header', 'path'] as const).map((m) => ( ))}
{method === 'header' && (
{t('mcp_client', 'Client')}
{mcpClients.map((client) => ( ))}
)}
{method === 'header' ? hint : t( 'remote_server_url_hint', 'Paste this URL into your remote MCP client (ChatGPT, Claude, etc.).' )}
            {method === 'header' ? maskedConfig : maskedRemoteUrl}
          
{method === 'header' && ( )}
); }; const localCliSteps = [ { label: 'Install the CLI', code: 'npm install -g postiz', }, { label: 'Run: postiz auth:login', code: 'postiz auth:login', }, { label: 'Install the Postiz skill for your AI agent', code: 'npx skills add gitroomhq/postiz-agent', }, ] as const; const ciCliSteps = [ { label: 'Install the CLI', code: 'npm install -g postiz', }, { label: 'Set your API key as an environment variable', code: 'export POSTIZ_API_KEY="{API_KEY}"', }, { label: 'Install the Postiz skill for your AI agent', code: 'npx skills add gitroomhq/postiz-agent', }, ] as const; const CliSection = ({ apiKey }: { apiKey: string }) => { const t = useT(); const [mode, setMode] = useState<'local' | 'ci'>('local'); const [revealed, setRevealed] = useState(false); const steps = mode === 'local' ? localCliSteps.map((step) => ({ ...step })) : ciCliSteps.map((step) => ({ ...step, code: step.code.replace('{API_KEY}', apiKey), })); const displaySteps = mode === 'ci' && !revealed ? steps.map((step) => ({ ...step, code: step.code.replace( new RegExp(apiKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '*'.repeat(apiKey.length) ), })) : steps; return (
{t('cli_and_skills', 'CLI & AI Skills')}
{t( 'cli_description', 'Use the Postiz CLI to automate posting from your terminal, or install the skill to let your AI agent schedule posts for you.' )}
{t('read_the_docs', 'Docs')}
{(['local', 'ci'] as const).map((m) => ( ))}
{displaySteps.map((step, i) => (
{i + 1}. {step.label}
              {step.code}
            
))}
{mode === 'ci' && ( )} s.code).join(' && ')} label={t('copy_all', 'Copy All')} />
); }; const PublicApiContent = () => { const user = useUser(); const { backendUrl, frontEndUrl, mcpUrl } = useVariables(); const toaster = useToaster(); const fetch = useFetch(); const decision = useDecisionModal(); const { mutate } = useSWRConfig(); const [reveal, setReveal] = useState(false); const t = useT(); const rotateKey = useCallback(async () => { const approved = await decision.open({ title: t('rotate_api_key', 'Rotate API Key?'), description: t( 'rotate_api_key_description', 'This will generate a new API key and invalidate the current one. Any integrations using the old key will stop working.' ), approveLabel: t('rotate', 'Rotate'), cancelLabel: t('cancel', 'Cancel'), }); if (!approved) return; await fetch('/user/api-key/rotate', { method: 'POST' }); await mutate('/user/self'); setReveal(false); toaster.show( t('api_key_rotated', 'API Key rotated successfully'), 'success' ); }, [decision, fetch, mutate, toaster]); if (!user || !user.publicApi) { return null; } const mcpBase = mcpUrl || backendUrl; return (
{t( 'api_auth_note_line1', 'Use your API Key to automate your own account.' )}
{t( 'api_auth_note_line2', 'If you are building a product that schedules posts on behalf of other Postiz users,' )}
{t( 'api_auth_note_line3', 'create an OAuth App under the "Apps" tab. Your users will authorize your app via OAuth2,' )}
{t( 'api_auth_note_line4', 'and you will receive a pos_ prefixed token that works with the API, MCP, and CLI — just like an API Key.' )}
{t('api_key', 'API Key')}
{t( 'use_postiz_api_to_integrate_with_your_tools', 'Use Postiz API to integrate with your tools.' )}
{t('read_the_docs', 'Docs')} {t('n8n_node', 'N8N Node')}
{reveal ? ( user.publicApi ) : ( {user.publicApi.slice(0, -5)} {user.publicApi.slice(-5)} )}
); }; export const PublicComponent = () => { const t = useT(); const [subTab, setSubTab] = useState<'api' | 'developer'>('api'); return (
{(['api', 'developer'] as const).map((tab) => ( ))}
{subTab === 'api' && } {subTab === 'developer' && }
); };