Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

313 lines
8.0 KiB
TypeScript

/**
* Credential preset definitions and loader for the connector `/setup` flow.
* Describes the fields each credential preset requires and reads their values
* from disk.
*/
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
export interface CredentialPreset {
name: string;
displayName: string;
fields: CredentialField[];
helpUrl: string;
helpText: string;
validate: (
credentials: Record<string, string>,
) => Promise<{ valid: boolean; identity?: string; error?: string }>;
}
export interface CredentialField {
key: string;
label: string;
secret: boolean;
}
const SAFE_PRESET_NAME_RE = /^[A-Za-z0-9_-]+$/;
const presets = new Map<string, CredentialPreset>();
function getCredentialsDir(): string {
const configured = process.env.CREDENTIALS_DIR?.trim();
if (configured) {
return configured;
}
const home =
(typeof os.homedir === "function" ? os.homedir() : "") ||
process.env.HOME ||
process.env.USERPROFILE;
return home
? path.join(home, ".credentials")
: path.join(process.cwd(), ".credentials");
}
export function registerPreset(preset: CredentialPreset): void {
const normalizedName = preset.name.trim().toLowerCase();
if (!SAFE_PRESET_NAME_RE.test(normalizedName)) {
throw new Error(
`Invalid credential preset name "${preset.name}". Only letters, numbers, underscores, and hyphens are allowed.`,
);
}
presets.set(normalizedName, { ...preset, name: normalizedName });
}
export function getPreset(name: string): CredentialPreset | undefined {
return presets.get(name.toLowerCase());
}
export function listPresets(): string[] {
return [...presets.keys()];
}
registerPreset({
name: "github",
displayName: "GitHub",
fields: [{ key: "token", label: "Personal Access Token", secret: true }],
helpUrl: "https://github.com/settings/tokens",
helpText:
"Create a fine-grained PAT at the link above. Give it the repository permissions you need.",
async validate(credentials) {
try {
const response = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${credentials.token}`,
Accept: "application/vnd.github+json",
},
});
if (!response.ok) {
return {
valid: false,
error: `GitHub returned ${response.status}`,
};
}
const data = (await response.json()) as { login?: string };
return {
valid: true,
identity: data.login ? `@${data.login}` : "verified",
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "vercel",
displayName: "Vercel",
fields: [{ key: "token", label: "API Token", secret: true }],
helpUrl: "https://vercel.com/account/tokens",
helpText: "Create a token at the link above. Full Account scope works best.",
async validate(credentials) {
try {
const response = await fetch("https://api.vercel.com/v9/projects", {
headers: { Authorization: `Bearer ${credentials.token}` },
});
if (!response.ok) {
return {
valid: false,
error: `Vercel returned ${response.status}`,
};
}
const data = (await response.json()) as {
projects?: Array<{ name: string }>;
};
return {
valid: true,
identity: `${data.projects?.length ?? 0} project(s) accessible`,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "cloudflare",
displayName: "Cloudflare",
fields: [
{ key: "apiKey", label: "Global API Key", secret: true },
{ key: "email", label: "Account Email", secret: false },
],
helpUrl: "https://dash.cloudflare.com/profile/api-tokens",
helpText:
'Go to Cloudflare > Profile > API Tokens > "Global API Key". You will also need your account email.',
async validate(credentials) {
try {
const response = await fetch(
"https://api.cloudflare.com/client/v4/zones",
{
headers: {
"X-Auth-Key": credentials.apiKey,
"X-Auth-Email": credentials.email,
},
},
);
if (!response.ok) {
return {
valid: false,
error: `Cloudflare returned ${response.status}`,
};
}
const data = (await response.json()) as {
result?: Array<{ name: string }>;
};
return {
valid: true,
identity:
data.result && data.result.length > 0
? `zones: ${data.result.map((zone) => zone.name).join(", ")}`
: "verified",
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "anthropic",
displayName: "Anthropic",
fields: [{ key: "apiKey", label: "API Key", secret: true }],
helpUrl: "https://console.anthropic.com/settings/keys",
helpText: "Create an API key in the Anthropic console.",
async validate(credentials) {
try {
// @duplicate-component-audit-allow: credential probe validates the key; response content is ignored.
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": credentials.apiKey,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-haiku-20241022",
max_tokens: 1,
messages: [{ role: "user", content: "hi" }],
}),
});
if (response.ok || response.status === 429) {
return { valid: true, identity: "key verified" };
}
return {
valid: false,
error: `Anthropic returned ${response.status}`,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "openai",
displayName: "OpenAI",
fields: [{ key: "apiKey", label: "API Key", secret: true }],
helpUrl: "https://platform.openai.com/api-keys",
helpText: "Create an API key at the OpenAI platform link above.",
async validate(credentials) {
try {
const response = await fetch("https://api.openai.com/v1/models", {
headers: { Authorization: `Bearer ${credentials.apiKey}` },
});
if (response.ok || response.status === 429) {
return { valid: true, identity: "key verified" };
}
return {
valid: false,
error: `OpenAI returned ${response.status}`,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "fal",
displayName: "fal.ai",
fields: [{ key: "apiKey", label: "API Key", secret: true }],
helpUrl: "https://fal.ai/dashboard/keys",
helpText: "Generate an API key from your fal.ai dashboard.",
async validate(credentials) {
try {
const response = await fetch("https://rest.fal.run/fal-ai/fast-sdxl", {
method: "POST",
headers: {
Authorization: `Key ${credentials.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "test",
image_size: { width: 64, height: 64 },
num_images: 1,
}),
});
if (response.ok || response.status === 422 || response.status === 429) {
return { valid: true, identity: "key verified" };
}
return {
valid: false,
error: `fal.ai returned ${response.status}`,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : String(error),
};
}
},
});
registerPreset({
name: "generic",
displayName: "Custom Credential",
fields: [
{
key: "envName",
label: "environment variable name (for example MY_API_KEY)",
secret: false,
},
{ key: "value", label: "value", secret: true },
],
helpUrl: "",
helpText:
"I'll store this as a generic credential. Give me the env var name and value.",
async validate() {
return { valid: true, identity: "stored (unvalidated)" };
},
});
export function loadCredentials(
service: string,
): Record<string, string> | null {
const filePath = path.join(getCredentialsDir(), `${service}.json`);
if (!fs.existsSync(filePath)) {
return null;
}
try {
return JSON.parse(fs.readFileSync(filePath, "utf8")) as Record<
string,
string
>;
} catch {
return null;
}
}