|
| 1 | +// Central registry for standard secrets used by automations and tools. |
| 2 | +// AI-friendly design: a flat list with simple fields and helper lookups. |
| 3 | + |
| 4 | +export type SecretProvider = 'github'; |
| 5 | + |
| 6 | +export interface SecretEntry { |
| 7 | + id: string; // stable identifier, e.g. 'github.token' |
| 8 | + provider: SecretProvider; |
| 9 | + name: string; // short name within provider, e.g. 'token' |
| 10 | + envVar: string; // environment variable name, e.g. 'GITHUB_TOKEN' |
| 11 | + description: string; |
| 12 | + required: boolean; |
| 13 | + docsUrl?: string; |
| 14 | + aliases?: readonly string[]; // additional phrases an AI/user might use |
| 15 | +} |
| 16 | + |
| 17 | +export const SECRETS: readonly SecretEntry[] = [ |
| 18 | + { |
| 19 | + id: 'github.token', |
| 20 | + provider: 'github', |
| 21 | + name: 'token', |
| 22 | + envVar: 'GITHUB_TOKEN', |
| 23 | + description: |
| 24 | + 'GitHub token (PAT or App installation token) with read access to repository contents and metadata.', |
| 25 | + required: true, |
| 26 | + docsUrl: |
| 27 | + 'https://docs.github.com/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token', |
| 28 | + aliases: ['github token', 'gh token', 'github_pat', 'github personal access token'], |
| 29 | + }, |
| 30 | +] as const; |
| 31 | + |
| 32 | +// Lightweight indexes for fast lookup |
| 33 | +const SECRET_BY_ID: Readonly<Record<string, SecretEntry>> = Object.freeze( |
| 34 | + Object.fromEntries(SECRETS.map((s) => [s.id, s])), |
| 35 | +); |
| 36 | + |
| 37 | +const SECRET_BY_ENV: Readonly<Record<string, SecretEntry>> = Object.freeze( |
| 38 | + Object.fromEntries(SECRETS.map((s) => [s.envVar.toUpperCase(), s])), |
| 39 | +); |
| 40 | + |
| 41 | +export function listSecrets(): readonly SecretEntry[] { |
| 42 | + return SECRETS; |
| 43 | +} |
| 44 | + |
| 45 | +export function listProviderSecrets(provider: SecretProvider): readonly SecretEntry[] { |
| 46 | + return SECRETS.filter((s) => s.provider === provider); |
| 47 | +} |
| 48 | + |
| 49 | +export function getSecretById(id: string): SecretEntry | undefined { |
| 50 | + return SECRET_BY_ID[id]; |
| 51 | +} |
| 52 | + |
| 53 | +export function getSecretByEnvVar(envVar: string): SecretEntry | undefined { |
| 54 | + return SECRET_BY_ENV[envVar.toUpperCase()]; |
| 55 | +} |
| 56 | + |
| 57 | +export function getEnvVarNameById(id: string): string | undefined { |
| 58 | + return getSecretById(id)?.envVar; |
| 59 | +} |
| 60 | + |
| 61 | +// Flexible resolver that accepts: id, env var, provider.name, or an alias phrase |
| 62 | +export function resolveSecretIdentifier(identifier: string): SecretEntry | undefined { |
| 63 | + const raw = identifier.trim(); |
| 64 | + if (!raw) return undefined; |
| 65 | + |
| 66 | + // Exact id |
| 67 | + const byId = getSecretById(raw); |
| 68 | + if (byId) return byId; |
| 69 | + |
| 70 | + // Exact env var |
| 71 | + const byEnv = getSecretByEnvVar(raw); |
| 72 | + if (byEnv) return byEnv; |
| 73 | + |
| 74 | + const normalized = raw.toLowerCase().replace(/\s+/g, ' ').trim(); |
| 75 | + |
| 76 | + // provider.name form |
| 77 | + const dotIdx = normalized.indexOf('.'); |
| 78 | + if (dotIdx > 0) { |
| 79 | + const provider = normalized.slice(0, dotIdx); |
| 80 | + const name = normalized.slice(dotIdx + 1); |
| 81 | + const match = SECRETS.find( |
| 82 | + (s) => s.provider === (provider as SecretProvider) && s.name.toLowerCase() === name, |
| 83 | + ); |
| 84 | + if (match) return match; |
| 85 | + } |
| 86 | + |
| 87 | + // Alias match |
| 88 | + const byAlias = SECRETS.find((s) => |
| 89 | + (s.aliases ?? []).some((a) => a.toLowerCase() === normalized), |
| 90 | + ); |
| 91 | + if (byAlias) return byAlias; |
| 92 | + |
| 93 | + // Provider-keyword fallback: e.g., 'github token' |
| 94 | + const byTokens = SECRETS.find( |
| 95 | + (s) => normalized.includes(s.provider) && normalized.includes(s.name.toLowerCase()), |
| 96 | + ); |
| 97 | + return byTokens; |
| 98 | +} |
0 commit comments