chore: sanitize onboarding api keys
This commit is contained in:
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,9 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2026.1.12
|
## 2026.1.12-1
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Cron: add optional `agentId` binding (CLI `--agent` / `--clear-agent`), route cron runs + summaries to the chosen agent, and document/test the fallback to the default agent. (#770)
|
- Heartbeat: raise default `ackMaxChars` to 300 so any `HEARTBEAT_OK` replies with short padding stay internal (fewer noisy heartbeat posts on providers).
|
||||||
|
- Onboarding: normalize API key inputs (strip `export KEY=...` wrappers) so shell-style entries paste cleanly.
|
||||||
|
|
||||||
|
## 2026.1.11-5
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Auto-reply: prevent duplicate /status replies (including /usage alias) and add tests for inline + standalone cases.
|
||||||
|
|
||||||
|
## 2026.1.11-4
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- CLI: read the git commit hash from the package root so npm installs show it.
|
||||||
|
|
||||||
|
## 2026.1.11-3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- CLI: avoid top-level await warnings in the entrypoint on fresh installs.
|
||||||
|
- CLI: show a commit hash in the banner for npm installs (package.json gitHead fallback).
|
||||||
|
|
||||||
|
## 2026.1.11-2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Installer: ensure the CLI entrypoint is executable after npm installs.
|
||||||
|
- Packaging: include `dist/plugins/` in the npm package to avoid missing module errors.
|
||||||
|
|
||||||
|
## 2026.1.11-1
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Installer: include `patches/` in the npm package so postinstall patching works for npm/bun installs.
|
||||||
|
|
||||||
## 2026.1.11
|
## 2026.1.11
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ describe("applyAuthChoice", () => {
|
|||||||
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;
|
||||||
|
|
||||||
const text = vi.fn().mockResolvedValue("sk-minimax-test");
|
const text = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue('export MINIMAX_API_KEY="sk-minimax-test"');
|
||||||
const select: WizardPrompter["select"] = vi.fn(
|
const select: WizardPrompter["select"] = vi.fn(
|
||||||
async (params) => params.options[0]?.value as never,
|
async (params) => params.options[0]?.value as never,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,6 +70,34 @@ import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
|
|||||||
|
|
||||||
const DEFAULT_KEY_PREVIEW = { head: 4, tail: 4 };
|
const DEFAULT_KEY_PREVIEW = { head: 4, tail: 4 };
|
||||||
|
|
||||||
|
function normalizeApiKeyInput(raw: string): string {
|
||||||
|
const trimmed = String(raw ?? "").trim();
|
||||||
|
if (!trimmed) return "";
|
||||||
|
|
||||||
|
// Handle shell-style assignments: export KEY="value" or KEY=value
|
||||||
|
const assignmentMatch = trimmed.match(
|
||||||
|
/^(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.+)$/,
|
||||||
|
);
|
||||||
|
const valuePart = assignmentMatch ? assignmentMatch[1].trim() : trimmed;
|
||||||
|
|
||||||
|
const unquoted =
|
||||||
|
valuePart.length >= 2 &&
|
||||||
|
((valuePart.startsWith('"') && valuePart.endsWith('"')) ||
|
||||||
|
(valuePart.startsWith("'") && valuePart.endsWith("'")) ||
|
||||||
|
(valuePart.startsWith("`") && valuePart.endsWith("`")))
|
||||||
|
? valuePart.slice(1, -1)
|
||||||
|
: valuePart;
|
||||||
|
|
||||||
|
const withoutSemicolon = unquoted.endsWith(";")
|
||||||
|
? unquoted.slice(0, -1)
|
||||||
|
: unquoted;
|
||||||
|
|
||||||
|
return withoutSemicolon.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateApiKeyInput = (value: unknown) =>
|
||||||
|
normalizeApiKeyInput(String(value ?? "")).length > 0 ? undefined : "Required";
|
||||||
|
|
||||||
function formatApiKeyPreview(
|
function formatApiKeyPreview(
|
||||||
raw: string,
|
raw: string,
|
||||||
opts: { head?: number; tail?: number } = {},
|
opts: { head?: number; tail?: number } = {},
|
||||||
@@ -381,9 +409,9 @@ export async function applyAuthChoice(params: {
|
|||||||
|
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter OpenAI API key",
|
message: "Enter OpenAI API key",
|
||||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
const trimmed = String(key).trim();
|
const trimmed = normalizeApiKeyInput(String(key));
|
||||||
const result = upsertSharedEnvVar({
|
const result = upsertSharedEnvVar({
|
||||||
key: "OPENAI_API_KEY",
|
key: "OPENAI_API_KEY",
|
||||||
value: trimmed,
|
value: trimmed,
|
||||||
@@ -440,9 +468,12 @@ export async function applyAuthChoice(params: {
|
|||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter OpenRouter API key",
|
message: "Enter OpenRouter API key",
|
||||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setOpenrouterApiKey(String(key).trim(), params.agentDir);
|
await setOpenrouterApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
hasCredential = true;
|
hasCredential = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,9 +511,12 @@ export async function applyAuthChoice(params: {
|
|||||||
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: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setMoonshotApiKey(String(key).trim(), params.agentDir);
|
await setMoonshotApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "moonshot:default",
|
profileId: "moonshot:default",
|
||||||
@@ -723,9 +757,12 @@ export async function applyAuthChoice(params: {
|
|||||||
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: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setGeminiApiKey(String(key).trim(), params.agentDir);
|
await setGeminiApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "google:default",
|
profileId: "google:default",
|
||||||
@@ -761,9 +798,9 @@ export async function applyAuthChoice(params: {
|
|||||||
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: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setZaiApiKey(String(key).trim(), params.agentDir);
|
await setZaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "zai:default",
|
profileId: "zai:default",
|
||||||
@@ -814,9 +851,12 @@ export async function applyAuthChoice(params: {
|
|||||||
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: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setAnthropicApiKey(String(key).trim(), params.agentDir);
|
await setAnthropicApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "anthropic:default",
|
profileId: "anthropic:default",
|
||||||
@@ -847,9 +887,12 @@ export async function applyAuthChoice(params: {
|
|||||||
if (!hasCredential) {
|
if (!hasCredential) {
|
||||||
const key = await params.prompter.text({
|
const key = await params.prompter.text({
|
||||||
message: "Enter MiniMax API key",
|
message: "Enter MiniMax API key",
|
||||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setMinimaxApiKey(String(key).trim(), params.agentDir);
|
await setMinimaxApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "minimax:default",
|
profileId: "minimax:default",
|
||||||
@@ -896,9 +939,12 @@ export async function applyAuthChoice(params: {
|
|||||||
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: (value) => (value?.trim() ? undefined : "Required"),
|
validate: validateApiKeyInput,
|
||||||
});
|
});
|
||||||
await setOpencodeZenApiKey(String(key).trim(), params.agentDir);
|
await setOpencodeZenApiKey(
|
||||||
|
normalizeApiKeyInput(String(key)),
|
||||||
|
params.agentDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||||
profileId: "opencode:default",
|
profileId: "opencode:default",
|
||||||
|
|||||||
Reference in New Issue
Block a user