{"version":3,"file":"secrets.mjs","names":[],"sources":["../../src/handlers/secrets.ts"],"sourcesContent":["import process from \"node:process\";\nimport * as p from \"@clack/prompts\";\nimport { Effect } from \"effect\";\nimport type { ResultRenderer } from \"../cli/commands/context\";\nimport type { SecretsListResult, SecretsRemoveResult, SecretsSetResult } from \"../contract\";\nimport { SessionStore } from \"../services/auth/session-store\";\nimport { parseSiteErrorDetail, SiteClient } from \"../services/auth/site-client\";\nimport { isReservedSecretName, isValidSecretName } from \"../services/hosting/secrets\";\nimport { ResolutionSessionService } from \"../services/resolution/session\";\nimport { colors, icons } from \"../utils/theme\";\nimport type { BosBuilder } from \"./shared\";\n\n/**\n * Resolve the runtime identity + declared secrets from the resolution\n * session's config (the authored bos.app.ts in the cwd). The rows the vault\n * stores are keyed by this identity; the vault ITSELF is whichever deployment\n * answers at the credential's site.\n */\nfunction collectDeclaredSecrets(config: unknown): string[] {\n  const cfg = config as {\n    secrets?: string[];\n    app?: Record<string, { secrets?: string[] } | undefined>;\n    plugins?: Record<string, { secrets?: string[] } | undefined> | undefined;\n  } | null;\n  if (!cfg) return [];\n  const declared = new Set<string>();\n  for (const secret of cfg.secrets ?? []) declared.add(secret);\n  for (const section of Object.values(cfg.app ?? {})) {\n    for (const secret of section?.secrets ?? []) declared.add(secret);\n  }\n  for (const plugin of Object.values(cfg.plugins ?? {})) {\n    for (const secret of plugin?.secrets ?? []) declared.add(secret);\n  }\n  return [...declared];\n}\n\n/**\n * v1 tier heuristic (plan 055 ticket 09): authoritative tier data arrives with\n * the tenant bindings surface (ticket 14). Until then, a target that is\n * neither the declared config's own runtime nor a child of the vault site's\n * runtime gets an honest warning — a BYO-account runtime supplies its own env,\n * so rows stored for it would never be delivered.\n */\nfunction hostingRelationshipWarning(\n  config: unknown,\n  siteUrl: string,\n  account: string,\n  gateway: string,\n): string | null {\n  const cfg = config as { account?: string; domain?: string; extends?: unknown } | null | undefined;\n  if (cfg?.account === account && cfg?.domain === gateway) return null;\n  const extendsText = JSON.stringify(cfg?.extends ?? \"\");\n  const siteHost = new URL(siteUrl).host;\n  if (extendsText.includes(siteHost) || extendsText.includes(new URL(siteUrl).hostname)) {\n    return null;\n  }\n  return (\n    `The target runtime (${account}/${gateway}) is not this config's declared runtime, and its ` +\n    `extends chain does not reference the vault site (${siteHost}). If this is a BYO-account ` +\n    `runtime, it supplies its own env — these rows will not be delivered to it.`\n  );\n}\n\nexport function registerSecrets(builder: BosBuilder) {\n  return {\n    secretsSet: builder.secretsSet.effect(function* (_opts, input) {\n      const session = yield* ResolutionSessionService;\n      const sessions = yield* SessionStore;\n      const siteClient = yield* SiteClient;\n\n      const configDir = session?.root ?? process.cwd();\n      const credential = sessions.read(configDir)?.credential;\n      if (!credential?.apiKey) {\n        return { status: \"error\" as const, error: \"No CLI credential — run `bos login` first\" };\n      }\n      const siteUrl = input.site ?? credential.siteUrl;\n      if (!siteUrl) {\n        return {\n          status: \"error\" as const,\n          error: \"No vault site — pass --site or re-run `bos login`\",\n        };\n      }\n\n      if (!isValidSecretName(input.name)) {\n        return {\n          status: \"error\" as const,\n          error: isReservedSecretName(input.name)\n            ? `Secret name ${input.name} is reserved (BOS_* and PORT are composed by the platform)`\n            : \"Secret name must be UPPER_SNAKE_CASE env-var style\",\n        };\n      }\n\n      const value =\n        input.value ??\n        (yield* Effect.promise(() =>\n          p.password({ message: `Value for ${input.name} (input hidden)` }),\n        ));\n      if (p.isCancel(value)) process.exit(0);\n      if (!value) {\n        return { status: \"error\" as const, error: \"Empty secret value — nothing stored\" };\n      }\n\n      const account = session?.config?.account;\n      const gateway = session?.config?.domain;\n      if (!account || !gateway) {\n        return {\n          status: \"error\" as const,\n          error: \"No declared runtime identity — the authored config's account/domain are required\",\n        };\n      }\n\n      const response = yield* Effect.promise(() =>\n        siteClient.rpcPost(\n          siteUrl,\n          \"/api/rpc/secrets/set\",\n          { account, gateway, name: input.name, value },\n          undefined,\n          { \"x-api-key\": credential.apiKey },\n        ),\n      );\n      if (response.status !== 200) {\n        const detail = parseSiteErrorDetail(response.status, response.json);\n        return { status: \"error\" as const, error: detail ?? `Vault refused (${response.status})` };\n      }\n      return {\n        status: \"set\" as const,\n        name: input.name,\n        siteUrl,\n        warning:\n          hostingRelationshipWarning(session?.config, siteUrl, account, gateway) ?? undefined,\n      } satisfies SecretsSetResult;\n    }),\n\n    secretsList: builder.secretsList.effect(function* (_opts, input) {\n      const session = yield* ResolutionSessionService;\n      const sessions = yield* SessionStore;\n      const siteClient = yield* SiteClient;\n\n      const configDir = session?.root ?? process.cwd();\n      const credential = sessions.read(configDir)?.credential;\n      if (!credential?.apiKey) {\n        return { status: \"error\" as const, error: \"No CLI credential — run `bos login` first\" };\n      }\n      const siteUrl = input.site ?? credential.siteUrl;\n      if (!siteUrl) {\n        return {\n          status: \"error\" as const,\n          error: \"No vault site — pass --site or re-run `bos login`\",\n        };\n      }\n\n      const account = input.account ?? session?.config?.account;\n      const gateway = input.gateway ?? session?.config?.domain;\n      if (!account || !gateway) {\n        return {\n          status: \"error\" as const,\n          error: \"No target runtime — pass --account/--gateway or run from the runtime's repo\",\n        };\n      }\n\n      const response = yield* Effect.promise(() =>\n        siteClient.rpcPost(\n          siteUrl,\n          \"/api/rpc/secrets/list\",\n          { account, gateway, declared: collectDeclaredSecrets(session?.config) },\n          undefined,\n          { \"x-api-key\": credential.apiKey },\n        ),\n      );\n      if (response.status !== 200) {\n        const detail = parseSiteErrorDetail(response.status, response.json);\n        return { status: \"error\" as const, error: detail ?? `Vault refused (${response.status})` };\n      }\n      const body = (\n        response.json as { json?: { secrets?: SecretsListResult[\"secrets\"]; missing?: string[] } }\n      ).json;\n      return {\n        status: \"listed\" as const,\n        secrets: body?.secrets ?? [],\n        missing: body?.missing ?? [],\n        siteUrl,\n        warning:\n          hostingRelationshipWarning(session?.config, siteUrl, account, gateway) ?? undefined,\n      } satisfies SecretsListResult;\n    }),\n\n    secretsRemove: builder.secretsRemove.effect(function* (_opts, input) {\n      const session = yield* ResolutionSessionService;\n      const sessions = yield* SessionStore;\n      const siteClient = yield* SiteClient;\n\n      const configDir = session?.root ?? process.cwd();\n      const credential = sessions.read(configDir)?.credential;\n      if (!credential?.apiKey) {\n        return { status: \"error\" as const, error: \"No CLI credential — run `bos login` first\" };\n      }\n      const siteUrl = credential.siteUrl;\n      if (!siteUrl) {\n        return { status: \"error\" as const, error: \"No vault site — re-run `bos login`\" };\n      }\n\n      if (!input.yes) {\n        const confirmed = yield* Effect.promise(() =>\n          p.confirm({ message: `Remove ${input.name} from the vault at ${siteUrl}?` }),\n        );\n        if (p.isCancel(confirmed)) process.exit(0);\n        if (!confirmed) {\n          return {\n            status: \"error\" as const,\n            name: input.name,\n            error: \"Cancelled — nothing removed\",\n          };\n        }\n      }\n\n      const account = session?.config?.account;\n      const gateway = session?.config?.domain;\n      if (!account || !gateway) {\n        return {\n          status: \"error\" as const,\n          error: \"No declared runtime identity — the authored config's account/domain are required\",\n        };\n      }\n\n      const response = yield* Effect.promise(() =>\n        siteClient.rpcPost(\n          siteUrl,\n          \"/api/rpc/secrets/remove\",\n          { account, gateway, name: input.name },\n          undefined,\n          { \"x-api-key\": credential.apiKey },\n        ),\n      );\n      if (response.status !== 200) {\n        const detail = parseSiteErrorDetail(response.status, response.json);\n        return { status: \"error\" as const, error: detail ?? `Vault refused (${response.status})` };\n      }\n      const body = (response.json as { json?: { removed?: boolean } }).json;\n      return body?.removed\n        ? ({ status: \"removed\" as const, name: input.name } satisfies SecretsRemoveResult)\n        : ({ status: \"not-found\" as const, name: input.name } satisfies SecretsRemoveResult);\n    }),\n  };\n}\n\nexport const secretsRenderers: Record<string, ResultRenderer> = {\n  secretsSet: async (result: SecretsSetResult) => {\n    console.log();\n    if (result.status === \"set\") {\n      console.log(`  ${colors.green(`${icons.ok} Secret ${result.name} stored`)}`);\n      console.log(`  ${colors.dim(`Vault: ${result.siteUrl}`)}`);\n      console.log(`  ${colors.dim(\"The value is sealed at rest and never echoed back\")}`);\n      if (result.warning) console.log(`  ${colors.yellow(\"⚠\")} ${result.warning}`);\n    } else {\n      console.log(`  ${colors.error(`${icons.err} ${result.error ?? \"Failed to store secret\"}`)}`);\n    }\n    console.log();\n  },\n\n  secretsList: async (result: SecretsListResult) => {\n    console.log();\n    if (result.status !== \"listed\") {\n      console.log(`  ${colors.error(`${icons.err} ${result.error ?? \"Failed to list secrets\"}`)}`);\n      console.log();\n      return;\n    }\n    console.log(\n      `  ${colors.green(`${icons.ok} Secrets`)} ${colors.dim(`vault: ${result.siteUrl}`)}`,\n    );\n    if (result.warning) console.log(`  ${colors.yellow(\"⚠\")} ${result.warning}`);\n    const rows = result.secrets ?? [];\n    if (rows.length === 0) {\n      console.log(`  ${colors.dim(\"No secrets stored and nothing declared\")}`);\n    }\n    for (const secret of rows) {\n      const mark =\n        secret.state === \"set\"\n          ? colors.green(\"set\")\n          : secret.state === \"missing\"\n            ? colors.yellow(\"missing\")\n            : colors.dim(\"undeclared\");\n      console.log(`  ${colors.dim(secret.name.padEnd(24))} ${mark}`);\n    }\n    if ((result.missing ?? []).length > 0) {\n      console.log();\n      console.log(\n        `  ${colors.yellow(\"⚠\")} Missing: ${result.missing!.join(\", \")} — set them with \\`bos secrets set NAME\\``,\n      );\n    }\n    console.log();\n  },\n\n  secretsRemove: async (result: SecretsRemoveResult) => {\n    console.log();\n    if (result.status === \"removed\") {\n      console.log(`  ${colors.green(`${icons.ok} Removed ${result.name}`)}`);\n    } else if (result.status === \"not-found\") {\n      console.log(`  ${colors.yellow(\"⚠\")} ${result.name} was not stored`);\n    } else {\n      console.log(`  ${colors.error(`${icons.err} ${result.error ?? \"Failed to remove\"}`)}`);\n    }\n    console.log();\n  },\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,SAAS,uBAAuB,QAA2B;CACzD,MAAM,MAAM;CAKZ,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,UAAU,IAAI,WAAW,CAAC,GAAG,SAAS,IAAI,MAAM;CAC3D,KAAK,MAAM,WAAW,OAAO,OAAO,IAAI,OAAO,CAAC,CAAC,GAC/C,KAAK,MAAM,UAAU,SAAS,WAAW,CAAC,GAAG,SAAS,IAAI,MAAM;CAElE,KAAK,MAAM,UAAU,OAAO,OAAO,IAAI,WAAW,CAAC,CAAC,GAClD,KAAK,MAAM,UAAU,QAAQ,WAAW,CAAC,GAAG,SAAS,IAAI,MAAM;CAEjE,OAAO,CAAC,GAAG,QAAQ;AACrB;;;;;;;;AASA,SAAS,2BACP,QACA,SACA,SACA,SACe;CACf,MAAM,MAAM;CACZ,IAAI,KAAK,YAAY,WAAW,KAAK,WAAW,SAAS,OAAO;CAChE,MAAM,cAAc,KAAK,UAAU,KAAK,WAAW,EAAE;CACrD,MAAM,WAAW,IAAI,IAAI,OAAO,CAAC,CAAC;CAClC,IAAI,YAAY,SAAS,QAAQ,KAAK,YAAY,SAAS,IAAI,IAAI,OAAO,CAAC,CAAC,QAAQ,GAClF,OAAO;CAET,OACE,uBAAuB,QAAQ,GAAG,QAAQ,oGACU,SAAS;AAGjE;AAEA,SAAgB,gBAAgB,SAAqB;CACnD,OAAO;EACL,YAAY,QAAQ,WAAW,OAAO,WAAW,OAAO,OAAO;GAC7D,MAAM,UAAU,OAAO;GACvB,MAAM,WAAW,OAAO;GACxB,MAAM,aAAa,OAAO;GAE1B,MAAM,YAAY,SAAS,QAAQ,QAAQ,IAAI;GAC/C,MAAM,aAAa,SAAS,KAAK,SAAS,CAAC,EAAE;GAC7C,IAAI,CAAC,YAAY,QACf,OAAO;IAAE,QAAQ;IAAkB,OAAO;GAA4C;GAExF,MAAM,UAAU,MAAM,QAAQ,WAAW;GACzC,IAAI,CAAC,SACH,OAAO;IACL,QAAQ;IACR,OAAO;GACT;GAGF,IAAI,CAAC,kBAAkB,MAAM,IAAI,GAC/B,OAAO;IACL,QAAQ;IACR,OAAO,qBAAqB,MAAM,IAAI,IAClC,eAAe,MAAM,KAAK,8DAC1B;GACN;GAGF,MAAM,QACJ,MAAM,UACL,OAAO,OAAO,cACb,EAAE,SAAS,EAAE,SAAS,aAAa,MAAM,KAAK,iBAAiB,CAAC,CAClE;GACF,IAAI,EAAE,SAAS,KAAK,GAAG,QAAQ,KAAK,CAAC;GACrC,IAAI,CAAC,OACH,OAAO;IAAE,QAAQ;IAAkB,OAAO;GAAsC;GAGlF,MAAM,UAAU,SAAS,QAAQ;GACjC,MAAM,UAAU,SAAS,QAAQ;GACjC,IAAI,CAAC,WAAW,CAAC,SACf,OAAO;IACL,QAAQ;IACR,OAAO;GACT;GAGF,MAAM,WAAW,OAAO,OAAO,cAC7B,WAAW,QACT,SACA,wBACA;IAAE;IAAS;IAAS,MAAM,MAAM;IAAM;GAAM,GAC5C,QACA,EAAE,aAAa,WAAW,OAAO,CACnC,CACF;GACA,IAAI,SAAS,WAAW,KAEtB,OAAO;IAAE,QAAQ;IAAkB,OADpB,qBAAqB,SAAS,QAAQ,SAAS,IACf,KAAK,kBAAkB,SAAS,OAAO;GAAG;GAE3F,OAAO;IACL,QAAQ;IACR,MAAM,MAAM;IACZ;IACA,SACE,2BAA2B,SAAS,QAAQ,SAAS,SAAS,OAAO,KAAK;GAC9E;EACF,CAAC;EAED,aAAa,QAAQ,YAAY,OAAO,WAAW,OAAO,OAAO;GAC/D,MAAM,UAAU,OAAO;GACvB,MAAM,WAAW,OAAO;GACxB,MAAM,aAAa,OAAO;GAE1B,MAAM,YAAY,SAAS,QAAQ,QAAQ,IAAI;GAC/C,MAAM,aAAa,SAAS,KAAK,SAAS,CAAC,EAAE;GAC7C,IAAI,CAAC,YAAY,QACf,OAAO;IAAE,QAAQ;IAAkB,OAAO;GAA4C;GAExF,MAAM,UAAU,MAAM,QAAQ,WAAW;GACzC,IAAI,CAAC,SACH,OAAO;IACL,QAAQ;IACR,OAAO;GACT;GAGF,MAAM,UAAU,MAAM,WAAW,SAAS,QAAQ;GAClD,MAAM,UAAU,MAAM,WAAW,SAAS,QAAQ;GAClD,IAAI,CAAC,WAAW,CAAC,SACf,OAAO;IACL,QAAQ;IACR,OAAO;GACT;GAGF,MAAM,WAAW,OAAO,OAAO,cAC7B,WAAW,QACT,SACA,yBACA;IAAE;IAAS;IAAS,UAAU,uBAAuB,SAAS,MAAM;GAAE,GACtE,QACA,EAAE,aAAa,WAAW,OAAO,CACnC,CACF;GACA,IAAI,SAAS,WAAW,KAEtB,OAAO;IAAE,QAAQ;IAAkB,OADpB,qBAAqB,SAAS,QAAQ,SAAS,IACf,KAAK,kBAAkB,SAAS,OAAO;GAAG;GAE3F,MAAM,OACJ,SAAS,KACT;GACF,OAAO;IACL,QAAQ;IACR,SAAS,MAAM,WAAW,CAAC;IAC3B,SAAS,MAAM,WAAW,CAAC;IAC3B;IACA,SACE,2BAA2B,SAAS,QAAQ,SAAS,SAAS,OAAO,KAAK;GAC9E;EACF,CAAC;EAED,eAAe,QAAQ,cAAc,OAAO,WAAW,OAAO,OAAO;GACnE,MAAM,UAAU,OAAO;GACvB,MAAM,WAAW,OAAO;GACxB,MAAM,aAAa,OAAO;GAE1B,MAAM,YAAY,SAAS,QAAQ,QAAQ,IAAI;GAC/C,MAAM,aAAa,SAAS,KAAK,SAAS,CAAC,EAAE;GAC7C,IAAI,CAAC,YAAY,QACf,OAAO;IAAE,QAAQ;IAAkB,OAAO;GAA4C;GAExF,MAAM,UAAU,WAAW;GAC3B,IAAI,CAAC,SACH,OAAO;IAAE,QAAQ;IAAkB,OAAO;GAAqC;GAGjF,IAAI,CAAC,MAAM,KAAK;IACd,MAAM,YAAY,OAAO,OAAO,cAC9B,EAAE,QAAQ,EAAE,SAAS,UAAU,MAAM,KAAK,qBAAqB,QAAQ,GAAG,CAAC,CAC7E;IACA,IAAI,EAAE,SAAS,SAAS,GAAG,QAAQ,KAAK,CAAC;IACzC,IAAI,CAAC,WACH,OAAO;KACL,QAAQ;KACR,MAAM,MAAM;KACZ,OAAO;IACT;GAEJ;GAEA,MAAM,UAAU,SAAS,QAAQ;GACjC,MAAM,UAAU,SAAS,QAAQ;GACjC,IAAI,CAAC,WAAW,CAAC,SACf,OAAO;IACL,QAAQ;IACR,OAAO;GACT;GAGF,MAAM,WAAW,OAAO,OAAO,cAC7B,WAAW,QACT,SACA,2BACA;IAAE;IAAS;IAAS,MAAM,MAAM;GAAK,GACrC,QACA,EAAE,aAAa,WAAW,OAAO,CACnC,CACF;GACA,IAAI,SAAS,WAAW,KAEtB,OAAO;IAAE,QAAQ;IAAkB,OADpB,qBAAqB,SAAS,QAAQ,SAAS,IACf,KAAK,kBAAkB,SAAS,OAAO;GAAG;GAG3F,OADc,SAAS,KAA0C,MACpD,UACR;IAAE,QAAQ;IAAoB,MAAM,MAAM;GAAK,IAC/C;IAAE,QAAQ;IAAsB,MAAM,MAAM;GAAK;EACxD,CAAC;CACH;AACF;AAEA,MAAa,mBAAmD;CAC9D,YAAY,OAAO,WAA6B;EAC9C,QAAQ,IAAI;EACZ,IAAI,OAAO,WAAW,OAAO;GAC3B,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG,MAAM,GAAG,UAAU,OAAO,KAAK,QAAQ,GAAG;GAC3E,QAAQ,IAAI,KAAK,OAAO,IAAI,UAAU,OAAO,SAAS,GAAG;GACzD,QAAQ,IAAI,KAAK,OAAO,IAAI,mDAAmD,GAAG;GAClF,IAAI,OAAO,SAAS,QAAQ,IAAI,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,SAAS;EAC7E,OACE,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG,MAAM,IAAI,GAAG,OAAO,SAAS,0BAA0B,GAAG;EAE7F,QAAQ,IAAI;CACd;CAEA,aAAa,OAAO,WAA8B;EAChD,QAAQ,IAAI;EACZ,IAAI,OAAO,WAAW,UAAU;GAC9B,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG,MAAM,IAAI,GAAG,OAAO,SAAS,0BAA0B,GAAG;GAC3F,QAAQ,IAAI;GACZ;EACF;EACA,QAAQ,IACN,KAAK,OAAO,MAAM,GAAG,MAAM,GAAG,SAAS,EAAE,GAAG,OAAO,IAAI,UAAU,OAAO,SAAS,GACnF;EACA,IAAI,OAAO,SAAS,QAAQ,IAAI,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,SAAS;EAC3E,MAAM,OAAO,OAAO,WAAW,CAAC;EAChC,IAAI,KAAK,WAAW,GAClB,QAAQ,IAAI,KAAK,OAAO,IAAI,wCAAwC,GAAG;EAEzE,KAAK,MAAM,UAAU,MAAM;GACzB,MAAM,OACJ,OAAO,UAAU,QACb,OAAO,MAAM,KAAK,IAClB,OAAO,UAAU,YACf,OAAO,OAAO,SAAS,IACvB,OAAO,IAAI,YAAY;GAC/B,QAAQ,IAAI,KAAK,OAAO,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC,EAAE,GAAG,MAAM;EAC/D;EACA,KAAK,OAAO,WAAW,CAAC,EAAC,CAAE,SAAS,GAAG;GACrC,QAAQ,IAAI;GACZ,QAAQ,IACN,KAAK,OAAO,OAAO,GAAG,EAAE,YAAY,OAAO,QAAS,KAAK,IAAI,EAAE,0CACjE;EACF;EACA,QAAQ,IAAI;CACd;CAEA,eAAe,OAAO,WAAgC;EACpD,QAAQ,IAAI;EACZ,IAAI,OAAO,WAAW,WACpB,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG,MAAM,GAAG,WAAW,OAAO,MAAM,GAAG;OAChE,IAAI,OAAO,WAAW,aAC3B,QAAQ,IAAI,KAAK,OAAO,OAAO,GAAG,EAAE,GAAG,OAAO,KAAK,gBAAgB;OAEnE,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG,MAAM,IAAI,GAAG,OAAO,SAAS,oBAAoB,GAAG;EAEvF,QAAQ,IAAI;CACd;AACF"}