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
313 lines
8.0 KiB
TypeScript
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;
|
|
}
|
|
}
|