feat(cli-tools): add dynamic configuration for Pi, OMP, Crush, ForgeCode, Smelt and CodeWhale
- Add dedicated settings API routes for pi, omp, crush, forge, smelt, codewhale - Integrate GenericCliToolCard with multi-model support for Pi and auto-discovery for OMP - Register tools in cliTools catalog and all-statuses route - Add official logos for all new CLI tools Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"use server";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const getSmeltConfigPath = () => path.join(os.homedir(), ".smelt", "config.json");
|
||||
const getSmeltDir = () => path.dirname(getSmeltConfigPath());
|
||||
|
||||
const checkSmeltInstalled = async () => {
|
||||
const isWindows = os.platform() === "win32";
|
||||
try {
|
||||
const command = isWindows ? "where smelt" : "which smelt";
|
||||
await execAsync(command, { windowsHide: true });
|
||||
return true;
|
||||
} catch {
|
||||
try {
|
||||
await fs.access(getSmeltConfigPath());
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const has9RouterConfig = (settings) => {
|
||||
if (!settings) return false;
|
||||
return (
|
||||
settings._managedBy === "9router" ||
|
||||
(typeof settings.baseUrl === "string" && settings.baseUrl.length > 0 && settings.baseUrl.includes("20128"))
|
||||
);
|
||||
};
|
||||
|
||||
const readConfig = async () => {
|
||||
try {
|
||||
const content = await fs.readFile(getSmeltConfigPath(), "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const installed = await checkSmeltInstalled();
|
||||
if (!installed) {
|
||||
return NextResponse.json({
|
||||
installed: false,
|
||||
config: null,
|
||||
message: "Smelt CLI is not installed",
|
||||
});
|
||||
}
|
||||
|
||||
const config = await readConfig();
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getSmeltConfigPath(),
|
||||
});
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: { message: err.message } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request) {
|
||||
let rawBody;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: { message: "Invalid JSON body" } }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { baseUrl, apiKey, model } = rawBody || {};
|
||||
if (!baseUrl) {
|
||||
return NextResponse.json({ error: { message: "baseUrl is required" } }, { status: 400 });
|
||||
}
|
||||
|
||||
const configPath = getSmeltConfigPath();
|
||||
await fs.mkdir(getSmeltDir(), { recursive: true });
|
||||
|
||||
let existing = {};
|
||||
try {
|
||||
const raw = await fs.readFile(configPath, "utf-8");
|
||||
existing = JSON.parse(raw);
|
||||
} catch {}
|
||||
|
||||
const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
||||
const updated = {
|
||||
...existing,
|
||||
baseUrl: normalizedBaseUrl,
|
||||
apiKey: apiKey || "sk_9router",
|
||||
model: model || existing.model || "provider/model-id",
|
||||
_managedBy: "9router",
|
||||
};
|
||||
|
||||
await fs.writeFile(configPath, JSON.stringify(updated, null, 2), "utf-8");
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Smelt settings applied successfully!",
|
||||
configPath,
|
||||
});
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: { message: err.message } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE() {
|
||||
try {
|
||||
const configPath = getSmeltConfigPath();
|
||||
let existing = {};
|
||||
try {
|
||||
const raw = await fs.readFile(configPath, "utf-8");
|
||||
existing = JSON.parse(raw);
|
||||
} catch {
|
||||
return NextResponse.json({ success: true, message: "No config file to reset" });
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, message: "Smelt 9Router settings removed" });
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: { message: err.message } }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user