#!/usr/bin/env node

// src/config/global-config.ts
import os2 from "os";
import path4 from "path";

// src/constants.ts
var DEFAULT_API_BASE_URL = "https://api.dreamboard.games";
var DEFAULT_WEB_BASE_URL = "https://dreamboard.games";
var PROJECT_DIR_NAME = ".dreamboard";
var DEFAULT_CLERK_OAUTH_SCOPE = "openid profile email offline_access";
var ENVIRONMENT_CONFIGS = {
  local: {
    apiBaseUrl: "http://localhost:8080",
    webBaseUrl: "http://localhost:5173",
    clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
  },
  staging: {
    apiBaseUrl: "https://api-staging.dreamboard.games",
    webBaseUrl: "https://staging.dreamboard.games",
    clerkOAuthIssuer: "https://happy-caribou-19.clerk.accounts.dev",
    clerkOAuthTokenUrl: "https://happy-caribou-19.clerk.accounts.dev/oauth/token",
    clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
  },
  prod: {
    apiBaseUrl: "https://api.dreamboard.games",
    webBaseUrl: "https://dreamboard.games",
    clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
  }
};
var PROJECT_CONFIG_FILE = "project.json";
var PROJECT_STATE_FILE = "state.json";
var SNAPSHOT_FILE = "snapshot.json";
var MANIFEST_FILE = "manifest.ts";
var GENERATED_DIR_NAME = "generated";
var MATERIALIZED_MANIFEST_FILE = `${PROJECT_DIR_NAME}/${GENERATED_DIR_NAME}/manifest.json`;
var MANIFEST_TYPECHECK_CONFIG_FILE = "manifest.tsconfig.json";
var RULE_FILE = "rule.md";
var DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
var LOCAL_IGNORE_DIRS = /* @__PURE__ */ new Set([
  ".dreamboard",
  ".git",
  "node_modules",
  "dist"
]);

// src/utils/fs.ts
import { mkdir, readFile, stat, writeFile } from "fs/promises";
import path from "path";
async function ensureDir(dirPath) {
  await mkdir(dirPath, { recursive: true });
}
async function exists(filePath) {
  try {
    await stat(filePath);
    return true;
  } catch {
    return false;
  }
}
async function readTextFile(filePath) {
  return readFile(filePath, "utf8");
}
async function readTextFileIfExists(filePath) {
  try {
    return await readFile(filePath, "utf8");
  } catch {
    return null;
  }
}
async function writeTextFile(filePath, content) {
  await ensureDir(path.dirname(filePath));
  await writeFile(filePath, content, "utf8");
}
async function readJsonFile(filePath) {
  const data = await readTextFile(filePath);
  return JSON.parse(data);
}
async function writeJsonFile(filePath, data) {
  await writeTextFile(filePath, `${JSON.stringify(data, null, 2)}
`);
}

// src/utils/atomic-file.ts
import { constants as fsConstants, promises as fs } from "fs";
import path2 from "path";
import crypto from "crypto";
async function atomicWriteFile(targetPath, contents, options = {}) {
  if (contents.length === 0) {
    throw new Error(
      `Refusing to atomicWriteFile an empty payload to ${targetPath}`
    );
  }
  const mode = options.mode ?? 384;
  const shouldFsync = options.fsync ?? true;
  const dir = path2.dirname(targetPath);
  await fs.mkdir(dir, { recursive: true });
  const suffix = crypto.randomBytes(6).toString("hex");
  const tmpPath = `${targetPath}.tmp-${process.pid}-${suffix}`;
  const fh = await fs.open(
    tmpPath,
    fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
    mode
  );
  try {
    await fh.writeFile(contents, "utf8");
    try {
      await fh.chmod(mode);
    } catch {
    }
    if (shouldFsync) {
      try {
        await fh.sync();
      } catch {
      }
    }
  } finally {
    await fh.close();
  }
  try {
    await fs.rename(tmpPath, targetPath);
  } catch (err) {
    await fs.unlink(tmpPath).catch(() => void 0);
    throw err;
  }
}
async function withFileLock(lockPath, fn, options = {}) {
  const retries = options.retries ?? 100;
  const minDelayMs = options.minDelayMs ?? 20;
  const maxDelayMs = options.maxDelayMs ?? 200;
  const staleMs = options.staleMs ?? 3e4;
  await fs.mkdir(path2.dirname(lockPath), { recursive: true });
  let attempt = 0;
  let acquired = false;
  while (!acquired) {
    try {
      const fh = await fs.open(
        lockPath,
        fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
        384
      );
      await fh.writeFile(`${process.pid}
`, "utf8");
      await fh.close();
      acquired = true;
      break;
    } catch (err) {
      const code = err.code;
      if (code !== "EEXIST") {
        throw err;
      }
    }
    let stat2 = null;
    try {
      stat2 = await fs.stat(lockPath);
    } catch {
      continue;
    }
    if (stat2 !== null) {
      const ageMs = Date.now() - stat2.mtimeMs;
      if (ageMs > staleMs) {
        await fs.unlink(lockPath).catch(() => void 0);
        continue;
      }
    }
    attempt += 1;
    if (attempt >= retries) {
      throw new Error(
        `Timed out acquiring file lock at ${lockPath} after ${retries} attempts.`
      );
    }
    const jitter = Math.floor(
      Math.random() * Math.max(1, maxDelayMs - minDelayMs)
    );
    await new Promise((resolve) => setTimeout(resolve, minDelayMs + jitter));
  }
  try {
    return await fn();
  } finally {
    await fs.unlink(lockPath).catch(() => void 0);
  }
}

