| "use server"; |
|
|
| import { NextResponse } from "next/server"; |
| import fs from "fs/promises"; |
| import path from "path"; |
| import os from "os"; |
| import { ensureCliConfigWriteAllowed, getCliConfigPaths } from "@/shared/services/cliRuntime"; |
|
|
| const PROFILES_DIR = path.join(os.homedir(), ".omniroute", "codex-profiles"); |
|
|
| |
| |
| |
| async function ensureProfilesDir() { |
| await fs.mkdir(PROFILES_DIR, { recursive: true }); |
| return PROFILES_DIR; |
| } |
|
|
| |
| |
| |
| function extractAuthLabel(authJson) { |
| try { |
| const data = JSON.parse(authJson); |
| |
| if (data.tokens?.id_token) { |
| const payload = data.tokens.id_token.split(".")[1]; |
| const decoded = JSON.parse(Buffer.from(payload, "base64").toString()); |
| if (decoded.email) return decoded.email; |
| } |
| if (data.auth_mode) return data.auth_mode; |
| if (data.OPENAI_API_KEY) return `API Key: ${data.OPENAI_API_KEY.slice(0, 8)}...`; |
| return "unknown"; |
| } catch { |
| return "unknown"; |
| } |
| } |
|
|
| |
| export async function GET() { |
| try { |
| await ensureProfilesDir(); |
|
|
| let entries; |
| try { |
| entries = await fs.readdir(PROFILES_DIR); |
| } catch { |
| return NextResponse.json({ profiles: [] }); |
| } |
|
|
| const profileFiles = entries.filter((e) => e.endsWith(".json")); |
| const profiles = []; |
|
|
| for (const file of profileFiles) { |
| try { |
| const raw = await fs.readFile(path.join(PROFILES_DIR, file), "utf-8"); |
| const profile = JSON.parse(raw); |
| profiles.push({ |
| id: file.replace(".json", ""), |
| name: profile.name, |
| authLabel: profile.authLabel || "unknown", |
| createdAt: profile.createdAt, |
| hasConfig: !!profile.configToml, |
| hasAuth: !!profile.authJson, |
| }); |
| } catch { |
| |
| } |
| } |
|
|
| |
| profiles.sort((a, b) => a.name.localeCompare(b.name)); |
| return NextResponse.json({ profiles }); |
| } catch (error) { |
| console.log("Error listing codex profiles:", error.message); |
| return NextResponse.json({ error: "Failed to list profiles" }, { status: 500 }); |
| } |
| } |
|
|
| |
| export async function POST(request) { |
| try { |
| const writeGuard = ensureCliConfigWriteAllowed(); |
| if (writeGuard) { |
| return NextResponse.json({ error: writeGuard }, { status: 403 }); |
| } |
|
|
| const { name } = await request.json(); |
|
|
| if (!name || typeof name !== "string" || !name.trim()) { |
| return NextResponse.json({ error: "Profile name is required" }, { status: 400 }); |
| } |
|
|
| const paths = getCliConfigPaths("codex"); |
| if (!paths) { |
| return NextResponse.json({ error: "Codex config paths not found" }, { status: 500 }); |
| } |
|
|
| |
| let configToml = null; |
| let authJson = null; |
|
|
| try { |
| configToml = await fs.readFile(paths.config, "utf-8"); |
| } catch { |
| |
| } |
|
|
| try { |
| authJson = await fs.readFile(paths.auth, "utf-8"); |
| } catch { |
| |
| } |
|
|
| if (!configToml && !authJson) { |
| return NextResponse.json( |
| { error: "No Codex configuration files found to save" }, |
| { status: 400 } |
| ); |
| } |
|
|
| const profileId = name |
| .trim() |
| .toLowerCase() |
| .replace(/[^a-z0-9]+/g, "-") |
| .replace(/^-|-$/g, ""); |
|
|
| const profile = { |
| name: name.trim(), |
| createdAt: new Date().toISOString(), |
| authLabel: authJson ? extractAuthLabel(authJson) : "no-auth", |
| configToml, |
| authJson, |
| }; |
|
|
| await ensureProfilesDir(); |
| const profilePath = path.join(PROFILES_DIR, `${profileId}.json`); |
| await fs.writeFile(profilePath, JSON.stringify(profile, null, 2)); |
|
|
| return NextResponse.json({ |
| success: true, |
| message: `Profile "${name}" saved successfully`, |
| profileId, |
| }); |
| } catch (error) { |
| console.log("Error saving codex profile:", error.message); |
| return NextResponse.json({ error: "Failed to save profile" }, { status: 500 }); |
| } |
| } |
|
|
| |
| export async function PUT(request) { |
| try { |
| const writeGuard = ensureCliConfigWriteAllowed(); |
| if (writeGuard) { |
| return NextResponse.json({ error: writeGuard }, { status: 403 }); |
| } |
|
|
| const { profileId } = await request.json(); |
|
|
| if (!profileId) { |
| return NextResponse.json({ error: "profileId is required" }, { status: 400 }); |
| } |
|
|
| const profilePath = path.join(PROFILES_DIR, `${profileId}.json`); |
| let profile; |
| try { |
| const raw = await fs.readFile(profilePath, "utf-8"); |
| profile = JSON.parse(raw); |
| } catch { |
| return NextResponse.json({ error: `Profile "${profileId}" not found` }, { status: 404 }); |
| } |
|
|
| const paths = getCliConfigPaths("codex"); |
| if (!paths) { |
| return NextResponse.json({ error: "Codex config paths not found" }, { status: 500 }); |
| } |
|
|
| |
| const { createMultiBackup } = await import("@/shared/services/backupService"); |
| await createMultiBackup("codex", [paths.config, paths.auth]); |
|
|
| |
| await fs.mkdir(path.dirname(paths.config), { recursive: true }); |
|
|
| |
| if (profile.configToml) { |
| await fs.writeFile(paths.config, profile.configToml); |
| } |
| if (profile.authJson) { |
| await fs.writeFile(paths.auth, profile.authJson); |
| } |
|
|
| return NextResponse.json({ |
| success: true, |
| message: `Profile "${profile.name}" activated`, |
| profileId, |
| restoredConfig: !!profile.configToml, |
| restoredAuth: !!profile.authJson, |
| }); |
| } catch (error) { |
| console.log("Error activating codex profile:", error.message); |
| return NextResponse.json({ error: "Failed to activate profile" }, { status: 500 }); |
| } |
| } |
|
|
| |
| export async function DELETE(request) { |
| try { |
| const { profileId } = await request.json(); |
|
|
| if (!profileId) { |
| return NextResponse.json({ error: "profileId is required" }, { status: 400 }); |
| } |
|
|
| const profilePath = path.join(PROFILES_DIR, `${profileId}.json`); |
| try { |
| await fs.unlink(profilePath); |
| } catch (err) { |
| if (err.code === "ENOENT") { |
| return NextResponse.json({ error: `Profile "${profileId}" not found` }, { status: 404 }); |
| } |
| throw err; |
| } |
|
|
| return NextResponse.json({ |
| success: true, |
| message: `Profile "${profileId}" deleted`, |
| }); |
| } catch (error) { |
| console.log("Error deleting codex profile:", error.message); |
| return NextResponse.json({ error: "Failed to delete profile" }, { status: 500 }); |
| } |
| } |
|
|