/** * Local dev seed for an existing Organization that predates the ADR-0024 * secret-envelope plane (e.g. the legacy `org_default` from the tenant-root * migration). Creates ACTIVE Feishu Application + Provider (BYOK openrouter) * connections with encrypted envelopes, using the local dev keyring. * * Idempotent: re-running rotates a new secret version if the connection * already exists. * * Usage: npx tsx scripts/dev-seed-connections.ts */ import "dotenv/config"; import { PrismaClient } from "@prisma/client"; import { FeishuApplicationConnectionService } from "../src/connections/feishuApplicationConnections.js"; import { ProviderConnectionService } from "../src/connections/providerConnections.js"; import { loadLocalSecretKeyring, LocalSecretEnvelope } from "../src/security/secretEnvelope.js"; function requiredEnv(name: string): string { const v = process.env[name]?.trim(); if (v === undefined || v === "") throw new Error(`missing required env: ${name}`); return v; } async function main(): Promise { const databaseUrl = requiredEnv("DATABASE_URL"); const organizationId = requiredEnv("HUB_SILO_ORGANIZATION_ID"); const keyring = await loadLocalSecretKeyring(); const secrets = new LocalSecretEnvelope(keyring); const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } }, log: [] }); const feishuAppId = requiredEnv("FEISHU_APP_ID"); const feishuAppSecret = requiredEnv("FEISHU_APP_SECRET"); const feishuBotOpenId = requiredEnv("FEISHU_BOT_OPEN_ID"); const providerAuthToken = requiredEnv("ANTHROPIC_AUTH_TOKEN"); const providerBaseUrl = process.env["ANTHROPIC_BASE_URL"]?.trim() || "https://openrouter.ai/api"; const anthropicApiKey = process.env["ANTHROPIC_API_KEY"]?.trim() || ""; // Pick an existing active OWNER/ADMIN as the actor for the audit rows. const actor = await prisma.organizationMembership.findFirst({ where: { organizationId, role: { in: ["OWNER", "ADMIN"] }, revokedAt: null }, select: { userId: true }, }); if (actor === null) throw new Error(`no active OWNER/ADMIN membership on ${organizationId}; seed a member first`); const actorUserId = actor.userId; console.log("actor:", actorUserId); try { const feishuService = new FeishuApplicationConnectionService( prisma, secrets, async () => {}, // skip live Feishu probe in local dev ); const feishuResult = await feishuService.rotateCustomerApplication({ organizationId, actorUserId, appId: feishuAppId, appSecret: feishuAppSecret, botOpenId: feishuBotOpenId, }); console.log("feishu connection:", JSON.stringify(feishuResult, null, 2)); const providerService = new ProviderConnectionService( prisma, secrets, async () => {}, // skip live OpenRouter probe in local dev ); const providerResult = await providerService.rotateByok({ organizationId, actorUserId, providerId: "openrouter", baseUrl: providerBaseUrl, authToken: providerAuthToken, ...(anthropicApiKey !== "" ? { anthropicApiKey } : {}), }); console.log("provider connection:", JSON.stringify(providerResult, null, 2)); } finally { await prisma.$disconnect(); } } main().catch((error: unknown) => { console.error("[dev-seed-connections] failed:", error instanceof Error ? error.message : String(error)); process.exitCode = 1; });