refactor: centralize onboarding auth paths
This commit is contained in:
@@ -155,12 +155,3 @@ export function resolveAgentDir(cfg: ClawdbotConfig, agentId: string) {
|
|||||||
const root = resolveStateDir(process.env, os.homedir);
|
const root = resolveStateDir(process.env, os.homedir);
|
||||||
return path.join(root, "agents", id, "agent");
|
return path.join(root, "agents", id, "agent");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the agent directory for the default agent without requiring config.
|
|
||||||
* Used by onboarding when writing auth profiles before config is fully set up.
|
|
||||||
*/
|
|
||||||
export function resolveDefaultAgentDir(): string {
|
|
||||||
const root = resolveStateDir(process.env, os.homedir);
|
|
||||||
return path.join(root, "agents", DEFAULT_AGENT_ID, "agent");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ vi.mock("../providers/github-copilot-auth.js", () => ({
|
|||||||
|
|
||||||
const noopAsync = async () => {};
|
const noopAsync = async () => {};
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
const authProfilePathFor = (agentDir: string) =>
|
||||||
|
path.join(agentDir, "auth-profiles.json");
|
||||||
|
|
||||||
describe("applyAuthChoice", () => {
|
describe("applyAuthChoice", () => {
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
@@ -111,12 +113,8 @@ describe("applyAuthChoice", () => {
|
|||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
|
|
||||||
const authProfilePath = path.join(
|
const authProfilePath = authProfilePathFor(
|
||||||
tempStateDir,
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"agent",
|
|
||||||
"auth-profiles.json",
|
|
||||||
);
|
);
|
||||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||||
const parsed = JSON.parse(raw) as {
|
const parsed = JSON.parse(raw) as {
|
||||||
@@ -170,12 +168,8 @@ describe("applyAuthChoice", () => {
|
|||||||
mode: "api_key",
|
mode: "api_key",
|
||||||
});
|
});
|
||||||
|
|
||||||
const authProfilePath = path.join(
|
const authProfilePath = authProfilePathFor(
|
||||||
tempStateDir,
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"agent",
|
|
||||||
"auth-profiles.json",
|
|
||||||
);
|
);
|
||||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||||
const parsed = JSON.parse(raw) as {
|
const parsed = JSON.parse(raw) as {
|
||||||
@@ -337,12 +331,8 @@ describe("applyAuthChoice", () => {
|
|||||||
"openrouter/auto",
|
"openrouter/auto",
|
||||||
);
|
);
|
||||||
|
|
||||||
const authProfilePath = path.join(
|
const authProfilePath = authProfilePathFor(
|
||||||
tempStateDir,
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"agent",
|
|
||||||
"auth-profiles.json",
|
|
||||||
);
|
);
|
||||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||||
const parsed = JSON.parse(raw) as {
|
const parsed = JSON.parse(raw) as {
|
||||||
@@ -426,12 +416,8 @@ describe("applyAuthChoice", () => {
|
|||||||
mode: "oauth",
|
mode: "oauth",
|
||||||
});
|
});
|
||||||
|
|
||||||
const authProfilePath = path.join(
|
const authProfilePath = authProfilePathFor(
|
||||||
tempStateDir,
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"agent",
|
|
||||||
"auth-profiles.json",
|
|
||||||
);
|
);
|
||||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||||
const parsed = JSON.parse(raw) as {
|
const parsed = JSON.parse(raw) as {
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ import {
|
|||||||
applySyntheticConfig,
|
applySyntheticConfig,
|
||||||
applySyntheticProviderConfig,
|
applySyntheticProviderConfig,
|
||||||
OPENROUTER_DEFAULT_MODEL_REF,
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
|
setMinimaxApiKey,
|
||||||
SYNTHETIC_DEFAULT_MODEL_ID,
|
SYNTHETIC_DEFAULT_MODEL_ID,
|
||||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
|
|
||||||
|
const authProfilePathFor = (agentDir: string) =>
|
||||||
|
path.join(agentDir, "auth-profiles.json");
|
||||||
|
|
||||||
describe("writeOAuthCredentials", () => {
|
describe("writeOAuthCredentials", () => {
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
@@ -50,10 +54,9 @@ describe("writeOAuthCredentials", () => {
|
|||||||
delete process.env.CLAWDBOT_OAUTH_DIR;
|
delete process.env.CLAWDBOT_OAUTH_DIR;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("writes auth-profiles.json under CLAWDBOT_STATE_DIR/agents/main/agent", async () => {
|
it("writes auth-profiles.json under CLAWDBOT_AGENT_DIR when set", async () => {
|
||||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-oauth-"));
|
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-oauth-"));
|
||||||
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
|
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
|
||||||
// Even if legacy env vars are set, onboarding should write to the multi-agent path.
|
|
||||||
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
|
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||||
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
|
||||||
@@ -65,13 +68,8 @@ describe("writeOAuthCredentials", () => {
|
|||||||
|
|
||||||
await writeOAuthCredentials("openai-codex", creds);
|
await writeOAuthCredentials("openai-codex", creds);
|
||||||
|
|
||||||
// Now writes to the multi-agent path: agents/main/agent
|
const authProfilePath = authProfilePathFor(
|
||||||
const authProfilePath = path.join(
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
tempStateDir,
|
|
||||||
"agents",
|
|
||||||
"main",
|
|
||||||
"agent",
|
|
||||||
"auth-profiles.json",
|
|
||||||
);
|
);
|
||||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||||
const parsed = JSON.parse(raw) as {
|
const parsed = JSON.parse(raw) as {
|
||||||
@@ -85,7 +83,65 @@ describe("writeOAuthCredentials", () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
fs.readFile(
|
fs.readFile(
|
||||||
path.join(tempStateDir, "agent", "auth-profiles.json"),
|
path.join(tempStateDir, "agents", "main", "agent", "auth-profiles.json"),
|
||||||
|
"utf8",
|
||||||
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setMinimaxApiKey", () => {
|
||||||
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
|
let tempStateDir: string | null = null;
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (tempStateDir) {
|
||||||
|
await fs.rm(tempStateDir, { recursive: true, force: true });
|
||||||
|
tempStateDir = null;
|
||||||
|
}
|
||||||
|
if (previousStateDir === undefined) {
|
||||||
|
delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||||
|
}
|
||||||
|
if (previousAgentDir === undefined) {
|
||||||
|
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||||
|
}
|
||||||
|
if (previousPiAgentDir === undefined) {
|
||||||
|
delete process.env.PI_CODING_AGENT_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("writes to CLAWDBOT_AGENT_DIR when set", async () => {
|
||||||
|
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-minimax-"));
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = tempStateDir;
|
||||||
|
process.env.CLAWDBOT_AGENT_DIR = path.join(tempStateDir, "custom-agent");
|
||||||
|
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
|
||||||
|
await setMinimaxApiKey("sk-minimax-test");
|
||||||
|
|
||||||
|
const customAuthPath = authProfilePathFor(
|
||||||
|
process.env.CLAWDBOT_AGENT_DIR!,
|
||||||
|
);
|
||||||
|
const raw = await fs.readFile(customAuthPath, "utf8");
|
||||||
|
const parsed = JSON.parse(raw) as {
|
||||||
|
profiles?: Record<string, { type?: string; provider?: string; key?: string }>;
|
||||||
|
};
|
||||||
|
expect(parsed.profiles?.["minimax:default"]).toMatchObject({
|
||||||
|
type: "api_key",
|
||||||
|
provider: "minimax",
|
||||||
|
key: "sk-minimax-test",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
fs.readFile(
|
||||||
|
path.join(tempStateDir, "agents", "main", "agent", "auth-profiles.json"),
|
||||||
"utf8",
|
"utf8",
|
||||||
),
|
),
|
||||||
).rejects.toThrow();
|
).rejects.toThrow();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
||||||
import { resolveDefaultAgentDir } from "../agents/agent-scope.js";
|
import { resolveClawdbotAgentDir } from "../agents/agent-paths.js";
|
||||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||||
import { OPENCODE_ZEN_DEFAULT_MODEL_REF } from "../agents/opencode-zen-models.js";
|
import { OPENCODE_ZEN_DEFAULT_MODEL_REF } from "../agents/opencode-zen-models.js";
|
||||||
import {
|
import {
|
||||||
@@ -24,6 +24,9 @@ const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000;
|
|||||||
const MOONSHOT_DEFAULT_MAX_TOKENS = 8192;
|
const MOONSHOT_DEFAULT_MAX_TOKENS = 8192;
|
||||||
export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`;
|
export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`;
|
||||||
export { SYNTHETIC_DEFAULT_MODEL_ID, SYNTHETIC_DEFAULT_MODEL_REF };
|
export { SYNTHETIC_DEFAULT_MODEL_ID, SYNTHETIC_DEFAULT_MODEL_REF };
|
||||||
|
|
||||||
|
const resolveAuthAgentDir = (agentDir?: string) =>
|
||||||
|
agentDir ?? resolveClawdbotAgentDir();
|
||||||
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
// Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs.
|
||||||
const MINIMAX_API_COST = {
|
const MINIMAX_API_COST = {
|
||||||
input: 15,
|
input: 15,
|
||||||
@@ -107,7 +110,7 @@ export async function writeOAuthCredentials(
|
|||||||
creds: OAuthCredentials,
|
creds: OAuthCredentials,
|
||||||
agentDir?: string,
|
agentDir?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: `${provider}:${creds.email ?? "default"}`,
|
profileId: `${provider}:${creds.email ?? "default"}`,
|
||||||
credential: {
|
credential: {
|
||||||
@@ -115,12 +118,12 @@ export async function writeOAuthCredentials(
|
|||||||
provider,
|
provider,
|
||||||
...creds,
|
...creds,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setAnthropicApiKey(key: string, agentDir?: string) {
|
export async function setAnthropicApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "anthropic:default",
|
profileId: "anthropic:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -128,12 +131,12 @@ export async function setAnthropicApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setGeminiApiKey(key: string, agentDir?: string) {
|
export async function setGeminiApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "google:default",
|
profileId: "google:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -141,12 +144,12 @@ export async function setGeminiApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "google",
|
provider: "google",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "minimax:default",
|
profileId: "minimax:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -154,12 +157,12 @@ export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "minimax",
|
provider: "minimax",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setMoonshotApiKey(key: string, agentDir?: string) {
|
export async function setMoonshotApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "moonshot:default",
|
profileId: "moonshot:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -167,12 +170,12 @@ export async function setMoonshotApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "moonshot",
|
provider: "moonshot",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setSyntheticApiKey(key: string, agentDir?: string) {
|
export async function setSyntheticApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "synthetic:default",
|
profileId: "synthetic:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -180,7 +183,7 @@ export async function setSyntheticApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "synthetic",
|
provider: "synthetic",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +191,7 @@ export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
|||||||
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||||
|
|
||||||
export async function setZaiApiKey(key: string, agentDir?: string) {
|
export async function setZaiApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||||
upsertAuthProfile({
|
upsertAuthProfile({
|
||||||
profileId: "zai:default",
|
profileId: "zai:default",
|
||||||
credential: {
|
credential: {
|
||||||
@@ -196,7 +199,7 @@ export async function setZaiApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "zai",
|
provider: "zai",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +211,7 @@ export async function setOpenrouterApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "openrouter",
|
provider: "openrouter",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +717,7 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) {
|
|||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
agentDir: agentDir ?? resolveDefaultAgentDir(),
|
agentDir: resolveAuthAgentDir(agentDir),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user