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();
  }
}