{"version":3,"file":"keys.mjs","names":[],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context, Effect } from \"effect\";\nimport { type KeyPair, parseKey } from \"near-kit\";\nimport { openInBrowser, startDeviceLogin } from \"../auth-login\";\nimport {\n  deleteSessionHandle,\n  nearCredentialsPath,\n  readSessionHandle,\n  removePublishedKeyFile,\n  type SessionCredential,\n  writeSessionHandle,\n} from \"../auth-session\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { LoginResult } from \"../contract\";\nimport { getRegistryNamespaceForAccount } from \"../fastkv\";\nimport {\n  addFunctionCallAccessKey,\n  deleteAccessKeys,\n  ensureNearCli,\n  generateNearKeyPair,\n  listPublishKeys,\n} from \"../near-cli\";\nimport { getNetworkIdForAccount } from \"../network\";\nimport type { ResolutionSession } from \"../resolution/session\";\nimport { colors } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nconst PUBLISH_FUNCTION_NAMES = [\"__fastdata_kv\"];\n\nexport function registerKeys(builder: BosBuilder) {\n  return {\n    keyPublish: builder.keyPublish.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const session = deps.session;\n      if (!session?.config) {\n        return {\n          status: \"error\" as const,\n          account: \"\",\n          network: \"mainnet\" as const,\n          env: input.env,\n          contract: \"\",\n          allowance: input.allowance,\n          functionNames: PUBLISH_FUNCTION_NAMES,\n          error: MISSING_CONFIG_MESSAGE,\n        };\n      }\n\n      const account =\n        input.env === \"staging\"\n          ? (session.config.staging?.account ?? session.config.account)\n          : session.config.account;\n      const network = getNetworkIdForAccount(account);\n      const contract = getRegistryNamespaceForAccount(account, input.registry);\n      try {\n        await Effect.runPromise(ensureNearCli);\n\n        const oldKeys = await listPublishKeys({ account, contract, network });\n\n        const keyPair = await addFunctionCallAccessKey({\n          account,\n          contract,\n          allowance: input.allowance,\n          functionNames: PUBLISH_FUNCTION_NAMES,\n          network,\n        });\n\n        if (oldKeys.length > 0) {\n          console.log();\n          console.log(\n            `  Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? \"s\" : \"\"}:`,\n          );\n          for (const k of oldKeys) {\n            console.log(`    ${colors.dim(k)}`);\n          }\n\n          if (input.removeOldKeys !== false) {\n            try {\n              await deleteAccessKeys(account, oldKeys, network);\n              console.log(\n                `  ${colors.green(\"✓\")} Removed ${oldKeys.length} old key${\n                  oldKeys.length > 1 ? \"s\" : \"\"\n                }`,\n              );\n            } catch {\n              console.log(\n                `  ${colors.yellow(\"⚠\")} Failed to remove old key${\n                  oldKeys.length > 1 ? \"s\" : \"\"\n                } (new key still active)`,\n              );\n            }\n          } else {\n            console.log(`  ${colors.dim(\"Old key(s) retained.\")}`);\n          }\n        }\n\n        return {\n          status: \"published\" as const,\n          account,\n          network,\n          env: input.env,\n          contract,\n          allowance: input.allowance,\n          functionNames: PUBLISH_FUNCTION_NAMES,\n          publicKey: keyPair.publicKey,\n          privateKey: keyPair.privateKey,\n        };\n      } catch (error) {\n        return {\n          status: \"error\" as const,\n          account,\n          network,\n          env: input.env,\n          contract,\n          allowance: input.allowance,\n          functionNames: PUBLISH_FUNCTION_NAMES,\n          error: error instanceof Error ? error.message : \"Unknown error\",\n        };\n      }\n    }),\n\n    login: builder.login.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const staging = input.env === \"staging\";\n      const account = staging\n        ? (deps.session?.config?.staging?.account ?? deps.session?.config?.account ?? \"\")\n        : (deps.session?.config?.account ?? \"\");\n\n      try {\n        const siteUrl = resolveLoginSiteUrl(input.site, deps.session, staging);\n        const login = await startDeviceLogin({\n          siteUrl,\n          device: input.device,\n          account: account || undefined,\n          expiresIn: input.expiresIn,\n        });\n\n        console.log();\n        console.log(`  One-time code: ${colors.cyan(login.userCode)}`);\n        await openInBrowser(login.verificationUrl).catch((error: unknown) => {\n          console.log(colors.yellow(`  ⚠ ${(error as Error).message}`));\n        });\n        console.log();\n        console.log(`  Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n        const approval = await login.waitForApproval();\n\n        if (!approval.apiKey) {\n          return {\n            status: \"error\" as const,\n            siteUrl,\n            loginUrl: login.verificationUrl,\n            error: \"Login approved but no CLI credential was created\",\n          };\n        }\n\n        const expiresAt = new Date(Date.now() + input.expiresIn * 1000).toISOString();\n        writeSessionHandle(deps.session?.root ?? process.cwd(), {\n          version: 1,\n          credential: {\n            kind: \"session\",\n            apiKey: approval.apiKey.key,\n            apiKeyId: approval.apiKey.id,\n            accountId: approval.accountId,\n            label: input.device ?? siteUrl,\n            siteUrl,\n            createdAt: new Date().toISOString(),\n            expiresAt,\n          },\n          publishKey: null,\n          delegateKey: null,\n        });\n\n        let warning: string | null = null;\n        if (account && approval.accountId && approval.accountId !== account) {\n          warning = `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n        }\n\n        let publishKey: LoginResult[\"publishKey\"] = null;\n        if (input.key) {\n          try {\n            publishKey = await exportPublishKey(account, input.registry);\n          } catch (error) {\n            warning = `Session stored, but publish-key export failed: ${\n              error instanceof Error ? error.message : \"unknown error\"\n            }`;\n          }\n        }\n\n        return {\n          status: \"logged-in\" as const,\n          siteUrl,\n          accountId: approval.accountId,\n          expiresAt,\n          loginUrl: login.verificationUrl,\n          publishKey,\n          warning,\n        };\n      } catch (error) {\n        return {\n          status: \"error\" as const,\n          siteUrl: input.site ?? \"\",\n          error: error instanceof Error ? error.message : \"Unknown error\",\n        };\n      }\n    }),\n\n    logout: builder.logout.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const configDir = input.configDir ?? deps.session?.root ?? process.cwd();\n\n      try {\n        const session = readSessionHandle(configDir);\n        let revokedApiKey = false;\n        let removedPublishKey = false;\n        let warning: string | null = null;\n\n        if (session?.credential) {\n          revokedApiKey = await revokeApiKey(session.credential).catch(() => false);\n          if (!revokedApiKey) {\n            warning =\n              \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n          }\n        }\n\n        if (session?.publishKey) {\n          const account = session.credential?.accountId ?? deps.session?.config?.account ?? \"\";\n          removePublishedKeyFile(session.publishKey.network, account, session.publishKey.publicKey);\n          removedPublishKey = true;\n          warning = warning\n            ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n            : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n        }\n\n        deleteSessionHandle(configDir);\n        return {\n          status: \"logged-out\" as const,\n          revokedApiKey,\n          removedPublishKey,\n          warning,\n        };\n      } catch (error) {\n        return {\n          status: \"error\" as const,\n          revokedApiKey: false,\n          removedPublishKey: false,\n          error: error instanceof Error ? error.message : \"Unknown error\",\n        };\n      }\n    }),\n  };\n}\n\nfunction resolveLoginSiteUrl(\n  siteInput: string | undefined,\n  session: ResolutionSession | null,\n  staging: boolean,\n): string {\n  if (siteInput) {\n    return siteInput.replace(/\\/+$/, \"\");\n  }\n  if (staging) {\n    const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n    return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n  }\n  const devUiUrl = session?.runtime?.ui?.url;\n  if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n    return devUiUrl.replace(/\\/+$/, \"\");\n  }\n  const domain = session?.config?.domain;\n  return domain ? `https://${domain}` : \"\";\n}\n\nasync function exportPublishKey(\n  account: string,\n  registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n  if (!account) {\n    throw new Error(\"the authored config has no account to export a publish key for\");\n  }\n\n  await Effect.runPromise(ensureNearCli);\n\n  const network = getNetworkIdForAccount(account);\n  const contract = getRegistryNamespaceForAccount(account, registry);\n  await listPublishKeys({ account, contract, network });\n\n  const keyPair = generateNearKeyPair();\n  await addFunctionCallAccessKey({\n    account,\n    contract,\n    allowance: \"1NEAR\",\n    functionNames: PUBLISH_FUNCTION_NAMES,\n    network,\n    keyPair,\n  });\n\n  const { FileKeyStore } = await import(\"near-kit/keys/file\");\n  const keyStore = new FileKeyStore(\"~/.near-credentials\", network);\n  await keyStore.add(account, parseNearPrivateKey(keyPair.privateKey));\n  try {\n    const { statSync, chmodSync } = await import(\"node:fs\");\n    const { homedir } = await import(\"node:os\");\n    const credentialPath = join(\n      homedir(),\n      \".near-credentials\",\n      network === \"mainnet\" ? \"mainnet\" : network,\n      `${account}.json`,\n    );\n    if (statSync(credentialPath).mode & 0o077) {\n      chmodSync(credentialPath, 0o600);\n    }\n  } catch {}\n\n  return {\n    publicKey: keyPair.publicKey,\n    network,\n    contract,\n    exportedTo: nearCredentialsPath(network, account),\n  };\n}\nfunction parseNearPrivateKey(privateKey: string): KeyPair {\n  return parseKey(privateKey) as KeyPair;\n}\n\nasync function revokeApiKey(credential: SessionCredential): Promise<boolean> {\n  if (!credential.apiKey || !credential.siteUrl) return false;\n  try {\n    const response = await fetch(`${credential.siteUrl}/api/auth/api-key/delete`, {\n      method: \"POST\",\n      headers: {\n        \"x-api-key\": credential.apiKey,\n        \"content-type\": \"application/json\",\n      },\n      body: JSON.stringify({ keyId: credential.apiKeyId }),\n      signal: AbortSignal.timeout(10_000),\n    });\n    return response.ok;\n  } catch {\n    return false;\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,MAAM,yBAAyB,CAAC,eAAe;AAE/C,SAAgB,aAAa,SAAqB;CAChD,OAAO;EACL,YAAY,QAAQ,WAAW,QAAQ,OAAO,EAAE,OAAO,cAAc;GAEnE,MAAM,UADO,QAAQ,IAAI,QAAQ,mBAAmB,UACjC,CAAC,CAAC;GACrB,IAAI,CAAC,SAAS,QACZ,OAAO;IACL,QAAQ;IACR,SAAS;IACT,SAAS;IACT,KAAK,MAAM;IACX,UAAU;IACV,WAAW,MAAM;IACjB,eAAe;IACf,OAAO;GACT;GAGF,MAAM,UACJ,MAAM,QAAQ,YACT,QAAQ,OAAO,SAAS,WAAW,QAAQ,OAAO,UACnD,QAAQ,OAAO;GACrB,MAAM,UAAU,uBAAuB,OAAO;GAC9C,MAAM,WAAW,+BAA+B,SAAS,MAAM,QAAQ;GACvE,IAAI;IACF,MAAM,OAAO,WAAW,aAAa;IAErC,MAAM,UAAU,MAAM,gBAAgB;KAAE;KAAS;KAAU;IAAQ,CAAC;IAEpE,MAAM,UAAU,MAAM,yBAAyB;KAC7C;KACA;KACA,WAAW,MAAM;KACjB,eAAe;KACf;IACF,CAAC;IAED,IAAI,QAAQ,SAAS,GAAG;KACtB,QAAQ,IAAI;KACZ,QAAQ,IACN,WAAW,QAAQ,OAAO,uBAAuB,QAAQ,SAAS,IAAI,MAAM,GAAG,EACjF;KACA,KAAK,MAAM,KAAK,SACd,QAAQ,IAAI,OAAO,OAAO,IAAI,CAAC,GAAG;KAGpC,IAAI,MAAM,kBAAkB,OAC1B,IAAI;MACF,MAAM,iBAAiB,SAAS,SAAS,OAAO;MAChD,QAAQ,IACN,KAAK,OAAO,MAAM,GAAG,EAAE,WAAW,QAAQ,OAAO,UAC/C,QAAQ,SAAS,IAAI,MAAM,IAE/B;KACF,QAAQ;MACN,QAAQ,IACN,KAAK,OAAO,OAAO,GAAG,EAAE,2BACtB,QAAQ,SAAS,IAAI,MAAM,GAC5B,wBACH;KACF;UAEA,QAAQ,IAAI,KAAK,OAAO,IAAI,sBAAsB,GAAG;IAEzD;IAEA,OAAO;KACL,QAAQ;KACR;KACA;KACA,KAAK,MAAM;KACX;KACA,WAAW,MAAM;KACjB,eAAe;KACf,WAAW,QAAQ;KACnB,YAAY,QAAQ;IACtB;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR;KACA;KACA,KAAK,MAAM;KACX;KACA,WAAW,MAAM;KACjB,eAAe;KACf,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;EAED,OAAO,QAAQ,MAAM,QAAQ,OAAO,EAAE,OAAO,cAAc;GACzD,MAAM,OAAO,QAAQ,IAAI,QAAQ,mBAAmB,UAAU;GAC9D,MAAM,UAAU,MAAM,QAAQ;GAC9B,MAAM,UAAU,UACX,KAAK,SAAS,QAAQ,SAAS,WAAW,KAAK,SAAS,QAAQ,WAAW,KAC3E,KAAK,SAAS,QAAQ,WAAW;GAEtC,IAAI;IACF,MAAM,UAAU,oBAAoB,MAAM,MAAM,KAAK,SAAS,OAAO;IACrE,MAAM,QAAQ,MAAM,iBAAiB;KACnC;KACA,QAAQ,MAAM;KACd,SAAS,WAAW;KACpB,WAAW,MAAM;IACnB,CAAC;IAED,QAAQ,IAAI;IACZ,QAAQ,IAAI,oBAAoB,OAAO,KAAK,MAAM,QAAQ,GAAG;IAC7D,MAAM,cAAc,MAAM,eAAe,CAAC,CAAC,OAAO,UAAmB;KACnE,QAAQ,IAAI,OAAO,OAAO,OAAQ,MAAgB,SAAS,CAAC;IAC9D,CAAC;IACD,QAAQ,IAAI;IACZ,QAAQ,IAAI,6BAA6B,OAAO,IAAI,MAAM,eAAe,EAAE,EAAE;IAE7E,MAAM,WAAW,MAAM,MAAM,gBAAgB;IAE7C,IAAI,CAAC,SAAS,QACZ,OAAO;KACL,QAAQ;KACR;KACA,UAAU,MAAM;KAChB,OAAO;IACT;IAGF,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,YAAY,GAAI,CAAC,CAAC,YAAY;IAC5E,mBAAmB,KAAK,SAAS,QAAQ,QAAQ,IAAI,GAAG;KACtD,SAAS;KACT,YAAY;MACV,MAAM;MACN,QAAQ,SAAS,OAAO;MACxB,UAAU,SAAS,OAAO;MAC1B,WAAW,SAAS;MACpB,OAAO,MAAM,UAAU;MACvB;MACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;MAClC;KACF;KACA,YAAY;KACZ,aAAa;IACf,CAAC;IAED,IAAI,UAAyB;IAC7B,IAAI,WAAW,SAAS,aAAa,SAAS,cAAc,SAC1D,UAAU,gBAAgB,SAAS,UAAU,uCAAuC,QAAQ;IAG9F,IAAI,aAAwC;IAC5C,IAAI,MAAM,KACR,IAAI;KACF,aAAa,MAAM,iBAAiB,SAAS,MAAM,QAAQ;IAC7D,SAAS,OAAO;KACd,UAAU,kDACR,iBAAiB,QAAQ,MAAM,UAAU;IAE7C;IAGF,OAAO;KACL,QAAQ;KACR;KACA,WAAW,SAAS;KACpB;KACA,UAAU,MAAM;KAChB;KACA;IACF;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR,SAAS,MAAM,QAAQ;KACvB,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;EAED,QAAQ,QAAQ,OAAO,QAAQ,OAAO,EAAE,OAAO,cAAc;GAC3D,MAAM,OAAO,QAAQ,IAAI,QAAQ,mBAAmB,UAAU;GAC9D,MAAM,YAAY,MAAM,aAAa,KAAK,SAAS,QAAQ,QAAQ,IAAI;GAEvE,IAAI;IACF,MAAM,UAAU,kBAAkB,SAAS;IAC3C,IAAI,gBAAgB;IACpB,IAAI,oBAAoB;IACxB,IAAI,UAAyB;IAE7B,IAAI,SAAS,YAAY;KACvB,gBAAgB,MAAM,aAAa,QAAQ,UAAU,CAAC,CAAC,YAAY,KAAK;KACxE,IAAI,CAAC,eACH,UACE;IAEN;IAEA,IAAI,SAAS,YAAY;KACvB,MAAM,UAAU,QAAQ,YAAY,aAAa,KAAK,SAAS,QAAQ,WAAW;KAClF,uBAAuB,QAAQ,WAAW,SAAS,SAAS,QAAQ,WAAW,SAAS;KACxF,oBAAoB;KACpB,UAAU,UACN,GAAG,QAAQ,UAAU,OAAO,KAAK,iBAAiB,EAAE,gCACpD;IACN;IAEA,oBAAoB,SAAS;IAC7B,OAAO;KACL,QAAQ;KACR;KACA;KACA;IACF;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR,eAAe;KACf,mBAAmB;KACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;CACH;AACF;AAEA,SAAS,oBACP,WACA,SACA,SACQ;CACR,IAAI,WACF,OAAO,UAAU,QAAQ,QAAQ,EAAE;CAErC,IAAI,SAAS;EACX,MAAM,gBAAgB,SAAS,QAAQ,SAAS,UAAU,SAAS,QAAQ;EAC3E,OAAO,gBAAgB,WAAW,kBAAmB,SAAS,SAAS,IAAI,OAAO;CACpF;CACA,MAAM,WAAW,SAAS,SAAS,IAAI;CACvC,IAAI,YAAY,uCAAuC,KAAK,QAAQ,GAClE,OAAO,SAAS,QAAQ,QAAQ,EAAE;CAEpC,MAAM,SAAS,SAAS,QAAQ;CAChC,OAAO,SAAS,WAAW,WAAW;AACxC;AAEA,eAAe,iBACb,SACA,UACiD;CACjD,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,gEAAgE;CAGlF,MAAM,OAAO,WAAW,aAAa;CAErC,MAAM,UAAU,uBAAuB,OAAO;CAC9C,MAAM,WAAW,+BAA+B,SAAS,QAAQ;CACjE,MAAM,gBAAgB;EAAE;EAAS;EAAU;CAAQ,CAAC;CAEpD,MAAM,UAAU,oBAAoB;CACpC,MAAM,yBAAyB;EAC7B;EACA;EACA,WAAW;EACX,eAAe;EACf;EACA;CACF,CAAC;CAED,MAAM,EAAE,iBAAiB,MAAM,OAAO;CAEtC,MAAM,IADe,aAAa,uBAAuB,OAC5C,CAAC,CAAC,IAAI,SAAS,oBAAoB,QAAQ,UAAU,CAAC;CACnE,IAAI;EACF,MAAM,EAAE,UAAU,cAAc,MAAM,OAAO;EAC7C,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,iBAAiB,KACrB,QAAQ,GACR,qBACA,YAAY,YAAY,YAAY,SACpC,GAAG,QAAQ,MACb;EACA,IAAI,SAAS,cAAc,CAAC,CAAC,OAAO,IAClC,UAAU,gBAAgB,GAAK;CAEnC,QAAQ,CAAC;CAET,OAAO;EACL,WAAW,QAAQ;EACnB;EACA;EACA,YAAY,oBAAoB,SAAS,OAAO;CAClD;AACF;AACA,SAAS,oBAAoB,YAA6B;CACxD,OAAO,SAAS,UAAU;AAC5B;AAEA,eAAe,aAAa,YAAiD;CAC3E,IAAI,CAAC,WAAW,UAAU,CAAC,WAAW,SAAS,OAAO;CACtD,IAAI;EAUF,QAAO,MATgB,MAAM,GAAG,WAAW,QAAQ,2BAA2B;GAC5E,QAAQ;GACR,SAAS;IACP,aAAa,WAAW;IACxB,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,SAAS,CAAC;GACnD,QAAQ,YAAY,QAAQ,GAAM;EACpC,CAAC,EACc,CAAC;CAClB,QAAQ;EACN,OAAO;CACT;AACF"}