#!/usr/bin/env node
import {
  toDreamboardApiError
} from "./chunk-IDVQXGAO.mjs";
import {
  createGameRevision,
  createProjectSourceBlobUploadSession,
  ensureProject,
  getApiVersion,
  getCompiledResult,
  getCurrentAuthUser,
  getJob,
  getProjectCompiledResult,
  listCompiledResults,
  listProjectCompiledResults,
  queueProjectRevisionCompile,
  uploadInitialProjection
} from "./chunk-C3VW3DTA.mjs";
import {
  external_exports
} from "./chunk-JZTH3EMV.mjs";

// ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
import { parseArgs as parseArgs$1 } from "util";
function defineCommand(def) {
  return def;
}

// ../../packages/api-client/dist/source-revisions.js
var textEncoder = new TextEncoder();
function bytesToHex(bytes) {
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
async function sha256Hex(bytes) {
  const normalizedBytes = new Uint8Array(bytes.byteLength);
  normalizedBytes.set(bytes);
  const digest = await crypto.subtle.digest("SHA-256", normalizedBytes.buffer);
  return bytesToHex(new Uint8Array(digest));
}
function getUtf8ByteSize(content) {
  return textEncoder.encode(content).byteLength;
}
async function computeSourceContentHash(content) {
  return sha256Hex(textEncoder.encode(content));
}
async function describeSourceBlob(content) {
  return {
    contentHash: await computeSourceContentHash(content),
    byteSize: getUtf8ByteSize(content)
  };
}
async function materializeSourceChangeOperations(changes) {
  const blobsByHash = /* @__PURE__ */ new Map();
  const materialized = await Promise.all(Array.from(changes, async (change) => {
    if (change.kind === "delete") {
      return change;
    }
    const blob = await describeSourceBlob(change.content);
    const existing = blobsByHash.get(blob.contentHash);
    if (!existing) {
      blobsByHash.set(blob.contentHash, blob);
    }
    return {
      kind: "upsert",
      path: change.path,
      contentHash: blob.contentHash,
      byteSize: blob.byteSize
    };
  }));
  return {
    blobs: Array.from(blobsByHash.values()).sort((left, right) => left.contentHash.localeCompare(right.contentHash)),
    changes: materialized
  };
}
function mapUpsertBlobContentsByContentHash(localChanges, materializedChanges) {
  const uploadBlobs = /* @__PURE__ */ new Map();
  const length = Math.min(localChanges.length, materializedChanges.length);
  for (let index = 0; index < length; index += 1) {
    const localChange = localChanges[index];
    const materializedChange = materializedChanges[index];
    if (localChange?.kind !== "upsert" || materializedChange?.kind !== "upsert") {
      continue;
    }
    uploadBlobs.set(materializedChange.contentHash, {
      contentHash: materializedChange.contentHash,
      byteSize: materializedChange.byteSize,
      content: localChange.content
    });
  }
  return uploadBlobs;
}
var SourceBlobUploadError = class extends Error {
  constructor(status, details) {
    const suffix = details.trim().length > 0 ? `: ${details.trim()}` : "";
    super(`Failed to upload source blob (HTTP ${status}${suffix})`);
    this.name = "SourceBlobUploadError";
    this.status = status;
    this.details = details;
  }
};
function isDuplicateDirectUploadError(error) {
  if (!(error instanceof SourceBlobUploadError)) {
    return false;
  }
  if (error.status === 409) {
    return true;
  }
  const normalizedDetails = error.details.toLowerCase();
  return normalizedDetails.includes("duplicate") || normalizedDetails.includes("already exists") || normalizedDetails.includes("resource already exists");
}
async function uploadSourceBlob(uploadTarget, content) {
  const response = await fetch(uploadTarget.url, {
    method: uploadTarget.method,
    headers: uploadTarget.headers,
    body: textEncoder.encode(content)
  });
  if (response.ok) {
    return;
  }
  const details = await response.text().catch(() => "");
  throw new SourceBlobUploadError(response.status, details);
}
var SourceBlobSessionRequestError = class extends Error {
  constructor(message, apiError, response) {
    super(message);
    this.name = "SourceBlobSessionRequestError";
    this.apiError = apiError;
    this.response = response;
  }
};
function assertSourceBlobUploadSession(data, response) {
  if (!data || typeof data !== "object" || !Array.isArray(data.uploads)) {
    throw new SourceBlobSessionRequestError("Source blob upload session response did not include an uploads array", data, response);
  }
}
async function confirmSourceBlobAlreadyExists(options) {
  const { requestUploadSession, blob } = options;
  const { data, error, response } = await requestUploadSession([
    {
      contentHash: blob.contentHash,
      byteSize: blob.byteSize
    }
  ]);
  if (error || !data) {
    throw new SourceBlobSessionRequestError("Failed to create source blob upload session", error, response);
  }
  assertSourceBlobUploadSession(data, response);
  return data.uploads[0]?.status === "exists";
}
async function uploadSourceBlobs(options) {
  const { blobs, requestUploadSession } = options;
  const uniqueBlobs = /* @__PURE__ */ new Map();
  for (const blob of blobs) {
    const existing = uniqueBlobs.get(blob.contentHash);
    if (!existing) {
      uniqueBlobs.set(blob.contentHash, blob);
      continue;
    }
    if (existing.byteSize !== blob.byteSize) {
      throw new Error(`Source blob ${blob.contentHash} has conflicting byte sizes.`);
    }
  }
  if (uniqueBlobs.size === 0) {
    return;
  }
  const { data, error, response } = await requestUploadSession(Array.from(uniqueBlobs.values(), ({ contentHash, byteSize }) => ({
    contentHash,
    byteSize
  })));
  if (error || !data) {
    throw new SourceBlobSessionRequestError("Failed to create source blob upload session", error, response);
  }
  assertSourceBlobUploadSession(data, response);
  for (const upload of data.uploads) {
    if (upload.status !== "upload_required") {
      continue;
    }
    const blob = uniqueBlobs.get(upload.contentHash);
    if (!blob) {
      throw new Error(`Upload session referenced unknown source blob ${upload.contentHash}.`);
    }
    if (!upload.uploadTarget) {
      throw new Error(`Upload target missing for source blob ${upload.contentHash}.`);
    }
    try {
      await uploadSourceBlob(upload.uploadTarget, blob.content);
      if (!await confirmSourceBlobAlreadyExists({ requestUploadSession, blob })) {
        throw new Error(`Source blob ${blob.contentHash} was uploaded but not registered.`);
      }
    } catch (error2) {
      if (isDuplicateDirectUploadError(error2) && await confirmSourceBlobAlreadyExists({ requestUploadSession, blob })) {
        continue;
      }
      throw error2;
    }
  }
}
async function uploadProjectSourceBlobs(options) {
  const { projectId, blobs } = options;
  return uploadSourceBlobs({
    blobs,
    requestUploadSession: (uploadBlobs) => createProjectSourceBlobUploadSession({
      path: { projectId },
      body: { blobs: uploadBlobs }
    })
  });
}

// src/command-args.ts
var CONFIG_FLAG_ARGS = {
  env: {
    type: "string",
    description: "Environment: local | staging | prod"
  },
  token: {
    type: "string",
    description: "Auth token (Dreamboard bearer JWT)"
  }
};

// src/flags.ts
var configFlagsSchema = external_exports.object({
  env: external_exports.enum(["local", "staging", "prod"]).optional(),
  token: external_exports.string().optional()
});
var ruleInputFlagsSchema = external_exports.object({
  "rule-file": external_exports.string().optional(),
  rule: external_exports.string().optional()
});
var playerCountFlagsSchema = external_exports.object({
  players: external_exports.string().optional(),
  "player-count": external_exports.string().optional()
});
var newCommandArgsSchema = configFlagsSchema.extend({
  slug: external_exports.string().min(1),
  description: external_exports.string().min(1),
  force: external_exports.boolean().default(false)
});
var cloneCommandArgsSchema = configFlagsSchema.extend({
  slug: external_exports.string().min(1)
});
var queryCommandArgsSchema = configFlagsSchema.extend({
  title: external_exports.string().min(1)
});
var pullCommandArgsSchema = configFlagsSchema.extend({
  force: external_exports.boolean().default(false)
});
var syncCommandArgsSchema = configFlagsSchema.extend({
  force: external_exports.boolean().default(false),
  yes: external_exports.boolean().default(false)
});
var compileCommandArgsSchema = configFlagsSchema.extend({
  debug: external_exports.boolean().default(false),
  "skip-local-check": external_exports.boolean().default(false)
});
var statusCommandArgsSchema = configFlagsSchema.extend({
  json: external_exports.boolean().default(false)
});
var devCommandArgsSchema = configFlagsSchema.extend({
  seed: external_exports.string().optional(),
  "setup-profile": external_exports.string().optional(),
  players: external_exports.string().optional(),
  "player-count": external_exports.string().optional(),
  debug: external_exports.boolean().default(false),
  resume: external_exports.string().optional(),
  "from-scenario": external_exports.string().optional(),
  "new-session": external_exports.boolean().default(false),
  open: external_exports.boolean().default(false),
  port: external_exports.string().optional(),
  host: external_exports.union([external_exports.string(), external_exports.boolean()]).optional(),
  "allowed-host": external_exports.string().optional()
});
var joinCommandArgsSchema = configFlagsSchema.extend({
  session: external_exports.string().min(1).optional(),
  player: external_exports.string().min(1),
  "raw-events": external_exports.boolean().default(false)
});
var configCommandArgsSchema = configFlagsSchema.extend({
  action: external_exports.string().optional().default("show"),
  scope: external_exports.enum(["global", "workspace"]).optional().default("global")
});
var authCommandArgsSchema = external_exports.object({
  action: external_exports.enum(["set", "clear", "login", "env", "status"]),
  tokenValue: external_exports.string().optional(),
  token: external_exports.string().optional(),
  jwt: external_exports.boolean().optional(),
  env: external_exports.enum(["local", "staging", "prod"]).optional()
});
function parseArgs(commandName, schema, args) {
  const parsed = schema.safeParse(args);
  if (parsed.success) {
    return parsed.data;
  }
  const details = parsed.error.issues.map((issue) => {
    const field = issue.path.length > 0 ? issue.path.join(".") : "args";
    return `${field}: ${issue.message}`;
  }).join("; ");
  throw new Error(`Invalid arguments for '${commandName}': ${details}`);
}
function parseConfigFlags(args) {
  return parseArgs("config-flags", configFlagsSchema, args);
}
function parseSyncCommandArgs(args) {
  return parseArgs("sync", syncCommandArgsSchema, args);
}
function parseCompileCommandArgs(args) {
  return parseArgs("compile", compileCommandArgsSchema, args);
}

// src/utils/strings.ts
import path from "path";
function titleFromSlug(slug) {
  return slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// src/services/api/compiled-results-api.ts
var COMPILE_JOB_POLL_INTERVAL_MS = 1e3;
var DEFAULT_COMPILE_JOB_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
function firstNonEmpty(...values) {
  for (const value of values) {
    if (typeof value === "string" && value.trim().length > 0) {
      return value.trim();
    }
  }
  return null;
}
function formatTerminalCompileJobMessage(job) {
  const detail = firstNonEmpty(job.errorMessage, job.message);
  const phase = firstNonEmpty(job.phase);
  const prefix = `Compile ${job.status.toLowerCase()}${phase ? ` [${phase}]` : ""}`;
  return detail ? `${prefix}: ${detail}` : `${prefix}: job ${job.jobId} ended before a compiled result was created.`;
}
function compareCreatedAtDesc(left, right) {
  const leftTime = Date.parse(left.createdAt);
  const rightTime = Date.parse(right.createdAt);
  if (Number.isFinite(leftTime) && Number.isFinite(rightTime)) {
    return rightTime - leftTime;
  }
  if (Number.isFinite(rightTime)) {
    return 1;
  }
  if (Number.isFinite(leftTime)) {
    return -1;
  }
  return 0;
}
async function findFallbackCompiledResultForJob(options) {
  const { gameId, projectId, job } = options;
  const results = projectId ? await listProjectCompiledResults({
    path: { projectId },
    query: { limit: 100 }
  }) : await listCompiledResults({
    path: { gameId },
    query: { limit: 100 }
  });
  if (results.error || !results.data) {
    return null;
  }
  if (results.data.results.length === 0) {
    return null;
  }
  const jobCreatedAtMs = Date.parse(job.createdAt);
  const resultsCreatedAfterJob = Number.isFinite(jobCreatedAtMs) ? results.data.results.filter((result) => {
    const resultCreatedAtMs = Date.parse(result.createdAt);
    return !Number.isFinite(resultCreatedAtMs) || resultCreatedAtMs >= jobCreatedAtMs;
  }) : results.data.results;
  const candidateResults = resultsCreatedAfterJob.length > 0 ? resultsCreatedAfterJob : results.data.results;
  return [...candidateResults].sort(compareCreatedAtDesc)[0] ?? null;
}
async function getCompiledResultSdk(gameId, compiledResultId) {
  const { data, error, response } = await getCompiledResult({
    path: {
      gameId,
      compiledResultId
    }
  });
  if (error || !data) {
    throw toDreamboardApiError(
      error,
      response,
      "Failed to fetch compiled result"
    );
  }
  return data;
}
async function findProjectCompiledResultsForRevision(options) {
  const { projectId, revisionDigest } = options;
  const { data, error, response } = await listProjectCompiledResults({
    path: { projectId },
    query: { limit: 100 }
  });
  if (error || !data) {
    throw toDreamboardApiError(
      error,
      response,
      "Failed to list compiled results"
    );
  }
  return data.results.filter(
    (result) => {
      const maybeRevisionDigest = result.revisionDigest;
      return maybeRevisionDigest === revisionDigest || result.authoringStateId === revisionDigest;
    }
  );
}
async function getProjectCompiledResultSdk(projectId, compiledResultId) {
  const { data, error, response } = await getProjectCompiledResult({
    path: { projectId, compiledResultId }
  });
  if (error || !data) {
    throw toDreamboardApiError(
      error,
      response,
      "Failed to fetch compiled result"
    );
  }
  return data;
}
async function queueProjectRevisionCompileSdk(options) {
  const { data, error, response } = await queueProjectRevisionCompile({
    path: {
      projectId: options.projectId,
      revisionDigest: options.revisionDigest
    }
  });
  if (error || !data) {
    throw toDreamboardApiError(error, response, "Failed to create compile job");
  }
  return data;
}
async function waitForCompiledResultJobSdk(options) {
  const { gameId, projectId, jobId, onProgress } = options;
  let previousTransitionKey = null;
  const startedAt = Date.now();
  const timeoutMs = readCompileJobWaitTimeoutMs();
  while (Date.now() - startedAt < timeoutMs) {
    const {
      data: job,
      error,
      response
    } = await getJob({
      path: { jobId }
    });
    if (error || !job) {
      if (isTransientJobPollError(error, response)) {
        await sleep(COMPILE_JOB_POLL_INTERVAL_MS);
        continue;
      }
      throw toDreamboardApiError(error, response, "Failed to get job");
    }
    const transitionKey = `${job.status}:${job.phase ?? ""}`;
    if (transitionKey !== previousTransitionKey) {
      previousTransitionKey = transitionKey;
      onProgress?.(job);
    }
    if (job.status === "COMPLETED" || job.status === "FAILED") {
      const compiledResultId = job.createdCompiledResultId ?? job.createdAppScriptId;
      if (compiledResultId) {
        if (projectId) {
          const compiledResult2 = await getProjectCompiledResultSdk(
            projectId,
            compiledResultId
          );
          return { job, compiledResult: compiledResult2 };
        }
        const compiledResult = await getCompiledResultSdk(
          gameId,
          compiledResultId
        );
        return { job, compiledResult };
      }
      const fallbackCompiledResult = await findFallbackCompiledResultForJob({
        gameId,
        projectId,
        job
      });
      if (fallbackCompiledResult) {
        return { job, compiledResult: fallbackCompiledResult };
      }
      throw new Error(formatTerminalCompileJobMessage(job));
    }
    if (job.status === "CANCELLED" || job.status === "INTERRUPTED") {
      throw new Error(formatTerminalCompileJobMessage(job));
    }
    await sleep(COMPILE_JOB_POLL_INTERVAL_MS);
  }
  throw new Error(`Compile job ${jobId} did not complete in time.`);
}
function readCompileJobWaitTimeoutMs() {
  const raw = process.env.DREAMBOARD_COMPILE_WAIT_TIMEOUT_MS;
  if (!raw) return DEFAULT_COMPILE_JOB_WAIT_TIMEOUT_MS;
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return DEFAULT_COMPILE_JOB_WAIT_TIMEOUT_MS;
  }
  return parsed;
}
function isTransientJobPollError(error, response) {
  if (response) return false;
  if (!error) return false;
  if (error instanceof Error) {
    return isTransientJobPollMessage(error.message);
  }
  if (typeof error === "object" && error !== null && "message" in error) {
    return isTransientJobPollMessage(String(error.message));
  }
  return isTransientJobPollMessage(String(error));
}
function isTransientJobPollMessage(message) {
  const normalized = message.toLowerCase();
  return normalized.includes("fetch failed") || normalized.includes("network") || normalized.includes("timeout") || normalized.includes("econnreset") || normalized.includes("econnrefused") || normalized.includes("socket");
}

// src/services/api/project-api.ts
async function loadRemoteProjectIdentity() {
  const [versionResponse, userResponse] = await Promise.all([
    getApiVersion(),
    getCurrentAuthUser()
  ]);
  if (versionResponse.error || !versionResponse.data) {
    throw toDreamboardApiError(
      versionResponse.error,
      versionResponse.response,
      "Failed to resolve backend deployment identity"
    );
  }
  if (userResponse.error || !userResponse.data) {
    throw toDreamboardApiError(
      userResponse.error,
      userResponse.response,
      "Failed to resolve authenticated owner scope"
    );
  }
  const deploymentId = versionResponse.data.deploymentId;
  const ownerScopeId = userResponse.data.ownerScopeId;
  return {
    deploymentId,
    ownerScopeId,
    bindingKey: `${deploymentId}:${ownerScopeId}`
  };
}
async function ensureProjectSdk(options) {
  const { data, error, response } = await ensureProject({
    path: { projectId: options.projectId },
    body: {
      slug: options.slug,
      name: titleFromSlug(options.slug),
      description: options.description ?? `Dreamboard workspace for ${options.slug}.`,
      ...options.updateAlias ? { updateAlias: true } : {}
    }
  });
  if (error || !data) {
    throw toDreamboardApiError(error, response, "Failed to ensure project");
  }
  return data;
}
async function createGameRevisionSdk(options) {
  const { data, error, response } = await createGameRevision({
    path: { projectId: options.projectId },
    body: options.request
  });
  if (error || !data) {
    throw toDreamboardApiError(
      error,
      response,
      "Failed to create game revision"
    );
  }
  return data;
}

// src/services/api/preview-api.ts
async function uploadInitialProjectionSdk(gameId, projectionJson) {
  const { error, response } = await uploadInitialProjection({
    path: { gameId },
    body: { projectionJson }
  });
  if (error) {
    throw toDreamboardApiError(
      error,
      response,
      "Failed to upload initial preview projection"
    );
  }
}

// src/services/api/source-revisions-api.ts
async function uploadProjectSourceBlobsSdk(projectId, blobs) {
  try {
    await uploadProjectSourceBlobs({ projectId, blobs });
  } catch (error) {
    if (error instanceof SourceBlobSessionRequestError) {
      throw toDreamboardApiError(
        error.apiError,
        error.response,
        error.message
      );
    }
    throw error;
  }
}

export {
  defineCommand,
  materializeSourceChangeOperations,
  mapUpsertBlobContentsByContentHash,
  CONFIG_FLAG_ARGS,
  parseConfigFlags,
  parseSyncCommandArgs,
  parseCompileCommandArgs,
  findProjectCompiledResultsForRevision,
  getProjectCompiledResultSdk,
  queueProjectRevisionCompileSdk,
  waitForCompiledResultJobSdk,
  loadRemoteProjectIdentity,
  ensureProjectSdk,
  createGameRevisionSdk,
  uploadInitialProjectionSdk,
  uploadProjectSourceBlobsSdk
};
//# sourceMappingURL=chunk-KDBSVLCF.mjs.map