// src/config/credential-store.ts
import os from "os";
import path3 from "path";
import { promises as fs2 } from "fs";
function getCredentialFilePath() {
  return path3.join(os.homedir(), PROJECT_DIR_NAME, "auth.json");
}
function getCredentialLockPath() {
  return `${getCredentialFilePath()}.lock`;
}
async function fileRead() {
  const filePath = getCredentialFilePath();
  let data;
  try {
    data = await fs2.readFile(filePath, "utf8");
  } catch (err) {
    if (err.code === "ENOENT") return null;
    throw err;
  }
  if (data.trim().length === 0) {
    return null;
  }
  let parsed;
  try {
    parsed = JSON.parse(data);
  } catch {
    return null;
  }
  const accessToken = parsed.accessToken ?? parsed.authToken;
  const refreshToken = parsed.refreshToken;
  if (!accessToken && !refreshToken) return null;
  return {
    accessToken: accessToken || void 0,
    refreshToken: refreshToken || void 0,
    tokenExpiresAt: parsed.tokenExpiresAt || void 0,
    clerkOAuthIssuer: parsed.clerkOAuthIssuer || void 0,
    clerkOAuthClientId: parsed.clerkOAuthClientId || void 0,
    clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || void 0,
    environment: parsed.environment || void 0
  };
}
async function writeFilePayload(payload) {
  await atomicWriteFile(
    getCredentialFilePath(),
    `${JSON.stringify(payload, null, 2)}
`,
    { mode: 384 }
  );
}
async function fileWriteFull(creds) {
  if (!creds.accessToken || !creds.refreshToken) {
    throw new Error(
      "Refusing to persist credentials with an empty accessToken or refreshToken."
    );
  }
  await writeFilePayload({
    authToken: creds.accessToken,
    refreshToken: creds.refreshToken,
    tokenExpiresAt: creds.tokenExpiresAt,
    clerkOAuthIssuer: creds.clerkOAuthIssuer,
    clerkOAuthClientId: creds.clerkOAuthClientId,
    clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,
    environment: creds.environment
  });
}
async function fileWriteAccessOnly(accessToken) {
  if (!accessToken) {
    throw new Error("Refusing to persist an empty access token.");
  }
  await writeFilePayload({ authToken: accessToken });
}
async function fileClear() {
  const filePath = getCredentialFilePath();
  try {
    await fs2.unlink(filePath);
  } catch (err) {
    if (err.code !== "ENOENT") throw err;
  }
}
var fileCredentialBackend = {
  name: "file",
  read: fileRead,
  writeFull: fileWriteFull,
  writeAccessOnly: fileWriteAccessOnly,
  clear: fileClear
};
var cachedBackend = null;
var migrationCompleted = false;
var backendResolver = defaultBackendResolver;
async function defaultBackendResolver() {
  const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? "").trim().toLowerCase();
  if (override === "file") {
    return fileCredentialBackend;
  }
  if (override && override !== "keychain" && override !== "auto") {
    throw new Error(
      `Unknown DREAMBOARD_CREDENTIAL_BACKEND value "${override}" (expected "file", "keychain", or "auto").`
    );
  }
  const useKeychain = override === "keychain" || await readCredentialBackendPreference();
  if (!useKeychain) {
    return fileCredentialBackend;
  }
  const { tryKeychainBackend } = await import("./keychain-backend-JHTXAKWC.js");
  const keychain = await tryKeychainBackend();
  if (keychain.available) {
    return keychain.backend;
  }
  return fileCredentialBackend;
}
async function readCredentialBackendPreference() {
  try {
    const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-YBFEGJQG.js");
    const config = await loadGlobalConfig2();
    return config.credentialBackend === "keychain";
  } catch {
    return false;
  }
}
async function getCredentialBackend() {
  if (cachedBackend === null) {
    cachedBackend = await backendResolver();
    if (!migrationCompleted && cachedBackend.name !== "file") {
      await migrateFromFileBackendIfNeeded(cachedBackend);
    }
    migrationCompleted = true;
  }
  return cachedBackend;
}
async function migrateFromFileBackendIfNeeded(target) {
  try {
    const [onDisk, onTarget] = await Promise.all([
      fileCredentialBackend.read(),
      target.read()
    ]);
    if (!onDisk) return;
    if (onTarget) {
      await fileCredentialBackend.clear();
      return;
    }
    if (onDisk.accessToken && onDisk.refreshToken) {
      await target.writeFull({
        accessToken: onDisk.accessToken,
        refreshToken: onDisk.refreshToken
      });
    } else if (onDisk.accessToken) {
      await target.writeAccessOnly(onDisk.accessToken);
    } else {
      return;
    }
    await fileCredentialBackend.clear();
  } catch {
  }
}
async function getActiveCredentialBackendName() {
  const backend = await getCredentialBackend();
  return backend.name;
}
async function getStoredSession() {
  const backend = await getCredentialBackend();
  return backend.read();
}
async function setCredentials(creds) {
  await withFileLock(getCredentialLockPath(), async () => {
    const backend = await getCredentialBackend();
    await backend.writeFull(creds);
  });
}
async function setAccessOnlySession(accessToken) {
  await withFileLock(getCredentialLockPath(), async () => {
    const backend = await getCredentialBackend();
    await backend.writeAccessOnly(accessToken);
  });
}
async function clearCredentials() {
  await withFileLock(getCredentialLockPath(), async () => {
    const backend = await getCredentialBackend();
    await backend.clear();
  });
}

