diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx index 41d8450..8ec890c 100644 --- a/app/(dashboard)/settings/page.tsx +++ b/app/(dashboard)/settings/page.tsx @@ -28,6 +28,13 @@ interface SettingConfig { } const SETTINGS_GROUPS: SettingsGroup[] = [ + { + name: "Claude", + description: + "OAuth access token for the Claude Code CLI subprocess that drives every pipeline agent. Tokens expire — refresh here when launches start failing with auth errors.", + docsUrl: "https://docs.claude.com/en/docs/claude-code/setup", + keys: ["CLAUDE_CODE_OAUTH_TOKEN"], + }, { name: "Postiz", description: @@ -59,6 +66,11 @@ const SETTINGS_GROUPS: SettingsGroup[] = [ ]; const SETTINGS_CONFIG: Record = { + CLAUDE_CODE_OAUTH_TOKEN: { + label: "Claude Code OAuth Token", + placeholder: "sk-ant-oat01-...", + secret: true, + }, POSTIZ_URL: { label: "Postiz URL", placeholder: "http://localhost:5000" }, POSTIZ_API_KEY: { label: "Postiz API Key", diff --git a/lib/claude.ts b/lib/claude.ts index c361788..fb80d2b 100644 --- a/lib/claude.ts +++ b/lib/claude.ts @@ -663,6 +663,7 @@ async function loadPipelineEnv(): Promise> { const settings = await getAllSettings(); const env: Record = {}; + if (settings.CLAUDE_CODE_OAUTH_TOKEN) env.CLAUDE_CODE_OAUTH_TOKEN = settings.CLAUDE_CODE_OAUTH_TOKEN; if (settings.TAVILY_API_KEY) env.TAVILY_API_KEY = settings.TAVILY_API_KEY; if (settings.POSTIZ_URL) env.POSTIZ_URL = settings.POSTIZ_URL; if (settings.POSTIZ_API_KEY) env.POSTIZ_API_KEY = settings.POSTIZ_API_KEY; diff --git a/lib/settings.ts b/lib/settings.ts index 1f03b0d..fd55133 100644 --- a/lib/settings.ts +++ b/lib/settings.ts @@ -2,6 +2,9 @@ import { prisma } from "./prisma"; // Settings keys and their env var fallbacks const SETTINGS_KEYS = { + // Claude Code CLI authentication (OAuth access token from Claude Max) + CLAUDE_CODE_OAUTH_TOKEN: { envVar: "CLAUDE_CODE_OAUTH_TOKEN", label: "Claude Code OAuth Token", placeholder: "sk-ant-oat01-...", secret: true }, + // Postiz POSTIZ_URL: { envVar: "POSTIZ_URL", label: "Postiz URL", placeholder: "http://localhost:5000" }, POSTIZ_API_KEY: { envVar: "POSTIZ_API_KEY", label: "Postiz API Key", placeholder: "your-postiz-api-key", secret: true }, @@ -23,6 +26,12 @@ export const SETTINGS_CONFIG = SETTINGS_KEYS; // Grouped for UI export const SETTINGS_GROUPS = [ + { + name: "Claude", + description: "OAuth access token for the Claude Code CLI subprocess that drives every pipeline agent. Get one with `claude setup-token` then `security find-generic-password -s 'Claude Code-credentials' -a $(whoami) -w` and use the `claudeAiOauth.accessToken` field. Tokens expire — refresh here when launches start failing with auth errors.", + docsUrl: "https://docs.claude.com/en/docs/claude-code/setup", + keys: ["CLAUDE_CODE_OAUTH_TOKEN"] as SettingKey[], + }, { name: "Postiz", description: "Self-hosted social media scheduling. Handles Instagram and TikTok publishing.", @@ -100,6 +109,39 @@ export async function checkIntegrationStatus(): Promise = {}; + // Claude OAuth token — validate by hitting the messages API with a 1-token call. + // A valid OAuth token returns 200; an expired/invalid one returns 401. + if (settings.CLAUDE_CODE_OAUTH_TOKEN) { + try { + const res = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "anthropic-version": "2023-06-01", + "anthropic-beta": "oauth-2025-04-20", + Authorization: `Bearer ${settings.CLAUDE_CODE_OAUTH_TOKEN}`, + }, + body: JSON.stringify({ + model: "claude-haiku-4-5-20251001", + max_tokens: 1, + messages: [{ role: "user", content: "." }], + }), + signal: AbortSignal.timeout(10000), + }); + if (res.ok) { + status.claude = { connected: true }; + } else if (res.status === 401) { + status.claude = { connected: false, error: "Token expired or invalid" }; + } else { + status.claude = { connected: false, error: `HTTP ${res.status}` }; + } + } catch (e) { + status.claude = { connected: false, error: e instanceof Error ? e.message : "Connection failed" }; + } + } else { + status.claude = { connected: false, error: "Not configured" }; + } + // Postiz if (settings.POSTIZ_URL && settings.POSTIZ_API_KEY) { try {