File size: 6,968 Bytes
5448d8b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import { createPrompt, printHeading, printInfo, printSuccess } from "../io.mjs";
import { openOmniRouteDb } from "../sqlite.mjs";
import { getSettings, hashManagementPassword, updateSettings } from "../settings-store.mjs";
import { testProviderApiKey } from "../provider-test.mjs";
import { updateProviderTestResult, upsertApiKeyProviderConnection } from "../provider-store.mjs";
import {
formatProviderChoices,
getProviderDisplayName,
resolveProviderChoice,
} from "../provider-catalog.mjs";
import { t } from "../i18n.mjs";
const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
async function getListCliTools() {
const { listCliTools } = await import(`${PROJECT_ROOT}/src/shared/constants/cliTools.ts`);
return listCliTools;
}
function wantsProviderSetup(opts) {
return opts.addProvider || Boolean(opts.provider) || Boolean(opts.apiKey);
}
async function resolvePassword(opts, prompt, nonInteractive) {
if (opts.password) return opts.password;
if (nonInteractive) return "";
const answer = await prompt.ask("Set an admin password now? [y/N]", "N");
if (!/^y(es)?$/i.test(answer)) return "";
const password = await prompt.askSecret("Admin password");
const confirm = await prompt.askSecret("Confirm password");
if (password !== confirm) {
throw new Error("Passwords do not match.");
}
return password;
}
async function setupPassword(db, opts, prompt, nonInteractive) {
const password = await resolvePassword(opts, prompt, nonInteractive);
if (!password) {
const settings = getSettings(db);
if (!settings.password) {
updateSettings(db, { requireLogin: false });
}
if (!nonInteractive) {
printInfo("Password setup skipped. Dashboard login remains disabled.");
}
return false;
}
if (password.length < 8) {
throw new Error("Password must be at least 8 characters.");
}
const hashedPassword = await hashManagementPassword(password);
updateSettings(db, {
password: hashedPassword,
requireLogin: true,
});
printSuccess("Admin password configured");
return true;
}
async function resolveProviderInput(opts, prompt, nonInteractive) {
let provider = opts.provider;
let apiKey = opts.apiKey;
let name = opts.providerName;
const defaultModel = opts.defaultModel;
const baseUrl = opts.providerBaseUrl;
if (!provider && !nonInteractive) {
console.log("Choose a provider:");
console.log(formatProviderChoices());
provider = resolveProviderChoice(await prompt.ask("Provider", "1"));
}
provider = provider || "openai";
if (!apiKey && !nonInteractive) {
apiKey = await prompt.ask(`${getProviderDisplayName(provider)} API key`);
}
if (!apiKey) {
throw new Error("Provider API key is required. Pass --api-key or OMNIROUTE_API_KEY.");
}
if (!name) {
name = getProviderDisplayName(provider);
}
return {
provider,
apiKey,
name,
defaultModel: defaultModel || null,
providerSpecificData: baseUrl ? { baseUrl } : null,
};
}
async function setupProvider(db, opts, prompt, nonInteractive) {
if (!wantsProviderSetup(opts) && nonInteractive) return null;
if (!wantsProviderSetup(opts)) {
const answer = await prompt.ask("Add your first provider now? [Y/n]", "Y");
if (/^n(o)?$/i.test(answer)) return null;
}
const input = await resolveProviderInput(opts, prompt, nonInteractive);
const connection = upsertApiKeyProviderConnection(db, input);
printSuccess(`Provider configured: ${connection.name}`);
if (opts.testProvider) {
printInfo(`Testing provider connection: ${connection.provider}`);
const result = await testProviderApiKey({
provider: input.provider,
apiKey: input.apiKey,
defaultModel: input.defaultModel,
baseUrl: input.providerSpecificData?.baseUrl || null,
});
updateProviderTestResult(db, connection.id, result);
if (result.valid) {
printSuccess("Provider test passed");
} else {
printInfo(`Provider test failed: ${result.error || "unknown error"}`);
}
}
return connection;
}
export function registerSetup(program) {
program
.command("setup")
.description(t("setup.title"))
.option("--password <value>", "Set admin password")
.option("--add-provider", "Add an API-key provider connection")
.option("--provider <id>", "Provider id, for example openai or anthropic")
.option("--provider-name <name>", "Display name for the connection")
.option("--api-key <value>", "Provider API key")
.option("--default-model <model>", "Optional default model")
.option("--provider-base-url <url>", "Optional OpenAI-compatible base URL override")
.option("--test-provider", "Test the provider after saving it")
.option("--non-interactive", "Read all inputs from flags/env and do not prompt")
.option("--list", "List all supported CLI tools")
.action(async (opts, cmd) => {
const globalOpts = cmd.optsWithGlobals();
const exitCode = await runSetupCommand({ ...opts, output: globalOpts.output });
if (exitCode !== 0) process.exit(exitCode);
});
}
export async function runSetupCommand(opts = {}) {
if (opts.list) {
const listCliTools = await getListCliTools();
const tools = listCliTools();
if (opts.json || opts.output === "json") {
console.log(JSON.stringify(tools, null, 2));
} else {
printHeading("Supported CLI Tools");
for (const tool of tools) {
const cmd = tool.defaultCommand || tool.defaultCommands?.[0] || "";
const cmdStr = cmd ? ` \x1b[2m(${cmd})\x1b[0m` : "";
console.log(` • ${tool.name}${cmdStr}`);
}
}
return 0;
}
const nonInteractive = opts.nonInteractive ?? false;
const prompt = createPrompt();
try {
printHeading("OmniRoute Setup");
const { db, dbPath } = await openOmniRouteDb();
printInfo(`Database: ${dbPath}`);
const before = getSettings(db);
const passwordChanged = await setupPassword(db, opts, prompt, nonInteractive);
const providerConnection = await setupProvider(db, opts, prompt, nonInteractive);
updateSettings(db, { setupComplete: true });
const after = getSettings(db);
db.close();
console.log("");
printSuccess("Setup complete");
printInfo(
`Login: ${after.requireLogin === true ? "enabled" : "disabled"}${
passwordChanged ? " (password updated)" : ""
}`
);
if (providerConnection) {
printInfo(`Provider: ${providerConnection.provider} (${providerConnection.name})`);
} else if (!before.setupComplete) {
printInfo("Provider: skipped");
}
return 0;
} finally {
prompt.close();
}
}
|