// src/config/global-config.ts
function normalizeCredentialBackend(value) {
  if (value === "file" || value === "keychain") return value;
  return void 0;
}
function getGlobalConfigPath() {
  return path4.join(os2.homedir(), PROJECT_DIR_NAME, "config.json");
}
function getGlobalAuthPath() {
  return getCredentialFilePath();
}
async function loadGlobalConfig() {
  const config = await readJsonFile(getGlobalConfigPath()).catch(
    () => ({})
  );
  return {
    environment: config.environment,
    credentialBackend: normalizeCredentialBackend(config.credentialBackend)
  };
}
async function saveGlobalConfig(config) {
  const configDir = path4.join(os2.homedir(), PROJECT_DIR_NAME);
  await ensureDir(configDir);
  const normalized = {
    environment: config.environment,
    credentialBackend: normalizeCredentialBackend(config.credentialBackend)
  };
  await atomicWriteFile(
    getGlobalConfigPath(),
    `${JSON.stringify(normalized, null, 2)}
`,
    { mode: 384 }
  );
}

export {
  DEFAULT_API_BASE_URL,
  DEFAULT_WEB_BASE_URL,
  PROJECT_DIR_NAME,
  ENVIRONMENT_CONFIGS,
  PROJECT_CONFIG_FILE,
  PROJECT_STATE_FILE,
  SNAPSHOT_FILE,
  MANIFEST_FILE,
  MATERIALIZED_MANIFEST_FILE,
  MANIFEST_TYPECHECK_CONFIG_FILE,
  RULE_FILE,
  DEFAULT_LOGIN_TIMEOUT_MS,
  LOCAL_IGNORE_DIRS,
  ensureDir,
  exists,
  readTextFile,
  readTextFileIfExists,
  writeTextFile,
  readJsonFile,
  writeJsonFile,
  atomicWriteFile,
  getActiveCredentialBackendName,
  getStoredSession,
  setCredentials,
  setAccessOnlySession,
  clearCredentials,
  getGlobalConfigPath,
  getGlobalAuthPath,
  loadGlobalConfig,
  saveGlobalConfig
};
//# sourceMappingURL=chunk-3NRROR4P.js.map