mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 13:52:09 +03:00
- tests/unit/custom-cli-config.test.ts: fix ERR_MODULE_NOT_FOUND — stale import path cli-tools → cli-code (regression from F8 git mv, missed by F10 audit because it only ran curated test subset).
- tests/unit/ui/CliAgentsPage.test.tsx: update vi.mock path to current cli-code location (was no-op mock pointing to deleted path).
- tests/unit/ui/CliToolCard.test.tsx: update URL strings /dashboard/cli-tools/claude → /dashboard/cli-code/claude (cosmetic alignment with new routes).
- src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx: remove dead case "cliproxyapi" + unused import (no entry in CLI_TOOLS catalog).
- src/app/(dashboard)/dashboard/cli-agents/CliAgentsPageClient.tsx: replace inline div skeleton with shared <CardSkeleton /> for visual consistency with CliCodePageClient.
- src/app/api/cli-tools/{forge,jcode,deepseek-tui,smelt,pi}-settings/route.ts: replace catch (err: any) with catch (err) + (err as NodeJS.ErrnoException).code narrowing (8 instances, eliminates 8 of 11 implicit-any introductions).
Validated: custom-cli-config.test.ts now 3/3 PASS (was 0/1 FAIL with ERR_MODULE_NOT_FOUND); F1/F3 tests 147/147 PASS; UI tests 25/25 PASS; typecheck:core + noimplicit clean.
230 lines
6.8 KiB
TypeScript
230 lines
6.8 KiB
TypeScript
"use server";
|
|
|
|
import { NextResponse } from "next/server";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
|
|
import {
|
|
ensureCliConfigWriteAllowed,
|
|
getCliPrimaryConfigPath,
|
|
getCliRuntimeStatus,
|
|
} from "@/shared/services/cliRuntime";
|
|
import { createBackup } from "@/shared/services/backupService";
|
|
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
|
|
import { cliModelConfigSchema } from "@/shared/validation/schemas";
|
|
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
|
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
|
|
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
|
|
|
const TOOL_ID = "jcode";
|
|
|
|
const getJcodeConfigPath = (): string =>
|
|
getCliPrimaryConfigPath(TOOL_ID) ?? path.join(process.env.HOME ?? "~", ".jcode", "config.json");
|
|
|
|
const getJcodeDir = () => path.dirname(getJcodeConfigPath());
|
|
|
|
/**
|
|
* Check if the config file contains OmniRoute settings.
|
|
*/
|
|
const hasOmniRouteConfig = (settings: Record<string, unknown> | null): boolean => {
|
|
if (!settings) return false;
|
|
return (
|
|
typeof settings.baseUrl === "string" &&
|
|
settings.baseUrl.length > 0 &&
|
|
settings._managedBy === "omniroute"
|
|
);
|
|
};
|
|
|
|
// Read current config.json
|
|
const readConfig = async (): Promise<Record<string, unknown> | null> => {
|
|
try {
|
|
const content = await fs.readFile(getJcodeConfigPath(), "utf-8");
|
|
return JSON.parse(content) as Record<string, unknown>;
|
|
} catch (err) {
|
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
// GET — check jcode CLI and return current config
|
|
export async function GET(request: Request) {
|
|
const authError = await requireCliToolsAuth(request);
|
|
if (authError) return authError;
|
|
|
|
try {
|
|
const runtime = await getCliRuntimeStatus(TOOL_ID);
|
|
|
|
if (!runtime.installed || !runtime.runnable) {
|
|
return NextResponse.json({
|
|
installed: runtime.installed,
|
|
runnable: runtime.runnable,
|
|
command: runtime.command,
|
|
commandPath: runtime.commandPath,
|
|
runtimeMode: runtime.runtimeMode,
|
|
reason: runtime.reason,
|
|
config: null,
|
|
message:
|
|
runtime.installed && !runtime.runnable
|
|
? "jcode CLI is installed but not runnable"
|
|
: "jcode CLI is not installed",
|
|
});
|
|
}
|
|
|
|
const config = await readConfig();
|
|
|
|
return NextResponse.json({
|
|
installed: runtime.installed,
|
|
runnable: runtime.runnable,
|
|
command: runtime.command,
|
|
commandPath: runtime.commandPath,
|
|
runtimeMode: runtime.runtimeMode,
|
|
reason: runtime.reason,
|
|
config,
|
|
hasOmniRoute: hasOmniRouteConfig(config),
|
|
configPath: getJcodeConfigPath(),
|
|
});
|
|
} catch (err) {
|
|
return NextResponse.json(
|
|
{ error: { message: sanitizeErrorMessage(err) } },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// POST — write OmniRoute settings to jcode config.json
|
|
export async function POST(request: Request) {
|
|
const authError = await requireCliToolsAuth(request);
|
|
if (authError) return authError;
|
|
|
|
let rawBody;
|
|
try {
|
|
rawBody = await request.json();
|
|
} catch {
|
|
return NextResponse.json(
|
|
{ error: { message: "Invalid JSON body" } },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
try {
|
|
const writeGuard = ensureCliConfigWriteAllowed();
|
|
if (writeGuard) {
|
|
return NextResponse.json({ error: writeGuard }, { status: 403 });
|
|
}
|
|
|
|
// Extract keyId BEFORE Zod validation — Zod strips unknown fields
|
|
const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
|
|
|
|
const validation = validateBody(cliModelConfigSchema, rawBody);
|
|
if (isValidationFailure(validation)) {
|
|
return NextResponse.json({ error: validation.error }, { status: 400 });
|
|
}
|
|
const { baseUrl, model } = validation.data;
|
|
const apiKey = await resolveApiKey(keyId, validation.data.apiKey);
|
|
|
|
const configPath = getJcodeConfigPath();
|
|
const jcodeDir = getJcodeDir();
|
|
|
|
// Ensure directory exists
|
|
await fs.mkdir(jcodeDir, { recursive: true });
|
|
|
|
// Backup current config before modifying
|
|
await createBackup(TOOL_ID, configPath);
|
|
|
|
// Read existing config or start fresh
|
|
let existing: Record<string, unknown> = {};
|
|
try {
|
|
const raw = await fs.readFile(configPath, "utf-8");
|
|
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
} catch {
|
|
/* No existing config */
|
|
}
|
|
|
|
// Merge OmniRoute settings (jcode uses OpenAI-compatible config)
|
|
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
const updated: Record<string, unknown> = {
|
|
...existing,
|
|
baseUrl: normalizedBaseUrl,
|
|
apiKey,
|
|
model,
|
|
_managedBy: "omniroute",
|
|
};
|
|
|
|
await fs.writeFile(configPath, JSON.stringify(updated, null, 2), "utf-8");
|
|
|
|
// Persist last-configured timestamp
|
|
try {
|
|
saveCliToolLastConfigured(TOOL_ID);
|
|
} catch {
|
|
/* non-critical */
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: "jcode settings applied successfully!",
|
|
configPath,
|
|
});
|
|
} catch (err) {
|
|
return NextResponse.json(
|
|
{ error: { message: sanitizeErrorMessage(err) } },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// DELETE — remove OmniRoute settings from jcode config
|
|
export async function DELETE(request: Request) {
|
|
const authError = await requireCliToolsAuth(request);
|
|
if (authError) return authError;
|
|
|
|
try {
|
|
const writeGuard = ensureCliConfigWriteAllowed();
|
|
if (writeGuard) {
|
|
return NextResponse.json({ error: writeGuard }, { status: 403 });
|
|
}
|
|
|
|
const configPath = getJcodeConfigPath();
|
|
|
|
// Backup before modifying
|
|
await createBackup(TOOL_ID, configPath);
|
|
|
|
// Read existing config
|
|
let existing: Record<string, unknown> = {};
|
|
try {
|
|
const raw = await fs.readFile(configPath, "utf-8");
|
|
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
} catch (err) {
|
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
return NextResponse.json({ success: true, message: "No config file to reset" });
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
// Remove OmniRoute-managed fields
|
|
delete existing.baseUrl;
|
|
delete existing.apiKey;
|
|
delete existing.model;
|
|
delete existing._managedBy;
|
|
|
|
if (Object.keys(existing).length === 0) {
|
|
await fs.rm(configPath, { force: true });
|
|
} else {
|
|
await fs.writeFile(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
}
|
|
|
|
// Clear last-configured timestamp
|
|
try {
|
|
deleteCliToolLastConfigured(TOOL_ID);
|
|
} catch {
|
|
/* non-critical */
|
|
}
|
|
|
|
return NextResponse.json({ success: true, message: "jcode OmniRoute settings removed" });
|
|
} catch (err) {
|
|
return NextResponse.json(
|
|
{ error: { message: sanitizeErrorMessage(err) } },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|