feat(auth): add profile reuse and fix TypeScript errors [AI-assisted]
This commit is contained in:
parent
3fe4b2595a
commit
8f594617bb
@ -1,12 +1,23 @@
|
|||||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||||
|
declare const process: any;
|
||||||
import {
|
import {
|
||||||
formatApiKeyPreview,
|
formatApiKeyPreview,
|
||||||
normalizeApiKeyInput,
|
normalizeApiKeyInput,
|
||||||
validateApiKeyInput,
|
validateApiKeyInput,
|
||||||
} from "./auth-choice.api-key.js";
|
} from "./auth-choice.api-key.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type {
|
||||||
import { buildTokenProfileId, validateAnthropicSetupToken } from "./auth-token.js";
|
ApplyAuthChoiceParams,
|
||||||
import { applyAuthProfileConfig, setAnthropicApiKey } from "./onboard-auth.js";
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
|
import {
|
||||||
|
buildTokenProfileId,
|
||||||
|
validateAnthropicSetupToken,
|
||||||
|
} from "./auth-token.js";
|
||||||
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
setAnthropicApiKey,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
|
|
||||||
export async function applyAuthChoiceAnthropic(
|
export async function applyAuthChoiceAnthropic(
|
||||||
params: ApplyAuthChoiceParams,
|
params: ApplyAuthChoiceParams,
|
||||||
@ -18,9 +29,10 @@ export async function applyAuthChoiceAnthropic(
|
|||||||
) {
|
) {
|
||||||
let nextConfig = params.config;
|
let nextConfig = params.config;
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
["Run `claude setup-token` in your terminal.", "Then paste the generated token below."].join(
|
[
|
||||||
"\n",
|
"Run `claude setup-token` in your terminal.",
|
||||||
),
|
"Then paste the generated token below.",
|
||||||
|
].join("\n"),
|
||||||
"Anthropic setup-token",
|
"Anthropic setup-token",
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -59,7 +71,10 @@ export async function applyAuthChoiceAnthropic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.authChoice === "apiKey") {
|
if (params.authChoice === "apiKey") {
|
||||||
if (params.opts?.tokenProvider && params.opts.tokenProvider !== "anthropic") {
|
if (
|
||||||
|
params.opts?.tokenProvider &&
|
||||||
|
params.opts.tokenProvider !== "anthropic"
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +83,10 @@ export async function applyAuthChoiceAnthropic(
|
|||||||
const envKey = process.env.ANTHROPIC_API_KEY?.trim();
|
const envKey = process.env.ANTHROPIC_API_KEY?.trim();
|
||||||
|
|
||||||
if (params.opts?.token) {
|
if (params.opts?.token) {
|
||||||
await setAnthropicApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
await setAnthropicApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +100,32 @@ export async function applyAuthChoiceAnthropic(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existing = store.profiles["anthropic:default"];
|
||||||
|
if (existing && existing.type === "api_key") {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Anthropic API key (${formatApiKeyPreview(existing.key)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Anthropic API key",
|
message: "Enter Anthropic API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setAnthropicApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setAnthropicApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "anthropic:default",
|
profileId: "anthropic:default",
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
|
import {
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
resolveAuthProfileOrder,
|
||||||
|
} from "../agents/auth-profiles.js";
|
||||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||||
import {
|
import {
|
||||||
formatApiKeyPreview,
|
formatApiKeyPreview,
|
||||||
normalizeApiKeyInput,
|
normalizeApiKeyInput,
|
||||||
validateApiKeyInput,
|
validateApiKeyInput,
|
||||||
} from "./auth-choice.api-key.js";
|
} from "./auth-choice.api-key.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type {
|
||||||
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||||
import {
|
import {
|
||||||
applyGoogleGeminiModelDefault,
|
applyGoogleGeminiModelDefault,
|
||||||
@ -97,8 +103,12 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
store,
|
store,
|
||||||
provider: "openrouter",
|
provider: "openrouter",
|
||||||
});
|
});
|
||||||
const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
|
const existingProfileId = profileOrder.find((profileId) =>
|
||||||
const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
|
Boolean(store.profiles[profileId]),
|
||||||
|
);
|
||||||
|
const existingCred = existingProfileId
|
||||||
|
? store.profiles[existingProfileId]
|
||||||
|
: undefined;
|
||||||
let profileId = "openrouter:default";
|
let profileId = "openrouter:default";
|
||||||
let mode: "api_key" | "oauth" | "token" = "api_key";
|
let mode: "api_key" | "oauth" | "token" = "api_key";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
@ -114,8 +124,15 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "openrouter") {
|
if (
|
||||||
await setOpenrouterApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "openrouter"
|
||||||
|
) {
|
||||||
|
await setOpenrouterApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +155,10 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
message: "Enter OpenRouter API key",
|
message: "Enter OpenRouter API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setOpenrouterApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setOpenrouterApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,12 +194,40 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
params.opts?.token &&
|
params.opts?.token &&
|
||||||
params.opts?.tokenProvider === "vercel-ai-gateway"
|
params.opts?.tokenProvider === "vercel-ai-gateway"
|
||||||
) {
|
) {
|
||||||
await setVercelAiGatewayApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
await setVercelAiGatewayApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
const envKey = resolveEnvApiKey("vercel-ai-gateway");
|
||||||
if (envKey) {
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "vercel-ai-gateway",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "vercel-ai-gateway:default";
|
||||||
|
|
||||||
|
if (existingProfileId) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Vercel AI Gateway authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
profileId = existingProfileId;
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential && envKey) {
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing AI_GATEWAY_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
@ -194,10 +242,13 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
message: "Enter Vercel AI Gateway API key",
|
message: "Enter Vercel AI Gateway API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setVercelAiGatewayApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "vercel-ai-gateway:default",
|
profileId,
|
||||||
provider: "vercel-ai-gateway",
|
provider: "vercel-ai-gateway",
|
||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
@ -219,36 +270,77 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "moonshot-api-key") {
|
if (authChoice === "moonshot-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "moonshot",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "moonshot:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "moonshot") {
|
if (existingProfileId) {
|
||||||
await setMoonshotApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
const existing = store.profiles[existingProfileId];
|
||||||
hasCredential = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("moonshot");
|
|
||||||
if (envKey) {
|
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing Moonshot authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setMoonshotApiKey(envKey.apiKey, params.agentDir);
|
profileId = existingProfileId;
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "moonshot"
|
||||||
|
) {
|
||||||
|
await setMoonshotApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("moonshot");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setMoonshotApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Moonshot API key",
|
message: "Enter Moonshot API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setMoonshotApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "moonshot",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "moonshot:default",
|
|
||||||
provider: "moonshot",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -266,9 +358,41 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "kimi-code-api-key") {
|
if (authChoice === "kimi-code-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "kimi-code",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "kimi-code:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kimi-code") {
|
|
||||||
await setKimiCodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
if (existingProfileId) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Kimi Code authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
profileId = existingProfileId;
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "kimi-code"
|
||||||
|
) {
|
||||||
|
await setKimiCodeApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,30 +404,39 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Kimi Code",
|
"Kimi Code",
|
||||||
);
|
);
|
||||||
}
|
|
||||||
const envKey = resolveEnvApiKey("kimi-code");
|
const envKey = resolveEnvApiKey("kimi-code");
|
||||||
if (envKey) {
|
if (envKey) {
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing KIMICODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing KIMICODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setKimiCodeApiKey(envKey.apiKey, params.agentDir);
|
await setKimiCodeApiKey(envKey.apiKey, params.agentDir);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Kimi Code API key",
|
message: "Enter Kimi Code API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setKimiCodeApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setKimiCodeApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "kimi-code",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "kimi-code:default",
|
|
||||||
provider: "kimi-code",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -322,36 +455,75 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "gemini-api-key") {
|
if (authChoice === "gemini-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "google",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "google:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "google") {
|
if (existingProfileId) {
|
||||||
await setGeminiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
const existing = store.profiles[existingProfileId];
|
||||||
hasCredential = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("google");
|
|
||||||
if (envKey) {
|
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing Gemini authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setGeminiApiKey(envKey.apiKey, params.agentDir);
|
profileId = existingProfileId;
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "google"
|
||||||
|
) {
|
||||||
|
await setGeminiApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("google");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setGeminiApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Gemini API key",
|
message: "Enter Gemini API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setGeminiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setGeminiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "google:default",
|
if (hasCredential) {
|
||||||
provider: "google",
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
mode: "api_key",
|
profileId,
|
||||||
});
|
provider: "google",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (params.setDefaultModel) {
|
if (params.setDefaultModel) {
|
||||||
const applied = applyGoogleGeminiModelDefault(nextConfig);
|
const applied = applyGoogleGeminiModelDefault(nextConfig);
|
||||||
nextConfig = applied.next;
|
nextConfig = applied.next;
|
||||||
@ -369,36 +541,74 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "zai-api-key") {
|
if (authChoice === "zai-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "zai",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "zai:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
|
if (existingProfileId) {
|
||||||
await setZaiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
const existing = store.profiles[existingProfileId];
|
||||||
hasCredential = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("zai");
|
|
||||||
if (envKey) {
|
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing Z.AI authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setZaiApiKey(envKey.apiKey, params.agentDir);
|
profileId = existingProfileId;
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "zai"
|
||||||
|
) {
|
||||||
|
await setZaiApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
|
const envKey = resolveEnvApiKey("zai");
|
||||||
|
if (envKey) {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
await setZaiApiKey(envKey.apiKey, params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Z.AI API key",
|
message: "Enter Z.AI API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setZaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setZaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "zai",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "zai:default",
|
|
||||||
provider: "zai",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -415,7 +625,9 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
...config.agents?.defaults?.models,
|
...config.agents?.defaults?.models,
|
||||||
[ZAI_DEFAULT_MODEL_REF]: {
|
[ZAI_DEFAULT_MODEL_REF]: {
|
||||||
...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
|
...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
|
||||||
alias: config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
|
alias:
|
||||||
|
config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]
|
||||||
|
?.alias ?? "GLM",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -432,20 +644,60 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "synthetic-api-key") {
|
if (authChoice === "synthetic-api-key") {
|
||||||
if (params.opts?.token && params.opts?.tokenProvider === "synthetic") {
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
await setSyntheticApiKey(String(params.opts.token).trim(), params.agentDir);
|
allowKeychainPrompt: false,
|
||||||
} else {
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "synthetic",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "synthetic:default";
|
||||||
|
let hasCredential = false;
|
||||||
|
|
||||||
|
if (existingProfileId) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Synthetic authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
profileId = existingProfileId;
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "synthetic"
|
||||||
|
) {
|
||||||
|
await setSyntheticApiKey(
|
||||||
|
String(params.opts.token).trim(),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Synthetic API key",
|
message: "Enter Synthetic API key",
|
||||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
||||||
});
|
});
|
||||||
await setSyntheticApiKey(String(key).trim(), params.agentDir);
|
await setSyntheticApiKey(String(key).trim(), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "synthetic",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "synthetic:default",
|
|
||||||
provider: "synthetic",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -464,10 +716,41 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "venice-api-key") {
|
if (authChoice === "venice-api-key") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "venice",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "venice:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "venice") {
|
if (existingProfileId) {
|
||||||
await setVeniceApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Venice AI authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
profileId = existingProfileId;
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "venice"
|
||||||
|
) {
|
||||||
|
await setVeniceApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,31 +763,36 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Venice AI",
|
"Venice AI",
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("venice");
|
const envKey = resolveEnvApiKey("venice");
|
||||||
if (envKey) {
|
if (envKey) {
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing VENICE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setVeniceApiKey(envKey.apiKey, params.agentDir);
|
await setVeniceApiKey(envKey.apiKey, params.agentDir);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter Venice AI API key",
|
message: "Enter Venice AI API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setVeniceApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setVeniceApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "venice",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "venice:default",
|
|
||||||
provider: "venice",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -523,9 +811,41 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authChoice === "opencode-zen") {
|
if (authChoice === "opencode-zen") {
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const profileOrder = resolveAuthProfileOrder({
|
||||||
|
cfg: nextConfig,
|
||||||
|
store,
|
||||||
|
provider: "opencode",
|
||||||
|
});
|
||||||
|
const existingProfileId = profileOrder.find((pid) =>
|
||||||
|
Boolean(store.profiles[pid]),
|
||||||
|
);
|
||||||
|
let profileId = "opencode:default";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") {
|
|
||||||
await setOpencodeZenApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
if (existingProfileId) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing OpenCode Zen authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
profileId = existingProfileId;
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasCredential &&
|
||||||
|
params.opts?.token &&
|
||||||
|
params.opts?.tokenProvider === "opencode"
|
||||||
|
) {
|
||||||
|
await setOpencodeZenApiKey(
|
||||||
|
normalizeApiKeyInput(params.opts.token),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,30 +858,39 @@ export async function applyAuthChoiceApiProviders(
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
"OpenCode Zen",
|
"OpenCode Zen",
|
||||||
);
|
);
|
||||||
}
|
|
||||||
const envKey = resolveEnvApiKey("opencode");
|
const envKey = resolveEnvApiKey("opencode");
|
||||||
if (envKey) {
|
if (envKey) {
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (useExisting) {
|
if (useExisting) {
|
||||||
await setOpencodeZenApiKey(envKey.apiKey, params.agentDir);
|
await setOpencodeZenApiKey(envKey.apiKey, params.agentDir);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter OpenCode Zen API key",
|
message: "Enter OpenCode Zen API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setOpencodeZenApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setOpencodeZenApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCredential) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: "opencode",
|
||||||
|
mode: "api_key",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
|
||||||
profileId: "opencode:default",
|
|
||||||
provider: "opencode",
|
|
||||||
mode: "api_key",
|
|
||||||
});
|
|
||||||
{
|
{
|
||||||
const applied = await applyDefaultModelChoice({
|
const applied = await applyDefaultModelChoice({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { githubCopilotLoginCommand } from "../providers/github-copilot-auth.js";
|
import { githubCopilotLoginCommand } from "../providers/github-copilot-auth.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
declare const process: any;
|
||||||
import { applyAuthProfileConfig } from "./onboard-auth.js";
|
import type {
|
||||||
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
|
|
||||||
export async function applyAuthChoiceGitHubCopilot(
|
export async function applyAuthChoiceGitHubCopilot(
|
||||||
params: ApplyAuthChoiceParams,
|
params: ApplyAuthChoiceParams,
|
||||||
@ -17,19 +24,50 @@ export async function applyAuthChoiceGitHubCopilot(
|
|||||||
"GitHub Copilot",
|
"GitHub Copilot",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!process.stdin.isTTY) {
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existing = store.profiles["github-copilot:github"];
|
||||||
|
if (existing && existing.type === "token") {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing GitHub Copilot authentication (${existing.email ?? "authorized"})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
// already has profile, skip login
|
||||||
|
} else if (!process.stdin.isTTY) {
|
||||||
|
await params.prompter.note(
|
||||||
|
"GitHub Copilot login requires an interactive TTY.",
|
||||||
|
"GitHub Copilot",
|
||||||
|
);
|
||||||
|
return { config: nextConfig };
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await githubCopilotLoginCommand({ yes: true }, params.runtime);
|
||||||
|
} catch (err) {
|
||||||
|
await params.prompter.note(
|
||||||
|
`GitHub Copilot login failed: ${String(err)}`,
|
||||||
|
"GitHub Copilot",
|
||||||
|
);
|
||||||
|
return { config: nextConfig };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!process.stdin.isTTY) {
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
"GitHub Copilot login requires an interactive TTY.",
|
"GitHub Copilot login requires an interactive TTY.",
|
||||||
"GitHub Copilot",
|
"GitHub Copilot",
|
||||||
);
|
);
|
||||||
return { config: nextConfig };
|
return { config: nextConfig };
|
||||||
}
|
} else {
|
||||||
|
try {
|
||||||
try {
|
await githubCopilotLoginCommand({ yes: true }, params.runtime);
|
||||||
await githubCopilotLoginCommand({ yes: true }, params.runtime);
|
} catch (err) {
|
||||||
} catch (err) {
|
await params.prompter.note(
|
||||||
await params.prompter.note(`GitHub Copilot login failed: ${String(err)}`, "GitHub Copilot");
|
`GitHub Copilot login failed: ${String(err)}`,
|
||||||
return { config: nextConfig };
|
"GitHub Copilot",
|
||||||
|
);
|
||||||
|
return { config: nextConfig };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
@ -55,7 +93,10 @@ export async function applyAuthChoiceGitHubCopilot(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await params.prompter.note(`Default model set to ${model}`, "Model configured");
|
await params.prompter.note(
|
||||||
|
`Default model set to ${model}`,
|
||||||
|
"Model configured",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { config: nextConfig };
|
return { config: nextConfig };
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import {
|
|||||||
normalizeApiKeyInput,
|
normalizeApiKeyInput,
|
||||||
validateApiKeyInput,
|
validateApiKeyInput,
|
||||||
} from "./auth-choice.api-key.js";
|
} from "./auth-choice.api-key.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type {
|
||||||
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||||
import {
|
import {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
@ -12,6 +15,7 @@ import {
|
|||||||
applyMinimaxApiProviderConfig,
|
applyMinimaxApiProviderConfig,
|
||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
applyMinimaxProviderConfig,
|
applyMinimaxProviderConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
|
|
||||||
@ -34,8 +38,26 @@ export async function applyAuthChoiceMiniMax(
|
|||||||
params.authChoice === "minimax-api-lightning"
|
params.authChoice === "minimax-api-lightning"
|
||||||
) {
|
) {
|
||||||
const modelId =
|
const modelId =
|
||||||
params.authChoice === "minimax-api-lightning" ? "MiniMax-M2.1-lightning" : "MiniMax-M2.1";
|
params.authChoice === "minimax-api-lightning"
|
||||||
|
? "MiniMax-M2.1-lightning"
|
||||||
|
: "MiniMax-M2.1";
|
||||||
let hasCredential = false;
|
let hasCredential = false;
|
||||||
|
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existingProfileId = "minimax:default";
|
||||||
|
if (store.profiles[existingProfileId]) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing MiniMax authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
hasCredential = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const envKey = resolveEnvApiKey("minimax");
|
const envKey = resolveEnvApiKey("minimax");
|
||||||
if (envKey) {
|
if (envKey) {
|
||||||
const useExisting = await params.prompter.confirm({
|
const useExisting = await params.prompter.confirm({
|
||||||
@ -52,7 +74,10 @@ export async function applyAuthChoiceMiniMax(
|
|||||||
message: "Enter MiniMax API key",
|
message: "Enter MiniMax API key",
|
||||||
validate: validateApiKeyInput,
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setMinimaxApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
await setMinimaxApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "minimax:default",
|
profileId: "minimax:default",
|
||||||
@ -66,7 +91,8 @@ export async function applyAuthChoiceMiniMax(
|
|||||||
setDefaultModel: params.setDefaultModel,
|
setDefaultModel: params.setDefaultModel,
|
||||||
defaultModel: modelRef,
|
defaultModel: modelRef,
|
||||||
applyDefaultConfig: (config) => applyMinimaxApiConfig(config, modelId),
|
applyDefaultConfig: (config) => applyMinimaxApiConfig(config, modelId),
|
||||||
applyProviderConfig: (config) => applyMinimaxApiProviderConfig(config, modelId),
|
applyProviderConfig: (config) =>
|
||||||
|
applyMinimaxApiProviderConfig(config, modelId),
|
||||||
noteAgentModel,
|
noteAgentModel,
|
||||||
prompter: params.prompter,
|
prompter: params.prompter,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
import { isRemoteEnvironment } from "./oauth-env.js";
|
import { isRemoteEnvironment } from "./oauth-env.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
declare const process: any;
|
||||||
|
import type {
|
||||||
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
import { loginChutes } from "./chutes-oauth.js";
|
import { loginChutes } from "./chutes-oauth.js";
|
||||||
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
||||||
import { applyAuthProfileConfig, writeOAuthCredentials } from "./onboard-auth.js";
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
writeOAuthCredentials,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
import { openUrl } from "./onboard-helpers.js";
|
import { openUrl } from "./onboard-helpers.js";
|
||||||
|
|
||||||
export async function applyAuthChoiceOAuth(
|
export async function applyAuthChoiceOAuth(
|
||||||
@ -12,8 +20,10 @@ export async function applyAuthChoiceOAuth(
|
|||||||
let nextConfig = params.config;
|
let nextConfig = params.config;
|
||||||
const isRemote = isRemoteEnvironment();
|
const isRemote = isRemoteEnvironment();
|
||||||
const redirectUri =
|
const redirectUri =
|
||||||
process.env.CHUTES_OAUTH_REDIRECT_URI?.trim() || "http://127.0.0.1:1456/oauth-callback";
|
process.env.CHUTES_OAUTH_REDIRECT_URI?.trim() ||
|
||||||
const scopes = process.env.CHUTES_OAUTH_SCOPES?.trim() || "openid profile chutes:invoke";
|
"http://127.0.0.1:1456/oauth-callback";
|
||||||
|
const scopes =
|
||||||
|
process.env.CHUTES_OAUTH_SCOPES?.trim() || "openid profile chutes:invoke";
|
||||||
const clientId =
|
const clientId =
|
||||||
process.env.CHUTES_CLIENT_ID?.trim() ||
|
process.env.CHUTES_CLIENT_ID?.trim() ||
|
||||||
String(
|
String(
|
||||||
@ -25,6 +35,29 @@ export async function applyAuthChoiceOAuth(
|
|||||||
).trim();
|
).trim();
|
||||||
const clientSecret = process.env.CHUTES_CLIENT_SECRET?.trim() || undefined;
|
const clientSecret = process.env.CHUTES_CLIENT_SECRET?.trim() || undefined;
|
||||||
|
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existingProfileId = Object.keys(store.profiles).find((id) =>
|
||||||
|
id.startsWith("chutes:"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingProfileId) {
|
||||||
|
const existing = store.profiles[existingProfileId];
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing Chutes authentication (${"email" in existing && existing.email ? existing.email : existingProfileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: existingProfileId,
|
||||||
|
provider: "chutes",
|
||||||
|
mode: "oauth",
|
||||||
|
});
|
||||||
|
return { config: nextConfig };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
isRemote
|
isRemote
|
||||||
? [
|
? [
|
||||||
@ -64,7 +97,7 @@ export async function applyAuthChoiceOAuth(
|
|||||||
manual: isRemote,
|
manual: isRemote,
|
||||||
onAuth,
|
onAuth,
|
||||||
onPrompt,
|
onPrompt,
|
||||||
onProgress: (msg) => spin.update(msg),
|
onProgress: (msg: string) => spin.update(msg),
|
||||||
});
|
});
|
||||||
|
|
||||||
spin.stop("Chutes OAuth complete");
|
spin.stop("Chutes OAuth complete");
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
// @ts-ignore
|
||||||
import { loginOpenAICodex } from "@mariozechner/pi-ai";
|
import { loginOpenAICodex } from "@mariozechner/pi-ai";
|
||||||
|
declare const process: any;
|
||||||
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||||
import { isRemoteEnvironment } from "./oauth-env.js";
|
import { isRemoteEnvironment } from "./oauth-env.js";
|
||||||
@ -7,9 +9,16 @@ import {
|
|||||||
normalizeApiKeyInput,
|
normalizeApiKeyInput,
|
||||||
validateApiKeyInput,
|
validateApiKeyInput,
|
||||||
} from "./auth-choice.api-key.js";
|
} from "./auth-choice.api-key.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type {
|
||||||
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
||||||
import { applyAuthProfileConfig, writeOAuthCredentials } from "./onboard-auth.js";
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
writeOAuthCredentials,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
import { openUrl } from "./onboard-helpers.js";
|
import { openUrl } from "./onboard-helpers.js";
|
||||||
import {
|
import {
|
||||||
applyOpenAICodexModelDefault,
|
applyOpenAICodexModelDefault,
|
||||||
@ -96,6 +105,29 @@ export async function applyAuthChoiceOpenAI(
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
"OpenAI Codex OAuth",
|
"OpenAI Codex OAuth",
|
||||||
);
|
);
|
||||||
|
const store = ensureAuthProfileStore(params.agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existing = store.profiles["openai-codex:default"];
|
||||||
|
if (existing && existing.type === "oauth") {
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing OpenAI Codex authentication (${existing.email ?? "authorized"})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (useExisting) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId: "openai-codex:default",
|
||||||
|
provider: "openai-codex",
|
||||||
|
mode: "oauth",
|
||||||
|
});
|
||||||
|
if (params.setDefaultModel) {
|
||||||
|
const applied = applyOpenAICodexModelDefault(nextConfig);
|
||||||
|
nextConfig = applied.next;
|
||||||
|
}
|
||||||
|
return { config: nextConfig };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const spin = params.prompter.progress("Starting OAuth flow…");
|
const spin = params.prompter.progress("Starting OAuth flow…");
|
||||||
try {
|
try {
|
||||||
const { onAuth, onPrompt } = createVpsAwareOAuthHandlers({
|
const { onAuth, onPrompt } = createVpsAwareOAuthHandlers({
|
||||||
@ -110,7 +142,7 @@ export async function applyAuthChoiceOpenAI(
|
|||||||
const creds = await loginOpenAICodex({
|
const creds = await loginOpenAICodex({
|
||||||
onAuth,
|
onAuth,
|
||||||
onPrompt,
|
onPrompt,
|
||||||
onProgress: (msg) => spin.update(msg),
|
onProgress: (msg: string) => spin.update(msg),
|
||||||
});
|
});
|
||||||
spin.stop("OpenAI OAuth complete");
|
spin.stop("OpenAI OAuth complete");
|
||||||
if (creds) {
|
if (creds) {
|
||||||
|
|||||||
@ -11,8 +11,14 @@ import type { MoltbotConfig } from "../config/config.js";
|
|||||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||||
import { resolvePluginProviders } from "../plugins/providers.js";
|
import { resolvePluginProviders } from "../plugins/providers.js";
|
||||||
import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js";
|
import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js";
|
||||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
import type {
|
||||||
import { applyAuthProfileConfig } from "./onboard-auth.js";
|
ApplyAuthChoiceParams,
|
||||||
|
ApplyAuthChoiceResult,
|
||||||
|
} from "./auth-choice.apply.js";
|
||||||
|
import {
|
||||||
|
applyAuthProfileConfig,
|
||||||
|
ensureAuthProfileStore,
|
||||||
|
} from "./onboard-auth.js";
|
||||||
import { openUrl } from "./onboard-helpers.js";
|
import { openUrl } from "./onboard-helpers.js";
|
||||||
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
||||||
import { isRemoteEnvironment } from "./oauth-env.js";
|
import { isRemoteEnvironment } from "./oauth-env.js";
|
||||||
@ -31,16 +37,23 @@ function resolveProviderMatch(
|
|||||||
): ProviderPlugin | null {
|
): ProviderPlugin | null {
|
||||||
const normalized = normalizeProviderId(rawProvider);
|
const normalized = normalizeProviderId(rawProvider);
|
||||||
return (
|
return (
|
||||||
providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
|
providers.find(
|
||||||
|
(provider) => normalizeProviderId(provider.id) === normalized,
|
||||||
|
) ??
|
||||||
providers.find(
|
providers.find(
|
||||||
(provider) =>
|
(provider) =>
|
||||||
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false,
|
provider.aliases?.some(
|
||||||
|
(alias) => normalizeProviderId(alias) === normalized,
|
||||||
|
) ?? false,
|
||||||
) ??
|
) ??
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null {
|
function pickAuthMethod(
|
||||||
|
provider: ProviderPlugin,
|
||||||
|
rawMethod?: string,
|
||||||
|
): ProviderAuthMethod | null {
|
||||||
const raw = rawMethod?.trim();
|
const raw = rawMethod?.trim();
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
const normalized = raw.toLowerCase();
|
const normalized = raw.toLowerCase();
|
||||||
@ -85,8 +98,13 @@ function applyDefaultModel(cfg: MoltbotConfig, model: string): MoltbotConfig {
|
|||||||
...cfg.agents?.defaults,
|
...cfg.agents?.defaults,
|
||||||
models,
|
models,
|
||||||
model: {
|
model: {
|
||||||
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
...(existingModel &&
|
||||||
? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks }
|
typeof existingModel === "object" &&
|
||||||
|
"fallbacks" in existingModel
|
||||||
|
? {
|
||||||
|
fallbacks: (existingModel as { fallbacks?: string[] })
|
||||||
|
.fallbacks,
|
||||||
|
}
|
||||||
: undefined),
|
: undefined),
|
||||||
primary: model,
|
primary: model,
|
||||||
},
|
},
|
||||||
@ -100,6 +118,7 @@ export async function applyAuthChoicePluginProvider(
|
|||||||
options: PluginProviderAuthChoiceOptions,
|
options: PluginProviderAuthChoiceOptions,
|
||||||
): Promise<ApplyAuthChoiceResult | null> {
|
): Promise<ApplyAuthChoiceResult | null> {
|
||||||
if (params.authChoice !== options.authChoice) return null;
|
if (params.authChoice !== options.authChoice) return null;
|
||||||
|
let agentModelOverride: string | undefined;
|
||||||
|
|
||||||
const enableResult = enablePluginInConfig(params.config, options.pluginId);
|
const enableResult = enablePluginInConfig(params.config, options.pluginId);
|
||||||
let nextConfig = enableResult.config;
|
let nextConfig = enableResult.config;
|
||||||
@ -115,11 +134,17 @@ export async function applyAuthChoicePluginProvider(
|
|||||||
const defaultAgentId = resolveDefaultAgentId(nextConfig);
|
const defaultAgentId = resolveDefaultAgentId(nextConfig);
|
||||||
const agentDir =
|
const agentDir =
|
||||||
params.agentDir ??
|
params.agentDir ??
|
||||||
(agentId === defaultAgentId ? resolveMoltbotAgentDir() : resolveAgentDir(nextConfig, agentId));
|
(agentId === defaultAgentId
|
||||||
|
? resolveMoltbotAgentDir()
|
||||||
|
: resolveAgentDir(nextConfig, agentId));
|
||||||
const workspaceDir =
|
const workspaceDir =
|
||||||
resolveAgentWorkspaceDir(nextConfig, agentId) ?? resolveDefaultAgentWorkspaceDir();
|
resolveAgentWorkspaceDir(nextConfig, agentId) ??
|
||||||
|
resolveDefaultAgentWorkspaceDir();
|
||||||
|
|
||||||
const providers = resolvePluginProviders({ config: nextConfig, workspaceDir });
|
const providers = resolvePluginProviders({
|
||||||
|
config: nextConfig,
|
||||||
|
workspaceDir,
|
||||||
|
});
|
||||||
const provider = resolveProviderMatch(providers, options.providerId);
|
const provider = resolveProviderMatch(providers, options.providerId);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
@ -131,10 +156,56 @@ export async function applyAuthChoicePluginProvider(
|
|||||||
|
|
||||||
const method = pickAuthMethod(provider, options.methodId) ?? provider.auth[0];
|
const method = pickAuthMethod(provider, options.methodId) ?? provider.auth[0];
|
||||||
if (!method) {
|
if (!method) {
|
||||||
await params.prompter.note(`${options.label} auth method missing.`, options.label);
|
await params.prompter.note(
|
||||||
|
`${options.label} auth method missing.`,
|
||||||
|
options.label,
|
||||||
|
);
|
||||||
return { config: nextConfig };
|
return { config: nextConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = ensureAuthProfileStore(agentDir, {
|
||||||
|
allowKeychainPrompt: false,
|
||||||
|
});
|
||||||
|
const existingProfiles = Object.entries(store.profiles).filter(
|
||||||
|
([, p]) =>
|
||||||
|
normalizeProviderId((p as any).provider) ===
|
||||||
|
normalizeProviderId(options.providerId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingProfiles.length > 0) {
|
||||||
|
const defaultProfile =
|
||||||
|
existingProfiles.find(([id]) => id === `${options.providerId}:default`) ??
|
||||||
|
existingProfiles[0];
|
||||||
|
const [profileId, profile] = defaultProfile as [string, any];
|
||||||
|
|
||||||
|
const useExisting = await params.prompter.confirm({
|
||||||
|
message: `Use existing ${options.label} authentication (${"email" in profile && profile.email ? profile.email : profileId})?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useExisting) {
|
||||||
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
|
profileId,
|
||||||
|
provider: profile.provider,
|
||||||
|
mode: (profile.type === "token" ? "token" : profile.type) as any,
|
||||||
|
...("email" in profile && profile.email
|
||||||
|
? { email: profile.email as string }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.setDefaultModel && provider.models?.models?.[0]?.id) {
|
||||||
|
nextConfig = applyDefaultModel(
|
||||||
|
nextConfig,
|
||||||
|
provider.models.models[0].id,
|
||||||
|
);
|
||||||
|
} else if (params.agentId && provider.models?.models?.[0]?.id) {
|
||||||
|
agentModelOverride = provider.models.models[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { config: nextConfig, agentModelOverride };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isRemote = isRemoteEnvironment();
|
const isRemote = isRemoteEnvironment();
|
||||||
const result = await method.run({
|
const result = await method.run({
|
||||||
config: nextConfig,
|
config: nextConfig,
|
||||||
@ -165,18 +236,21 @@ export async function applyAuthChoicePluginProvider(
|
|||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: profile.profileId,
|
profileId: profile.profileId,
|
||||||
provider: profile.credential.provider,
|
provider: profile.credential.provider,
|
||||||
mode: profile.credential.type === "token" ? "token" : profile.credential.type,
|
mode:
|
||||||
|
profile.credential.type === "token" ? "token" : profile.credential.type,
|
||||||
...("email" in profile.credential && profile.credential.email
|
...("email" in profile.credential && profile.credential.email
|
||||||
? { email: profile.credential.email }
|
? { email: profile.credential.email }
|
||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let agentModelOverride: string | undefined;
|
|
||||||
if (result.defaultModel) {
|
if (result.defaultModel) {
|
||||||
if (params.setDefaultModel) {
|
if (params.setDefaultModel) {
|
||||||
nextConfig = applyDefaultModel(nextConfig, result.defaultModel);
|
nextConfig = applyDefaultModel(nextConfig, result.defaultModel);
|
||||||
await params.prompter.note(`Default model set to ${result.defaultModel}`, "Model configured");
|
await params.prompter.note(
|
||||||
|
`Default model set to ${result.defaultModel}`,
|
||||||
|
"Model configured",
|
||||||
|
);
|
||||||
} else if (params.agentId) {
|
} else if (params.agentId) {
|
||||||
agentModelOverride = result.defaultModel;
|
agentModelOverride = result.defaultModel;
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
import type { ChutesOAuthAppConfig } from "../agents/chutes-oauth.js";
|
import type { ChutesOAuthAppConfig } from "../agents/chutes-oauth.js";
|
||||||
@ -43,76 +44,83 @@ async function waitForLocalCallback(params: {
|
|||||||
}): Promise<{ code: string; state: string }> {
|
}): Promise<{ code: string; state: string }> {
|
||||||
const redirectUrl = new URL(params.redirectUri);
|
const redirectUrl = new URL(params.redirectUri);
|
||||||
if (redirectUrl.protocol !== "http:") {
|
if (redirectUrl.protocol !== "http:") {
|
||||||
throw new Error(`Chutes OAuth redirect URI must be http:// (got ${params.redirectUri})`);
|
throw new Error(
|
||||||
|
`Chutes OAuth redirect URI must be http:// (got ${params.redirectUri})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const hostname = redirectUrl.hostname || "127.0.0.1";
|
const hostname = redirectUrl.hostname || "127.0.0.1";
|
||||||
const port = redirectUrl.port ? Number.parseInt(redirectUrl.port, 10) : 80;
|
const port = redirectUrl.port ? Number.parseInt(redirectUrl.port, 10) : 80;
|
||||||
const expectedPath = redirectUrl.pathname || "/";
|
const expectedPath = redirectUrl.pathname || "/";
|
||||||
|
|
||||||
return await new Promise<{ code: string; state: string }>((resolve, reject) => {
|
return await new Promise<{ code: string; state: string }>(
|
||||||
let timeout: NodeJS.Timeout | null = null;
|
(resolve, reject) => {
|
||||||
const server = createServer((req, res) => {
|
// @ts-ignore
|
||||||
try {
|
let timeout: NodeJS.Timeout | null = null;
|
||||||
const requestUrl = new URL(req.url ?? "/", redirectUrl.origin);
|
const server = createServer((req: any, res: any) => {
|
||||||
if (requestUrl.pathname !== expectedPath) {
|
try {
|
||||||
res.statusCode = 404;
|
const requestUrl = new URL(req.url ?? "/", redirectUrl.origin);
|
||||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
if (requestUrl.pathname !== expectedPath) {
|
||||||
res.end("Not found");
|
res.statusCode = 404;
|
||||||
return;
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
}
|
res.end("Not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const code = requestUrl.searchParams.get("code")?.trim();
|
const code = requestUrl.searchParams.get("code")?.trim();
|
||||||
const state = requestUrl.searchParams.get("state")?.trim();
|
const state = requestUrl.searchParams.get("state")?.trim();
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
res.statusCode = 400;
|
res.statusCode = 400;
|
||||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
res.end("Missing code");
|
res.end("Missing code");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!state || state !== params.expectedState) {
|
if (!state || state !== params.expectedState) {
|
||||||
res.statusCode = 400;
|
res.statusCode = 400;
|
||||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
res.end("Invalid state");
|
res.end("Invalid state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||||
res.end(
|
res.end(
|
||||||
[
|
[
|
||||||
"<!doctype html>",
|
"<!doctype html>",
|
||||||
"<html><head><meta charset='utf-8' /></head>",
|
"<html><head><meta charset='utf-8' /></head>",
|
||||||
"<body><h2>Chutes OAuth complete</h2>",
|
"<body><h2>Chutes OAuth complete</h2>",
|
||||||
"<p>You can close this window and return to moltbot.</p></body></html>",
|
"<p>You can close this window and return to moltbot.</p></body></html>",
|
||||||
].join(""),
|
].join(""),
|
||||||
);
|
);
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
server.close();
|
server.close();
|
||||||
resolve({ code, state });
|
resolve({ code, state });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
server.close();
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.once("error", (err: any) => {
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
server.close();
|
server.close();
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
});
|
||||||
});
|
server.listen(port, hostname, () => {
|
||||||
|
params.onProgress?.(
|
||||||
|
`Waiting for OAuth callback on ${redirectUrl.origin}${expectedPath}…`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
server.once("error", (err) => {
|
timeout = setTimeout(() => {
|
||||||
if (timeout) clearTimeout(timeout);
|
try {
|
||||||
server.close();
|
server.close();
|
||||||
reject(err);
|
} catch {}
|
||||||
});
|
reject(new Error("OAuth callback timeout"));
|
||||||
server.listen(port, hostname, () => {
|
}, params.timeoutMs);
|
||||||
params.onProgress?.(`Waiting for OAuth callback on ${redirectUrl.origin}${expectedPath}…`);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
try {
|
|
||||||
server.close();
|
|
||||||
} catch {}
|
|
||||||
reject(new Error("OAuth callback timeout"));
|
|
||||||
}, params.timeoutMs);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginChutes(params: {
|
export async function loginChutes(params: {
|
||||||
@ -127,7 +135,8 @@ export async function loginChutes(params: {
|
|||||||
fetchFn?: typeof fetch;
|
fetchFn?: typeof fetch;
|
||||||
}): Promise<OAuthCredentials> {
|
}): Promise<OAuthCredentials> {
|
||||||
const createPkce = params.createPkce ?? generateChutesPkce;
|
const createPkce = params.createPkce ?? generateChutesPkce;
|
||||||
const createState = params.createState ?? (() => randomBytes(16).toString("hex"));
|
const createState =
|
||||||
|
params.createState ?? (() => randomBytes(16).toString("hex"));
|
||||||
|
|
||||||
const { verifier, challenge } = createPkce();
|
const { verifier, challenge } = createPkce();
|
||||||
const state = createState();
|
const state = createState();
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
|
export { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||||
export {
|
export {
|
||||||
SYNTHETIC_DEFAULT_MODEL_ID,
|
SYNTHETIC_DEFAULT_MODEL_ID,
|
||||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||||
} from "../agents/synthetic-models.js";
|
} from "../agents/synthetic-models.js";
|
||||||
export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js";
|
export {
|
||||||
|
VENICE_DEFAULT_MODEL_ID,
|
||||||
|
VENICE_DEFAULT_MODEL_REF,
|
||||||
|
} from "../agents/venice-models.js";
|
||||||
export {
|
export {
|
||||||
applyAuthProfileConfig,
|
applyAuthProfileConfig,
|
||||||
applyKimiCodeConfig,
|
applyKimiCodeConfig,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
declare const process: any;
|
||||||
|
import {
|
||||||
|
resolveAgentWorkspaceDir,
|
||||||
|
resolveDefaultAgentId,
|
||||||
|
} from "../../agents/agent-scope.js";
|
||||||
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
||||||
import type { MoltbotConfig } from "../../config/config.js";
|
import type { MoltbotConfig } from "../../config/config.js";
|
||||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||||
@ -49,7 +53,10 @@ function resolveLocalPath(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPluginLoadPath(cfg: MoltbotConfig, pluginPath: string): MoltbotConfig {
|
function addPluginLoadPath(
|
||||||
|
cfg: MoltbotConfig,
|
||||||
|
pluginPath: string,
|
||||||
|
): MoltbotConfig {
|
||||||
const existing = cfg.plugins?.load?.paths ?? [];
|
const existing = cfg.plugins?.load?.paths ?? [];
|
||||||
const merged = Array.from(new Set([...existing, pluginPath]));
|
const merged = Array.from(new Set([...existing, pluginPath]));
|
||||||
return {
|
return {
|
||||||
@ -71,7 +78,11 @@ async function promptInstallChoice(params: {
|
|||||||
prompter: WizardPrompter;
|
prompter: WizardPrompter;
|
||||||
}): Promise<InstallChoice> {
|
}): Promise<InstallChoice> {
|
||||||
const { entry, localPath, prompter, defaultChoice } = params;
|
const { entry, localPath, prompter, defaultChoice } = params;
|
||||||
const localOptions: Array<{ value: InstallChoice; label: string; hint?: string }> = localPath
|
const localOptions: Array<{
|
||||||
|
value: InstallChoice;
|
||||||
|
label: string;
|
||||||
|
hint?: string;
|
||||||
|
}> = localPath
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: "local",
|
value: "local",
|
||||||
@ -80,11 +91,12 @@ async function promptInstallChoice(params: {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
const options: Array<{ value: InstallChoice; label: string; hint?: string }> = [
|
const options: Array<{ value: InstallChoice; label: string; hint?: string }> =
|
||||||
{ value: "npm", label: `Download from npm (${entry.install.npmSpec})` },
|
[
|
||||||
...localOptions,
|
{ value: "npm", label: `Download from npm (${entry.install.npmSpec})` },
|
||||||
{ value: "skip", label: "Skip for now" },
|
...localOptions,
|
||||||
];
|
{ value: "skip", label: "Skip for now" },
|
||||||
|
];
|
||||||
const initialValue: InstallChoice =
|
const initialValue: InstallChoice =
|
||||||
defaultChoice === "local" && !localPath ? "npm" : defaultChoice;
|
defaultChoice === "local" && !localPath ? "npm" : defaultChoice;
|
||||||
return await prompter.select<InstallChoice>({
|
return await prompter.select<InstallChoice>({
|
||||||
@ -149,8 +161,8 @@ export async function ensureOnboardingPluginInstalled(params: {
|
|||||||
const result = await installPluginFromNpmSpec({
|
const result = await installPluginFromNpmSpec({
|
||||||
spec: entry.install.npmSpec,
|
spec: entry.install.npmSpec,
|
||||||
logger: {
|
logger: {
|
||||||
info: (msg) => runtime.log?.(msg),
|
info: (msg: string) => runtime.log?.(msg),
|
||||||
warn: (msg) => runtime.log?.(msg),
|
warn: (msg: string) => runtime.log?.(msg),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -193,17 +205,18 @@ export function reloadOnboardingPluginRegistry(params: {
|
|||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
}): void {
|
}): void {
|
||||||
const workspaceDir =
|
const workspaceDir =
|
||||||
params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg));
|
params.workspaceDir ??
|
||||||
|
resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg));
|
||||||
const log = createSubsystemLogger("plugins");
|
const log = createSubsystemLogger("plugins");
|
||||||
loadMoltbotPlugins({
|
loadMoltbotPlugins({
|
||||||
config: params.cfg,
|
config: params.cfg,
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
cache: false,
|
cache: false,
|
||||||
logger: {
|
logger: {
|
||||||
info: (msg) => log.info(msg),
|
info: (msg: string) => log.info(msg),
|
||||||
warn: (msg) => log.warn(msg),
|
warn: (msg: string) => log.warn(msg),
|
||||||
error: (msg) => log.error(msg),
|
error: (msg: string) => log.error(msg),
|
||||||
debug: (msg) => log.debug(msg),
|
debug: (msg: string) => log.debug(msg),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,27 @@ import { loadExecApprovals } from "./controllers/exec-approvals";
|
|||||||
import { loadPresence } from "./controllers/presence";
|
import { loadPresence } from "./controllers/presence";
|
||||||
import { loadSessions } from "./controllers/sessions";
|
import { loadSessions } from "./controllers/sessions";
|
||||||
import { loadSkills } from "./controllers/skills";
|
import { loadSkills } from "./controllers/skills";
|
||||||
import { inferBasePathFromPathname, normalizeBasePath, normalizePath, pathForTab, tabFromPath, type Tab } from "./navigation";
|
import {
|
||||||
|
inferBasePathFromPathname,
|
||||||
|
normalizeBasePath,
|
||||||
|
normalizePath,
|
||||||
|
pathForTab,
|
||||||
|
tabFromPath,
|
||||||
|
type Tab,
|
||||||
|
} from "./navigation";
|
||||||
import { saveSettings, type UiSettings } from "./storage";
|
import { saveSettings, type UiSettings } from "./storage";
|
||||||
import { resolveTheme, type ResolvedTheme, type ThemeMode } from "./theme";
|
import { resolveTheme, type ResolvedTheme, type ThemeMode } from "./theme";
|
||||||
import { startThemeTransition, type ThemeTransitionContext } from "./theme-transition";
|
import {
|
||||||
|
startThemeTransition,
|
||||||
|
type ThemeTransitionContext,
|
||||||
|
} from "./theme-transition";
|
||||||
import { scheduleChatScroll, scheduleLogsScroll } from "./app-scroll";
|
import { scheduleChatScroll, scheduleLogsScroll } from "./app-scroll";
|
||||||
import { startLogsPolling, stopLogsPolling, startDebugPolling, stopDebugPolling } from "./app-polling";
|
import {
|
||||||
|
startLogsPolling,
|
||||||
|
stopLogsPolling,
|
||||||
|
startDebugPolling,
|
||||||
|
stopDebugPolling,
|
||||||
|
} from "./app-polling";
|
||||||
import { refreshChat } from "./app-chat";
|
import { refreshChat } from "./app-chat";
|
||||||
import type { MoltbotApp } from "./app";
|
import type { MoltbotApp } from "./app";
|
||||||
|
|
||||||
@ -31,6 +46,7 @@ type SettingsHost = {
|
|||||||
eventLog: unknown[];
|
eventLog: unknown[];
|
||||||
eventLogBuffer: unknown[];
|
eventLogBuffer: unknown[];
|
||||||
basePath: string;
|
basePath: string;
|
||||||
|
password: string;
|
||||||
themeMedia: MediaQueryList | null;
|
themeMedia: MediaQueryList | null;
|
||||||
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
||||||
};
|
};
|
||||||
@ -38,7 +54,8 @@ type SettingsHost = {
|
|||||||
export function applySettings(host: SettingsHost, next: UiSettings) {
|
export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||||
const normalized = {
|
const normalized = {
|
||||||
...next,
|
...next,
|
||||||
lastActiveSessionKey: next.lastActiveSessionKey?.trim() || next.sessionKey.trim() || "main",
|
lastActiveSessionKey:
|
||||||
|
next.lastActiveSessionKey?.trim() || next.sessionKey.trim() || "main",
|
||||||
};
|
};
|
||||||
host.settings = normalized;
|
host.settings = normalized;
|
||||||
saveSettings(normalized);
|
saveSettings(normalized);
|
||||||
@ -78,6 +95,10 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
|||||||
const password = passwordRaw.trim();
|
const password = passwordRaw.trim();
|
||||||
if (password) {
|
if (password) {
|
||||||
(host as { password: string }).password = password;
|
(host as { password: string }).password = password;
|
||||||
|
applySettings(host as unknown as Parameters<typeof applySettings>[0], {
|
||||||
|
...host.settings,
|
||||||
|
password,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
params.delete("password");
|
params.delete("password");
|
||||||
shouldCleanUrl = true;
|
shouldCleanUrl = true;
|
||||||
@ -115,10 +136,14 @@ export function setTab(host: SettingsHost, next: Tab) {
|
|||||||
if (next === "chat") host.chatHasAutoScrolled = false;
|
if (next === "chat") host.chatHasAutoScrolled = false;
|
||||||
if (next === "logs")
|
if (next === "logs")
|
||||||
startLogsPolling(host as unknown as Parameters<typeof startLogsPolling>[0]);
|
startLogsPolling(host as unknown as Parameters<typeof startLogsPolling>[0]);
|
||||||
else stopLogsPolling(host as unknown as Parameters<typeof stopLogsPolling>[0]);
|
else
|
||||||
|
stopLogsPolling(host as unknown as Parameters<typeof stopLogsPolling>[0]);
|
||||||
if (next === "debug")
|
if (next === "debug")
|
||||||
startDebugPolling(host as unknown as Parameters<typeof startDebugPolling>[0]);
|
startDebugPolling(
|
||||||
else stopDebugPolling(host as unknown as Parameters<typeof stopDebugPolling>[0]);
|
host as unknown as Parameters<typeof startDebugPolling>[0],
|
||||||
|
);
|
||||||
|
else
|
||||||
|
stopDebugPolling(host as unknown as Parameters<typeof stopDebugPolling>[0]);
|
||||||
void refreshActiveTab(host);
|
void refreshActiveTab(host);
|
||||||
syncUrlWithTab(host, next, false);
|
syncUrlWithTab(host, next, false);
|
||||||
}
|
}
|
||||||
@ -144,8 +169,10 @@ export function setTheme(
|
|||||||
export async function refreshActiveTab(host: SettingsHost) {
|
export async function refreshActiveTab(host: SettingsHost) {
|
||||||
if (host.tab === "overview") await loadOverview(host);
|
if (host.tab === "overview") await loadOverview(host);
|
||||||
if (host.tab === "channels") await loadChannelsTab(host);
|
if (host.tab === "channels") await loadChannelsTab(host);
|
||||||
if (host.tab === "instances") await loadPresence(host as unknown as MoltbotApp);
|
if (host.tab === "instances")
|
||||||
if (host.tab === "sessions") await loadSessions(host as unknown as MoltbotApp);
|
await loadPresence(host as unknown as MoltbotApp);
|
||||||
|
if (host.tab === "sessions")
|
||||||
|
await loadSessions(host as unknown as MoltbotApp);
|
||||||
if (host.tab === "cron") await loadCron(host);
|
if (host.tab === "cron") await loadCron(host);
|
||||||
if (host.tab === "skills") await loadSkills(host as unknown as MoltbotApp);
|
if (host.tab === "skills") await loadSkills(host as unknown as MoltbotApp);
|
||||||
if (host.tab === "nodes") {
|
if (host.tab === "nodes") {
|
||||||
@ -193,7 +220,10 @@ export function syncThemeWithSettings(host: SettingsHost) {
|
|||||||
applyResolvedTheme(host, resolveTheme(host.theme));
|
applyResolvedTheme(host, resolveTheme(host.theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme) {
|
export function applyResolvedTheme(
|
||||||
|
host: SettingsHost,
|
||||||
|
resolved: ResolvedTheme,
|
||||||
|
) {
|
||||||
host.themeResolved = resolved;
|
host.themeResolved = resolved;
|
||||||
if (typeof document === "undefined") return;
|
if (typeof document === "undefined") return;
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
@ -202,7 +232,8 @@ export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function attachThemeListener(host: SettingsHost) {
|
export function attachThemeListener(host: SettingsHost) {
|
||||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
||||||
|
return;
|
||||||
host.themeMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
host.themeMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
host.themeMediaHandler = (event) => {
|
host.themeMediaHandler = (event) => {
|
||||||
if (host.theme !== "system") return;
|
if (host.theme !== "system") return;
|
||||||
@ -234,7 +265,8 @@ export function detachThemeListener(host: SettingsHost) {
|
|||||||
|
|
||||||
export function syncTabWithLocation(host: SettingsHost, replace: boolean) {
|
export function syncTabWithLocation(host: SettingsHost, replace: boolean) {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const resolved = tabFromPath(window.location.pathname, host.basePath) ?? "chat";
|
const resolved =
|
||||||
|
tabFromPath(window.location.pathname, host.basePath) ?? "chat";
|
||||||
setTabFromRoute(host, resolved);
|
setTabFromRoute(host, resolved);
|
||||||
syncUrlWithTab(host, resolved, replace);
|
syncUrlWithTab(host, resolved, replace);
|
||||||
}
|
}
|
||||||
@ -263,10 +295,14 @@ export function setTabFromRoute(host: SettingsHost, next: Tab) {
|
|||||||
if (next === "chat") host.chatHasAutoScrolled = false;
|
if (next === "chat") host.chatHasAutoScrolled = false;
|
||||||
if (next === "logs")
|
if (next === "logs")
|
||||||
startLogsPolling(host as unknown as Parameters<typeof startLogsPolling>[0]);
|
startLogsPolling(host as unknown as Parameters<typeof startLogsPolling>[0]);
|
||||||
else stopLogsPolling(host as unknown as Parameters<typeof stopLogsPolling>[0]);
|
else
|
||||||
|
stopLogsPolling(host as unknown as Parameters<typeof stopLogsPolling>[0]);
|
||||||
if (next === "debug")
|
if (next === "debug")
|
||||||
startDebugPolling(host as unknown as Parameters<typeof startDebugPolling>[0]);
|
startDebugPolling(
|
||||||
else stopDebugPolling(host as unknown as Parameters<typeof stopDebugPolling>[0]);
|
host as unknown as Parameters<typeof startDebugPolling>[0],
|
||||||
|
);
|
||||||
|
else
|
||||||
|
stopDebugPolling(host as unknown as Parameters<typeof stopDebugPolling>[0]);
|
||||||
if (host.connected) void refreshActiveTab(host);
|
if (host.connected) void refreshActiveTab(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,11 @@ import type {
|
|||||||
StatusSummary,
|
StatusSummary,
|
||||||
NostrProfile,
|
NostrProfile,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { type ChatAttachment, type ChatQueueItem, type CronFormState } from "./ui-types";
|
import {
|
||||||
|
type ChatAttachment,
|
||||||
|
type ChatQueueItem,
|
||||||
|
type CronFormState,
|
||||||
|
} from "./ui-types";
|
||||||
import type { EventLogEntry } from "./app-events";
|
import type { EventLogEntry } from "./app-events";
|
||||||
import { DEFAULT_CRON_FORM, DEFAULT_LOG_LEVEL_FILTERS } from "./app-defaults";
|
import { DEFAULT_CRON_FORM, DEFAULT_LOG_LEVEL_FILTERS } from "./app-defaults";
|
||||||
import type {
|
import type {
|
||||||
@ -93,13 +97,18 @@ function resolveOnboardingMode(): boolean {
|
|||||||
const raw = params.get("onboarding");
|
const raw = params.get("onboarding");
|
||||||
if (!raw) return false;
|
if (!raw) return false;
|
||||||
const normalized = raw.trim().toLowerCase();
|
const normalized = raw.trim().toLowerCase();
|
||||||
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
return (
|
||||||
|
normalized === "1" ||
|
||||||
|
normalized === "true" ||
|
||||||
|
normalized === "yes" ||
|
||||||
|
normalized === "on"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("moltbot-app")
|
@customElement("moltbot-app")
|
||||||
export class MoltbotApp extends LitElement {
|
export class MoltbotApp extends LitElement {
|
||||||
@state() settings: UiSettings = loadSettings();
|
@state() settings: UiSettings = loadSettings();
|
||||||
@state() password = "";
|
@state() password = this.settings.password ?? "";
|
||||||
@state() tab: Tab = "chat";
|
@state() tab: Tab = "chat";
|
||||||
@state() onboarding = resolveOnboardingMode();
|
@state() onboarding = resolveOnboardingMode();
|
||||||
@state() connected = false;
|
@state() connected = false;
|
||||||
@ -125,7 +134,9 @@ export class MoltbotApp extends LitElement {
|
|||||||
@state() chatStream: string | null = null;
|
@state() chatStream: string | null = null;
|
||||||
@state() chatStreamStartedAt: number | null = null;
|
@state() chatStreamStartedAt: number | null = null;
|
||||||
@state() chatRunId: string | null = null;
|
@state() chatRunId: string | null = null;
|
||||||
@state() compactionStatus: import("./app-tool-stream").CompactionStatus | null = null;
|
@state() compactionStatus:
|
||||||
|
| import("./app-tool-stream").CompactionStatus
|
||||||
|
| null = null;
|
||||||
@state() chatAvatarUrl: string | null = null;
|
@state() chatAvatarUrl: string | null = null;
|
||||||
@state() chatThinkingLevel: string | null = null;
|
@state() chatThinkingLevel: string | null = null;
|
||||||
@state() chatQueue: ChatQueueItem[] = [];
|
@state() chatQueue: ChatQueueItem[] = [];
|
||||||
@ -263,7 +274,8 @@ export class MoltbotApp extends LitElement {
|
|||||||
this as unknown as Parameters<typeof onPopStateInternal>[0],
|
this as unknown as Parameters<typeof onPopStateInternal>[0],
|
||||||
);
|
);
|
||||||
private themeMedia: MediaQueryList | null = null;
|
private themeMedia: MediaQueryList | null = null;
|
||||||
private themeMediaHandler: ((event: MediaQueryListEvent) => void) | null = null;
|
private themeMediaHandler: ((event: MediaQueryListEvent) => void) | null =
|
||||||
|
null;
|
||||||
private topbarObserver: ResizeObserver | null = null;
|
private topbarObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
@ -276,11 +288,15 @@ export class MoltbotApp extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
handleFirstUpdated(this as unknown as Parameters<typeof handleFirstUpdated>[0]);
|
handleFirstUpdated(
|
||||||
|
this as unknown as Parameters<typeof handleFirstUpdated>[0],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
handleDisconnected(this as unknown as Parameters<typeof handleDisconnected>[0]);
|
handleDisconnected(
|
||||||
|
this as unknown as Parameters<typeof handleDisconnected>[0],
|
||||||
|
);
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +355,10 @@ export class MoltbotApp extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTab(next: Tab) {
|
setTab(next: Tab) {
|
||||||
setTabInternal(this as unknown as Parameters<typeof setTabInternal>[0], next);
|
setTabInternal(
|
||||||
|
this as unknown as Parameters<typeof setTabInternal>[0],
|
||||||
|
next,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTheme(next: ThemeMode, context?: Parameters<typeof setThemeInternal>[2]) {
|
setTheme(next: ThemeMode, context?: Parameters<typeof setThemeInternal>[2]) {
|
||||||
@ -430,7 +449,9 @@ export class MoltbotApp extends LitElement {
|
|||||||
handleNostrProfileToggleAdvancedInternal(this);
|
handleNostrProfileToggleAdvancedInternal(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleExecApprovalDecision(decision: "allow-once" | "allow-always" | "deny") {
|
async handleExecApprovalDecision(
|
||||||
|
decision: "allow-once" | "allow-always" | "deny",
|
||||||
|
) {
|
||||||
const active = this.execApprovalQueue[0];
|
const active = this.execApprovalQueue[0];
|
||||||
if (!active || !this.client || this.execApprovalBusy) return;
|
if (!active || !this.client || this.execApprovalBusy) return;
|
||||||
this.execApprovalBusy = true;
|
this.execApprovalBusy = true;
|
||||||
@ -440,7 +461,9 @@ export class MoltbotApp extends LitElement {
|
|||||||
id: active.id,
|
id: active.id,
|
||||||
decision,
|
decision,
|
||||||
});
|
});
|
||||||
this.execApprovalQueue = this.execApprovalQueue.filter((entry) => entry.id !== active.id);
|
this.execApprovalQueue = this.execApprovalQueue.filter(
|
||||||
|
(entry) => entry.id !== active.id,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.execApprovalError = `Exec approval failed: ${String(err)}`;
|
this.execApprovalError = `Exec approval failed: ${String(err)}`;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import type { ThemeMode } from "./theme";
|
|||||||
export type UiSettings = {
|
export type UiSettings = {
|
||||||
gatewayUrl: string;
|
gatewayUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
password?: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
lastActiveSessionKey: string;
|
lastActiveSessionKey: string;
|
||||||
theme: ThemeMode;
|
theme: ThemeMode;
|
||||||
@ -24,6 +25,7 @@ export function loadSettings(): UiSettings {
|
|||||||
const defaults: UiSettings = {
|
const defaults: UiSettings = {
|
||||||
gatewayUrl: defaultUrl,
|
gatewayUrl: defaultUrl,
|
||||||
token: "",
|
token: "",
|
||||||
|
password: "",
|
||||||
sessionKey: "main",
|
sessionKey: "main",
|
||||||
lastActiveSessionKey: "main",
|
lastActiveSessionKey: "main",
|
||||||
theme: "system",
|
theme: "system",
|
||||||
@ -44,6 +46,10 @@ export function loadSettings(): UiSettings {
|
|||||||
? parsed.gatewayUrl.trim()
|
? parsed.gatewayUrl.trim()
|
||||||
: defaults.gatewayUrl,
|
: defaults.gatewayUrl,
|
||||||
token: typeof parsed.token === "string" ? parsed.token : defaults.token,
|
token: typeof parsed.token === "string" ? parsed.token : defaults.token,
|
||||||
|
password:
|
||||||
|
typeof parsed.password === "string"
|
||||||
|
? parsed.password
|
||||||
|
: defaults.password,
|
||||||
sessionKey:
|
sessionKey:
|
||||||
typeof parsed.sessionKey === "string" && parsed.sessionKey.trim()
|
typeof parsed.sessionKey === "string" && parsed.sessionKey.trim()
|
||||||
? parsed.sessionKey.trim()
|
? parsed.sessionKey.trim()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user