UNPKG

@electric-sql/cli

Version:
2,382 lines 76.9 kB
#!/usr/bin/env node

// src/index.ts
import { Command as Command33 } from "commander";
import { existsSync as existsSync2 } from "fs";
import { join as join5 } from "path";

// src/config.ts
import { readFile, writeFile, mkdir, unlink } from "fs/promises";
import { join } from "path";
import { homedir } from "os";
function getConfigDir() {
  return process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, `electric`) : join(homedir(), `.config`, `electric`);
}
function getAuthFile() {
  return join(getConfigDir(), `auth.json`);
}
async function loadStoredAuth() {
  try {
    const raw = await readFile(getAuthFile(), `utf-8`);
    return JSON.parse(raw);
  } catch {
    return null;
  }
}
async function saveStoredAuth(auth2) {
  const configDir = getConfigDir();
  await mkdir(configDir, { recursive: true });
  await writeFile(getAuthFile(), JSON.stringify(auth2, null, 2), { mode: 384 });
}
async function clearStoredAuth() {
  try {
    await unlink(getAuthFile());
  } catch {
  }
}

// src/auth.ts
var explicitToken;
function setExplicitToken(token2) {
  explicitToken = token2;
}
var EXPIRY_WARNING_MS = 24 * 60 * 60 * 1e3;
async function resolveAuth() {
  if (explicitToken) {
    return { token: explicitToken, type: `api_token` };
  }
  const apiToken = process.env.ELECTRIC_API_TOKEN;
  if (apiToken) {
    return { token: apiToken, type: `api_token` };
  }
  const stored = await loadStoredAuth();
  if (stored) {
    const expiresAt = new Date(stored.expiresAt);
    const now = /* @__PURE__ */ new Date();
    if (expiresAt < now) {
      await clearStoredAuth();
      throw new AuthError(
        `Session expired. Run 'electric auth login' to re-authenticate.`
      );
    }
    if (expiresAt.getTime() - now.getTime() < EXPIRY_WARNING_MS) {
      const hours = Math.ceil(
        (expiresAt.getTime() - now.getTime()) / (60 * 60 * 1e3)
      );
      process.stderr.write(
        `Warning: Session expires in ${hours} hours. Run 'electric auth login' to refresh.
`
      );
    }
    return { token: stored.token, type: `jwt` };
  }
  throw new AuthError(
    `Not authenticated. Pass --token, set ELECTRIC_API_TOKEN, or run 'electric auth login'.`
  );
}
var AuthError = class extends Error {
  constructor(message) {
    super(message);
    this.name = `AuthError`;
  }
};

// src/lib/confirm.ts
import { createInterface } from "readline";
var _noInput = false;
function setNoInput(value) {
  _noInput = value;
}
function isInteractive() {
  if (_noInput) return false;
  if (process.env.ELECTRIC_API_TOKEN) return false;
  return true;
}
function confirm(prompt) {
  if (!isInteractive()) {
    return Promise.resolve(false);
  }
  return new Promise((resolve4) => {
    const rl = createInterface({
      input: process.stdin,
      output: process.stderr
    });
    rl.question(prompt, (answer) => {
      rl.close();
      resolve4(answer.toLowerCase() === `y` || answer.toLowerCase() === `yes`);
    });
  });
}

// src/client.ts
import { createORPCClient } from "@orpc/client";
import { RPCLink } from "@orpc/client/fetch";
var CLI_VERSION = true ? "0.1.0" : `0.0.1`;
var _verbose = false;
function setVerbose(value) {
  _verbose = value;
}
function getApiBaseUrl() {
  return process.env.ELECTRIC_API_URL ?? `https://dashboard.electric-sql.cloud`;
}
function getDashboardUrl() {
  return process.env.ELECTRIC_DASHBOARD_URL ?? `https://dashboard.electric-sql.cloud`;
}
function createClient() {
  const link = new RPCLink({
    url: `${getApiBaseUrl()}/api/rpc`,
    headers: async () => {
      const headers = {
        "User-Agent": `electric-cli/${CLI_VERSION}`
      };
      const auth2 = await resolveAuth();
      headers[`Authorization`] = `Bearer ${auth2.token}`;
      return headers;
    },
    fetch: _verbose ? verboseFetch : void 0
  });
  return createORPCClient(link);
}
function createPublicClient() {
  const link = new RPCLink({
    url: `${getApiBaseUrl()}/api/rpc`,
    headers: () => ({
      "User-Agent": `electric-cli/${CLI_VERSION}`
    }),
    fetch: _verbose ? verboseFetch : void 0
  });
  return createORPCClient(link);
}
async function verboseFetch(request, init) {
  const method = request.method;
  const url = request.url;
  const start = performance.now();
  process.stderr.write(`${method} ${url}
`);
  const response = await fetch(request, init);
  const elapsed = Math.round(performance.now() - start);
  process.stderr.write(
    `${response.status} ${response.statusText} (${elapsed}ms)
`
  );
  const requestId = response.headers.get(`x-request-id`);
  if (requestId) {
    process.stderr.write(`X-Request-Id: ${requestId}
`);
  }
  return response;
}

// src/output.ts
var _quiet = false;
function setQuiet(value) {
  _quiet = value;
}
function isQuiet() {
  return _quiet;
}
function printQuiet(value) {
  process.stdout.write(value + `
`);
}
function printJson(data) {
  process.stdout.write(JSON.stringify(data, null, 2) + `
`);
}
function printTable(headers, rows) {
  if (rows.length === 0) {
    process.stdout.write(`No results found.
`);
    return;
  }
  const widths = headers.map(
    (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? ``).length))
  );
  const headerLine = headers.map((h, i) => h.toUpperCase().padEnd(widths[i])).join(`  `);
  process.stdout.write(headerLine + `
`);
  for (const row of rows) {
    const line = row.map((cell, i) => (cell ?? ``).padEnd(widths[i])).join(`  `);
    process.stdout.write(line + `
`);
  }
}
function printSuccess(message) {
  process.stdout.write(message + `
`);
}
function printKeyValue(pairs) {
  const maxKeyLen = Math.max(...pairs.map(([k]) => k.length));
  for (const [key, value] of pairs) {
    process.stdout.write(`${key.padEnd(maxKeyLen)}  ${value}
`);
  }
}

// src/telemetry.ts
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
import { join as join2 } from "path";
import { randomUUID } from "crypto";
var POSTHOG_HOST = `https://us.i.posthog.com`;
var POSTHOG_KEY = "" ? "" : ``;
function isTelemetryEnabled() {
  if (process.env.ELECTRIC_TELEMETRY?.toLowerCase() === `off`) return false;
  if (!POSTHOG_KEY) return false;
  return true;
}
function getAnonymousIdFile() {
  return join2(getConfigDir(), `anonymous-id`);
}
var cachedAnonymousId;
async function getOrCreateAnonymousId() {
  if (cachedAnonymousId) return cachedAnonymousId;
  const idFile = getAnonymousIdFile();
  try {
    const id2 = (await readFile2(idFile, `utf-8`)).trim();
    if (id2) {
      cachedAnonymousId = id2;
      return id2;
    }
  } catch {
  }
  const id = randomUUID();
  try {
    const configDir = getConfigDir();
    await mkdir2(configDir, { recursive: true });
    await writeFile2(idFile, id, { mode: 384 });
  } catch {
  }
  cachedAnonymousId = id;
  return id;
}
function capture(telemetryEvent) {
  if (!isTelemetryEnabled()) return;
  const promise = captureAsync(telemetryEvent);
  pendingRequests.push(promise);
  promise.finally(() => {
    const idx = pendingRequests.indexOf(promise);
    if (idx >= 0) pendingRequests.splice(idx, 1);
  });
}
async function captureAsync(telemetryEvent) {
  try {
    const distinctId = telemetryEvent.distinctId ?? await getOrCreateAnonymousId();
    const payload = {
      api_key: POSTHOG_KEY,
      event: telemetryEvent.event,
      properties: {
        ...telemetryEvent.properties,
        $lib: `electric-cli`
      },
      distinct_id: distinctId,
      timestamp: (/* @__PURE__ */ new Date()).toISOString()
    };
    await fetch(`${POSTHOG_HOST}/i/v0/e/`, {
      method: `POST`,
      headers: { "Content-Type": `application/json` },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(5e3)
    });
  } catch {
  }
}
var pendingRequests = [];
async function flush() {
  await Promise.allSettled(pendingRequests);
  pendingRequests.length = 0;
}

// src/commands/auth/login.ts
import { Command } from "commander";
import { randomUUID as randomUUID2 } from "crypto";
import { URL as URL3 } from "url";

// src/lib/errors.ts
import { existsSync } from "fs";
import { join as join3 } from "path";
import { ORPCError } from "@orpc/client";
var EXIT_GENERAL_ERROR = 1;
var EXIT_AUTH_ERROR = 2;
var EXIT_NOT_FOUND = 3;
var EXIT_VALIDATION_ERROR = 4;
var EXIT_CONFLICT = 5;
function detectAuthType() {
  if (process.env.ELECTRIC_API_TOKEN) return `api_token`;
  try {
    if (existsSync(join3(getConfigDir(), `auth.json`))) return `jwt`;
  } catch {
  }
  return `none`;
}
function errorNameForCode(code) {
  if (code === EXIT_AUTH_ERROR) return `AUTH_ERROR`;
  if (code === EXIT_NOT_FOUND) return `NOT_FOUND`;
  if (code === EXIT_VALIDATION_ERROR) return `VALIDATION_ERROR`;
  if (code === EXIT_CONFLICT) return `CONFLICT`;
  return `ERROR`;
}
function exitWithError(message, code, json) {
  if (json) {
    process.stderr.write(
      JSON.stringify({
        error: errorNameForCode(code),
        message,
        exitCode: code
      }) + `
`
    );
  } else {
    process.stderr.write(`Error: ${message}
`);
  }
  process.exit(code);
}
async function handleError(error, json, command) {
  const { code, message } = classifyError(error);
  capture({
    event: `cli_error`,
    properties: {
      command: command ?? `unknown`,
      error_type: errorNameForCode(code),
      cli_version: CLI_VERSION,
      auth_type: detectAuthType()
    }
  });
  await flush();
  exitWithError(message, code, json);
}
function classifyByStatus(status) {
  if (status === 401 || status === 403) return EXIT_AUTH_ERROR;
  if (status === 404) return EXIT_NOT_FOUND;
  if (status === 400 || status === 422) return EXIT_VALIDATION_ERROR;
  return EXIT_GENERAL_ERROR;
}
function classifyError(error) {
  if (error instanceof AuthError) {
    return { code: EXIT_AUTH_ERROR, message: error.message };
  }
  if (error instanceof ORPCError) {
    return {
      code: classifyByStatus(error.status),
      message: error.message
    };
  }
  if (error instanceof Error) {
    return { code: EXIT_GENERAL_ERROR, message: error.message };
  }
  return { code: EXIT_GENERAL_ERROR, message: String(error) };
}

// src/lib/local-server.ts
import { createServer } from "http";
import { URL as URL2 } from "url";
function startLocalServer(options) {
  const {
    callbackPath,
    parseParams,
    successHtml = `<html><body><p>Success. You can close this tab.</p><script>window.close()</script></body></html>`,
    errorHtml = `<html><body><p>Something went wrong. Please try again.</p></body></html>`
  } = options;
  return new Promise((resolveStart) => {
    let resolveResult;
    let rejectResult;
    const resultPromise = new Promise((resolve4, reject) => {
      resolveResult = resolve4;
      rejectResult = reject;
    });
    const server = createServer((req, res) => {
      const url = new URL2(req.url ?? `/`, `http://localhost`);
      if (url.pathname === callbackPath) {
        try {
          const result = parseParams(url.searchParams);
          res.writeHead(200, { "Content-Type": `text/html` });
          res.end(successHtml);
          resolveResult(result);
        } catch (err) {
          res.writeHead(400, { "Content-Type": `text/html` });
          res.end(errorHtml);
          rejectResult(err instanceof Error ? err : new Error(String(err)));
        }
      } else {
        res.writeHead(404);
        res.end();
      }
    });
    const shutdown = () => {
      server.close();
    };
    server.listen(0, `127.0.0.1`, () => {
      const addr = server.address();
      const port = typeof addr === `object` && addr ? addr.port : 0;
      resolveStart({ port, resultPromise, shutdown });
    });
  });
}

// src/commands/auth/login.ts
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
var loginCommand = new Command(`login`).description(`Log in to Electric Cloud via browser`).action(async () => {
  const json = loginCommand.parent?.parent?.opts().json ?? false;
  try {
    await runLogin(json);
  } catch (error) {
    await handleError(error, json);
  }
});
async function runLogin(json) {
  const state = randomUUID2();
  const { port, resultPromise, shutdown } = await startLocalServer({
    callbackPath: `/callback`,
    parseParams: (params) => {
      const receivedState = params.get(`state`);
      if (receivedState !== state) {
        throw new Error(`State mismatch in OAuth callback`);
      }
      const token2 = params.get(`token`);
      if (!token2) {
        throw new Error(`No token received in OAuth callback`);
      }
      return {
        token: token2,
        email: params.get(`email`) ?? void 0,
        expiresAt: params.get(`expiresAt`) ?? void 0
      };
    },
    successHtml: `<html><body><p>Authentication successful. You can close this tab.</p><script>window.close()</script></body></html>`,
    errorHtml: `<html><body><p>Authentication failed. Please try again.</p></body></html>`
  });
  const loginUrl = new URL3(`/`, getDashboardUrl());
  loginUrl.searchParams.set(`cli_port`, String(port));
  loginUrl.searchParams.set(`cli_state`, state);
  try {
    const open2 = (await import("open")).default;
    await open2(loginUrl.toString());
    if (!json) {
      process.stderr.write(
        `Opening browser for login...
If the browser doesn't open, visit:
${loginUrl.toString()}
`
      );
    }
  } catch {
    process.stderr.write(
      `Could not open browser. Visit this URL to log in:
${loginUrl.toString()}
`
    );
  }
  let timeoutId;
  const timeoutPromise = new Promise((_, reject) => {
    timeoutId = setTimeout(() => {
      shutdown();
      reject(
        new Error(
          `Authentication timed out after ${LOGIN_TIMEOUT_MS / 6e4} minutes. Run 'electric auth login' to try again.`
        )
      );
    }, LOGIN_TIMEOUT_MS);
  });
  try {
    const result = await Promise.race([resultPromise, timeoutPromise]);
    clearTimeout(timeoutId);
    shutdown();
    await saveStoredAuth({
      token: result.token,
      email: result.email ?? `unknown`,
      expiresAt: result.expiresAt ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
    });
    capture({
      event: `cli_auth_login`,
      properties: { cli_version: CLI_VERSION }
    });
    if (json) {
      process.stdout.write(
        JSON.stringify({ success: true, email: result.email }) + `
`
      );
    } else {
      printSuccess(`Logged in successfully.`);
    }
  } catch (error) {
    const reason = error instanceof Error && error.message.includes(`timed out`) ? `timeout` : error instanceof Error && error.message.includes(`State mismatch`) ? `state_mismatch` : error instanceof Error && error.message.includes(`No token`) ? `no_token` : `unknown`;
    capture({
      event: `cli_auth_login_failed`,
      properties: { cli_version: CLI_VERSION, reason }
    });
    await flush();
    shutdown();
    throw error;
  }
}

// src/commands/auth/logout.ts
import { Command as Command2 } from "commander";
var logoutCommand = new Command2(`logout`).description(`Log out and clear stored credentials`).action(async () => {
  const json = logoutCommand.parent?.parent?.opts().json ?? false;
  try {
    await clearStoredAuth();
    if (json) {
      printJson({ success: true });
    } else {
      printSuccess(`Logged out successfully.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/auth/whoami.ts
import { Command as Command3 } from "commander";
var whoamiCommand = new Command3(`whoami`).description(`Show the currently authenticated user`).action(async () => {
  const json = whoamiCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.auth.whoami({});
    if (isQuiet()) {
      printQuiet(result.type === `user` ? result.userId : result.tokenId);
      return;
    }
    if (json) {
      printJson(result);
    } else if (result.type === `user`) {
      printKeyValue([
        [`Type:`, `User`],
        [`User ID:`, result.userId],
        [`Email:`, result.email]
      ]);
    } else {
      printKeyValue([
        [`Type:`, `API Token`],
        [`Token ID:`, result.tokenId],
        [`Workspace:`, result.workspaceId],
        [`Scopes:`, result.scopes.join(`, `)]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/auth/token/create.ts
import { Command as Command4 } from "commander";

// src/lib/workspace-resolver.ts
async function resolveWorkspace(client, explicitWorkspace) {
  if (explicitWorkspace) {
    return explicitWorkspace;
  }
  const envWorkspace = process.env.ELECTRIC_WORKSPACE_ID;
  if (envWorkspace) {
    return envWorkspace;
  }
  const whoami = await client.auth.whoami({});
  if (whoami.type === `token`) {
    return whoami.workspaceId;
  }
  const { workspaces: workspaces2 } = await client.workspaces.list({});
  if (workspaces2.length === 0) {
    throw new Error(
      `No workspaces found. Create one at https://dashboard.electric-sql.cloud`
    );
  }
  if (workspaces2.length === 1) {
    return workspaces2[0].id;
  }
  const list = workspaces2.map((w) => `  ${w.id}  ${w.name}`).join(`
`);
  throw new Error(
    `Multiple workspaces found. Specify one with --workspace or ELECTRIC_WORKSPACE_ID.

Available workspaces:
${list}

Set a default: export ELECTRIC_WORKSPACE_ID=<id>`
  );
}

// src/commands/auth/token/create.ts
var tokenCreateCommand = new Command4(`create`).usage(`--name <name> --scopes <scopes> [options]`).description(`Create a new API token`).requiredOption(`--name <name>`, `Token name`).requiredOption(`--scopes <scopes>`, `Comma-separated scope list`).option(`--workspace <id>`, `Workspace ID`).option(`--project-ids <ids>`, `Comma-separated project ID list`).option(`--expires-at <date>`, `Expiration date (ISO 8601)`).addHelpText(
  `after`,
  `
Available scopes:
  v2:projects:read          Read project details
  v2:projects:write         Create, update, and delete projects
  v2:environments:read      Read environment details
  v2:environments:write     Create, update, and delete environments
  v2:services:read          Read service details
  v2:services:write         Create, update, and delete services
  v2:services:secrets       Read service connection credentials
`
).action(async (opts) => {
  const json = tokenCreateCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const workspaceId = await resolveWorkspace(client, opts.workspace);
    const scopes = opts.scopes.split(`,`).map((s) => s.trim());
    const projectIds = opts.projectIds ? opts.projectIds.split(`,`).map((s) => s.trim()) : void 0;
    const result = await client.tokens.create({
      workspaceId,
      name: opts.name,
      scopes,
      projectIds,
      expiresAt: opts.expiresAt ?? void 0
    });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Token created successfully.
`);
      printKeyValue([
        [`ID:`, result.id],
        [`Token:`, result.token]
      ]);
      process.stdout.write(
        `
Save this token \u2014 it cannot be retrieved again.
`
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/auth/token/list.ts
import { Command as Command5 } from "commander";
var tokenListCommand = new Command5(`list`).description(`List API tokens in a workspace`).option(`--workspace <id>`, `Workspace ID`).action(async (opts) => {
  const json = tokenListCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const workspaceId = await resolveWorkspace(client, opts.workspace);
    const result = await client.tokens.list({ workspaceId });
    if (isQuiet()) {
      for (const t of result.tokens) {
        printQuiet(t.id);
      }
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printTable(
        [`ID`, `Name`, `Prefix`, `Scopes`, `Created`],
        result.tokens.map((t) => [
          t.id,
          t.name,
          t.tokenPrefix,
          t.scopes.join(`, `),
          t.createdAt.slice(0, 10)
        ])
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/auth/token/revoke.ts
import { Command as Command6 } from "commander";
var tokenRevokeCommand = new Command6(`revoke`).description(`Revoke an API token`).argument(`<token-id>`, `Token ID to revoke`).option(`--force`, `Skip confirmation prompt`).action(async (tokenId, opts) => {
  const json = tokenRevokeCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    if (!opts.force) {
      if (json) {
        process.stderr.write(
          JSON.stringify({
            error: `VALIDATION_ERROR`,
            message: `--force is required with --json`,
            exitCode: EXIT_VALIDATION_ERROR
          }) + `
`
        );
        process.exit(EXIT_VALIDATION_ERROR);
      }
      const confirmed = await confirm(`Revoke token ${tokenId}? [y/N] `);
      if (!confirmed) {
        if (!isInteractive()) {
          process.stderr.write(
            `Use --force to skip confirmation in non-interactive mode.
`
          );
          process.exit(EXIT_VALIDATION_ERROR);
        }
        process.stderr.write(`Aborted.
`);
        return;
      }
    }
    const client = createClient();
    const result = await client.tokens.revoke({ tokenId });
    if (isQuiet()) {
      printQuiet(tokenId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Token ${tokenId} revoked.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/workspaces/list.ts
import { Command as Command7 } from "commander";
var workspacesListCommand = new Command7(`list`).description(`List accessible workspaces`).action(async () => {
  const json = workspacesListCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.workspaces.list({});
    if (isQuiet()) {
      for (const w of result.workspaces) {
        printQuiet(w.id);
      }
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printTable(
        [`ID`, `Name`, `Created`],
        result.workspaces.map((w) => [w.id, w.name, w.createdAt.slice(0, 10)])
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/workspaces/get.ts
import { Command as Command8 } from "commander";
var workspacesGetCommand = new Command8(`get`).description(`Show workspace details`).argument(`<id>`, `Workspace ID`).action(async (workspaceId) => {
  const json = workspacesGetCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.workspaces.get({ workspaceId });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printKeyValue([
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Created:`, result.createdAt]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/projects/list.ts
import { Command as Command9 } from "commander";
var projectsListCommand = new Command9(`list`).description(`List projects in a workspace`).option(`--workspace <id>`, `Workspace ID`).action(async (opts) => {
  const json = projectsListCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const workspaceId = await resolveWorkspace(client, opts.workspace);
    const result = await client.projects.list({ workspaceId });
    if (isQuiet()) {
      for (const p of result.projects) {
        printQuiet(p.id);
      }
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printTable(
        [`ID`, `Name`, `Created`],
        result.projects.map((p) => [p.id, p.name, p.createdAt.slice(0, 10)])
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/projects/create.ts
import { Command as Command10 } from "commander";
var projectsCreateCommand = new Command10(`create`).usage(`--name <name> [options]`).description(`Create a new project`).requiredOption(`--name <name>`, `Project name`).option(`--workspace <id>`, `Workspace ID`).option(`--default-environment <name>`, `Default environment name`, `Main`).action(async (opts) => {
  const json = projectsCreateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const workspaceId = await resolveWorkspace(client, opts.workspace);
    const result = await client.projects.create({
      workspaceId,
      name: opts.name,
      defaultEnvName: opts.defaultEnvironment
    });
    if (isQuiet()) {
      printQuiet(result.projectId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Project created successfully.
`);
      printKeyValue([
        [`Project ID:`, result.projectId],
        [`Environment ID:`, result.environmentId]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/projects/get.ts
import { Command as Command11 } from "commander";
var projectsGetCommand = new Command11(`get`).description(`Show project details`).argument(`<id>`, `Project ID`).action(async (projectId) => {
  const json = projectsGetCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.projects.get({ projectId });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printKeyValue([
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Workspace:`, result.workspaceId],
        [`Created:`, result.createdAt]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/projects/update.ts
import { Command as Command12 } from "commander";
var projectsUpdateCommand = new Command12(`update`).usage(`<id> --name <name>`).description(`Update a project's name`).argument(`<id>`, `Project ID`).requiredOption(`--name <name>`, `New project name`).action(async (projectId, opts) => {
  const json = projectsUpdateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.projects.update({
      projectId,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(projectId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Project ${projectId} updated.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/projects/delete.ts
import { Command as Command13 } from "commander";
var projectsDeleteCommand = new Command13(`delete`).description(`Delete a project and all its environments`).argument(`<id>`, `Project ID`).option(`--force`, `Skip confirmation prompt`).action(async (projectId, opts) => {
  const json = projectsDeleteCommand.parent?.parent?.opts().json ?? false;
  try {
    if (!opts.force) {
      if (json) {
        process.stderr.write(
          JSON.stringify({
            error: `VALIDATION_ERROR`,
            message: `--force is required with --json`,
            exitCode: EXIT_VALIDATION_ERROR
          }) + `
`
        );
        process.exit(EXIT_VALIDATION_ERROR);
      }
      const confirmed = await confirm(
        `Delete project ${projectId}? This cannot be undone. [y/N] `
      );
      if (!confirmed) {
        if (!isInteractive()) {
          process.stderr.write(
            `Use --force to skip confirmation in non-interactive mode.
`
          );
          process.exit(EXIT_VALIDATION_ERROR);
        }
        process.stderr.write(`Aborted.
`);
        return;
      }
    }
    const client = createClient();
    const result = await client.projects.delete({ projectId });
    if (isQuiet()) {
      printQuiet(projectId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Project ${projectId} deleted.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/environments/list.ts
import { Command as Command14 } from "commander";
var environmentsListCommand = new Command14(`list`).usage(`--project <id>`).description(`List environments in a project`).requiredOption(`--project <id>`, `Project ID`).action(async (opts) => {
  const json = environmentsListCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.environments.list({ projectId: opts.project });
    if (isQuiet()) {
      for (const e of result.environments) {
        printQuiet(e.id);
      }
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printTable(
        [`ID`, `Name`, `Created`],
        result.environments.map((e) => [
          e.id,
          e.name,
          e.createdAt.slice(0, 10)
        ])
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/environments/create.ts
import { Command as Command15 } from "commander";
var environmentsCreateCommand = new Command15(`create`).usage(`--project <id> --name <name>`).description(`Create a new environment`).requiredOption(`--project <id>`, `Project ID`).requiredOption(`--name <name>`, `Environment name`).action(async (opts) => {
  const json = environmentsCreateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.projects.createEnvironment({
      projectId: opts.project,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(result.environmentId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Environment created successfully.
`);
      printKeyValue([[`ID:`, result.environmentId]]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/environments/get.ts
import { Command as Command16 } from "commander";
var environmentsGetCommand = new Command16(`get`).description(`Show environment details`).argument(`<id>`, `Environment ID`).action(async (environmentId) => {
  const json = environmentsGetCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.environments.get({ environmentId });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printKeyValue([
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Project:`, result.projectId],
        [`Created:`, result.createdAt]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/environments/update.ts
import { Command as Command17 } from "commander";
var environmentsUpdateCommand = new Command17(`update`).usage(`<id> --name <name>`).description(`Update an environment's name`).argument(`<id>`, `Environment ID`).requiredOption(`--name <name>`, `New environment name`).action(async (environmentId, opts) => {
  const json = environmentsUpdateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.environments.update({
      environmentId,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(environmentId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Environment ${environmentId} updated.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/environments/delete.ts
import { Command as Command18 } from "commander";
var environmentsDeleteCommand = new Command18(`delete`).description(`Delete an environment and all its services`).argument(`<id>`, `Environment ID`).option(`--force`, `Skip confirmation prompt`).action(async (environmentId, opts) => {
  const json = environmentsDeleteCommand.parent?.parent?.opts().json ?? false;
  try {
    if (!opts.force) {
      if (json) {
        process.stderr.write(
          JSON.stringify({
            error: `VALIDATION_ERROR`,
            message: `--force is required with --json`,
            exitCode: EXIT_VALIDATION_ERROR
          }) + `
`
        );
        process.exit(EXIT_VALIDATION_ERROR);
      }
      const confirmed = await confirm(
        `Delete environment ${environmentId}? This cannot be undone. [y/N] `
      );
      if (!confirmed) {
        if (!isInteractive()) {
          process.stderr.write(
            `Use --force to skip confirmation in non-interactive mode.
`
          );
          process.exit(EXIT_VALIDATION_ERROR);
        }
        process.stderr.write(`Aborted.
`);
        return;
      }
    }
    const client = createClient();
    const result = await client.environments.delete({ environmentId });
    if (isQuiet()) {
      printQuiet(environmentId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Environment ${environmentId} deleted.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/list.ts
import { Command as Command19 } from "commander";
var servicesListCommand = new Command19(`list`).usage(`--environment <id>`).description(`List services in an environment`).requiredOption(`--environment <id>`, `Environment ID`).action(async (opts) => {
  const json = servicesListCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.list({
      environmentId: opts.environment
    });
    if (isQuiet()) {
      for (const s of result.services) {
        printQuiet(s.id);
      }
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printTable(
        [`ID`, `Name`, `Type`],
        result.services.map((s) => [s.id, s.name, s.type])
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/get.ts
import { Command as Command20 } from "commander";
var servicesGetCommand = new Command20(`get`).description(`Show service details`).argument(`<id>`, `Service ID`).action(async (serviceId) => {
  const json = servicesGetCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.get({ serviceId });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      const pairs = [
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Type:`, result.type],
        [`Environment:`, result.environmentId]
      ];
      if (`status` in result && result.status) {
        pairs.push([`Status:`, result.status]);
      }
      if (`region` in result && result.region) {
        pairs.push([`Region:`, result.region]);
      }
      if (`baseUrl` in result && result.baseUrl) {
        pairs.push([`Base URL:`, result.baseUrl]);
      }
      printKeyValue(pairs);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/update.ts
import { Command as Command21 } from "commander";
var servicesUpdateCommand = new Command21(`update`).usage(`<id> --name <name>`).description(`Update a service`).argument(`<id>`, `Service ID`).requiredOption(`--name <name>`, `New service name`).action(async (serviceId, opts) => {
  const json = servicesUpdateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.update({
      serviceId,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(serviceId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Service ${serviceId} updated.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/delete.ts
import { Command as Command22 } from "commander";
var servicesDeleteCommand = new Command22(`delete`).description(`Delete a service`).argument(`<id>`, `Service ID`).option(`--force`, `Skip confirmation prompt`).action(async (serviceId, opts) => {
  const json = servicesDeleteCommand.parent?.parent?.opts().json ?? false;
  try {
    if (!opts.force) {
      if (json) {
        process.stderr.write(
          JSON.stringify({
            error: `VALIDATION_ERROR`,
            message: `--force is required with --json`,
            exitCode: EXIT_VALIDATION_ERROR
          }) + `
`
        );
        process.exit(EXIT_VALIDATION_ERROR);
      }
      const confirmed = await confirm(
        `Delete service ${serviceId}? This cannot be undone. [y/N] `
      );
      if (!confirmed) {
        if (!isInteractive()) {
          process.stderr.write(
            `Use --force to skip confirmation in non-interactive mode.
`
          );
          process.exit(EXIT_VALIDATION_ERROR);
        }
        process.stderr.write(`Aborted.
`);
        return;
      }
    }
    const client = createClient();
    const result = await client.services.delete({
      serviceId,
      forceDelete: opts.force ?? false
    });
    if (isQuiet()) {
      printQuiet(serviceId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Service ${serviceId} deleted.`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/get-secret.ts
import { Command as Command23 } from "commander";
var servicesGetSecretCommand = new Command23(`get-secret`).description(`Show service connection credentials`).argument(`<id>`, `Service ID`).action(async (serviceId) => {
  const json = servicesGetSecretCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.getSecret({ serviceId });
    if (isQuiet()) {
      printQuiet(result.secret);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      process.stdout.write(result.secret + `
`);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/create/postgres.ts
import { Command as Command24, Option } from "commander";

// src/lib/polling.ts
async function pollUntilDone(options) {
  const intervalMs = options.intervalMs ?? 2e3;
  const timeoutMs = (options.timeoutSeconds ?? 300) * 1e3;
  const start = Date.now();
  while (true) {
    const elapsed = Date.now() - start;
    if (elapsed >= timeoutMs) {
      throw new Error(
        `Timed out after ${Math.round(elapsed / 1e3)}s waiting for operation to complete.`
      );
    }
    const result = await options.fn();
    if (result.done) {
      return result.value;
    }
    options.onPoll?.(Math.round(elapsed / 1e3));
    await new Promise((resolve4) => setTimeout(resolve4, intervalMs));
  }
}

// src/commands/services/create/postgres.ts
var REGION_LABELS = {
  "us-east-1": `US East (N. Virginia)`,
  "eu-west-1": `EU West (Ireland)`,
  "ca-west-1": `CA West (Canada)`
};
var servicesCreatePostgresCommand = new Command24(`postgres`).usage(`--environment <id> --database-url <url> --region <region> [options]`).description(`Connect a Postgres database for sync`).requiredOption(`--environment <id>`, `Environment ID`).requiredOption(`--database-url <url>`, `Postgres connection string`).addOption(
  new Option(`--region <region>`, `Deployment region`).choices(Object.keys(REGION_LABELS)).makeOptionMandatory()
).option(`--name <name>`, `Service name`).option(`--pooled-database-url <url>`, `Pooled Postgres connection string`).option(
  `--db-pool-size <size>`,
  `Database connection pool size (1-20)`,
  parseInt
).option(`--manual-table-publishing`, `Opt out of automatic table publishing`).option(
  `--replication-idle-timeout <seconds>`,
  `Idle replication connection timeout in seconds (0 to disable; recommended for Neon)`,
  parseInt
).option(`--wait`, `Wait for service to become active before returning`).option(
  `--wait-timeout <seconds>`,
  `Timeout in seconds when using --wait`,
  `300`
).addHelpText(
  `after`,
  `
Regions:
` + Object.entries(REGION_LABELS).map(([k, v]) => `  ${k}  ${v}`).join(`
`)
).action(async (opts) => {
  const json = servicesCreatePostgresCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const options = {};
    if (opts.pooledDatabaseUrl)
      options.pooledDatabaseUrl = opts.pooledDatabaseUrl;
    if (opts.dbPoolSize) options.dbPoolSize = opts.dbPoolSize;
    if (opts.manualTablePublishing) options.manualTablePublishing = true;
    if (opts.replicationIdleTimeout !== void 0)
      options.replicationIdleTimeout = opts.replicationIdleTimeout;
    const result = await client.services.postgres.create({
      environmentId: opts.environment,
      databaseUrl: opts.databaseUrl,
      region: opts.region,
      ...opts.name ? { serviceName: opts.name } : {},
      ...Object.keys(options).length > 0 ? { options } : {}
    });
    const serviceId = result.id;
    let finalStatus = result.status;
    if (opts.wait && finalStatus !== `active` && finalStatus !== `error`) {
      if (!isQuiet() && !json) {
        process.stderr.write(`Waiting for service to become active...
`);
      }
      const polled = await pollUntilDone({
        fn: async () => {
          const service = await client.services.postgres.get({ serviceId });
          const status = service.status;
          if (status === `active` || status === `error`) {
            return { done: true, value: status };
          }
          return { done: false, value: status ?? `pending` };
        },
        timeoutSeconds: isNaN(parseInt(opts.waitTimeout, 10)) ? 300 : parseInt(opts.waitTimeout, 10),
        onPoll: (elapsed) => {
          if (!isQuiet() && !json) {
            process.stderr.write(
              `  Waiting for service (${elapsed}s elapsed)...
`
            );
          }
        }
      });
      finalStatus = polled;
    }
    if (finalStatus === `error`) {
      if (json) {
        printJson({ ...result, status: finalStatus });
      } else if (!isQuiet()) {
        process.stderr.write(`Service entered error state.
`);
        printKeyValue([
          [`ID:`, serviceId],
          [`Status:`, `error`]
        ]);
      }
      process.exit(1);
    }
    if (isQuiet()) {
      printQuiet(serviceId);
      return;
    }
    if (json) {
      printJson(opts.wait ? { ...result, status: finalStatus } : result);
    } else {
      printSuccess(`Service created successfully.
`);
      printKeyValue([
        [`ID:`, serviceId],
        [`Status:`, finalStatus],
        [`Secret:`, result.sourceSecret]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/create/streams.ts
import { Command as Command25 } from "commander";
var servicesCreateStreamsCommand = new Command25(`streams`).usage(`--environment <id> --name <name>`).description(`Create a durable streams service`).requiredOption(`--environment <id>`, `Environment ID`).requiredOption(`--name <name>`, `Service name`).action(async (opts) => {
  const json = servicesCreateStreamsCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.streams.create({
      environmentId: opts.environment,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(result.serviceId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Service created successfully.
`);
      printKeyValue([
        [`ID:`, result.serviceId],
        [`Name:`, result.name],
        [`Base URL:`, result.baseUrl],
        [`Secret:`, result.secret]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/create/proxy.ts
import { Command as Command26 } from "commander";
var servicesCreateProxyCommand = new Command26(`proxy`).usage(`--environment <id> --name <name> --allowlist <patterns>`).description(`Create a proxy service`).requiredOption(`--environment <id>`, `Environment ID`).requiredOption(`--name <name>`, `Service name`).requiredOption(`--allowlist <patterns>`, `Comma-separated URL patterns`).action(async (opts) => {
  const json = servicesCreateProxyCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const allowlist = opts.allowlist.split(`,`).map((s) => s.trim());
    const client = createClient();
    const result = await client.services.proxy.create({
      environmentId: opts.environment,
      name: opts.name,
      allowlist
    });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Service created successfully.
`);
      printKeyValue([
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Base URL:`, result.baseUrl],
        [`Secret:`, result.secret]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/services/create/yjs.ts
import { Command as Command27 } from "commander";
var servicesCreateYjsCommand = new Command27(`yjs`).usage(`--environment <id> --name <name>`).description(`Create a Yjs collaborative document service`).requiredOption(`--environment <id>`, `Environment ID`).requiredOption(`--name <name>`, `Service name`).action(async (opts) => {
  const json = servicesCreateYjsCommand.parent?.parent?.parent?.opts().json ?? false;
  try {
    const client = createClient();
    const result = await client.services.yjs.create({
      environmentId: opts.environment,
      name: opts.name
    });
    if (isQuiet()) {
      printQuiet(result.id);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printSuccess(`Service created successfully.
`);
      printKeyValue([
        [`ID:`, result.id],
        [`Name:`, result.name],
        [`Base URL:`, result.baseUrl],
        [`Secret:`, result.secret]
      ]);
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/durable-streams/export.ts
import { resolve as resolve2 } from "path";
import { Command as Command28 } from "commander";

// src/lib/durable-stream-archive.ts
import { createReadStream } from "fs";
import {
  access,
  mkdtemp,
  mkdir as mkdir3,
  open,
  readdir,
  readFile as readFile3,
  rm,
  stat
} from "fs/promises";
import { tmpdir } from "os";
import { basename, dirname, join as join4, posix, resolve } from "path";
import { create as createTar, extract as extractTar } from "tar";
var ARCHIVE_VERSION = 1;
var LIST_PAGE_SIZE = 1e3;
var MAX_FILENAME_BYTES = 240;
var STREAM_NEXT_OFFSET_HEADER = `Stream-Next-Offset`;
var STREAM_UP_TO_DATE_HEADER = `Stream-Up-To-Date`;
var STREAM_TTL_HEADER = `Stream-TTL`;
var STREAM_EXPIRES_AT_HEADER = `Stream-Expires-At`;
var STREAM_TOTAL_SIZE_HEADER = `Stream-Total-Size`;
var STREAM_WRITE_UNITS_HEADER = `Stream-Write-Units`;
var STREAM_CLOSED_HEADER = `Stream-Closed`;
var STREAM_METADATA_HEADER = `Electric-Stream-Metadata`;
function defaultExportDirectory(now = /* @__PURE__ */ new Date()) {
  const timestamp = now.toISOString().replace(/[:.]/g, `-`);
  return resolve(`durable-streams-${timestamp}`);
}
function parseHeader(value) {
  const separator = value.indexOf(`:`);
  if (separator <= 0) {
    throw new Error(`Invalid --header value "${value}"; expected "Name: value"`);
  }
  const name = value.slice(0, separator).trim();
  const headerValue = value.slice(separator + 1).trim();
  if (!name) {
    throw new Error(`Invalid --header value "${value}"; expected "Name: value"`);
  }
  try {
    new Headers({ [name]: headerValue });
  } catch {
    throw new Error(`Invalid HTTP header "${name}"`);
  }
  return [name, headerValue];
}
function normalizeBaseUrl(value) {
  let url;
  try {
    url = new URL(value);
  } catch {
    throw new Error(`Invalid URL: ${value}`);
  }
  if (url.protocol !== `http:` && url.protocol !== `https:`) {
    throw new Error(`URL must use http or https: ${value}`);
  }
  if (url.search || url.hash) {
    throw new Error(`URL must not contain a query string or fragment: ${value}`);
  }
  return url.href.replace(/\/$/, ``);
}
function encodeStreamFilename(streamPath) {
  if (!streamPath) throw new Error(`Stream path must not be empty`);
  const encoded = encodeURIComponent(streamPath);
  if (Buffer.byteLength(`${encoded}.json`) > MAX_FILENAME_BYTES) {
    throw new Error(
      `URL-encoded stream path exceeds ${MAX_FILENAME_BYTES} filename bytes: ${streamPath}`
    );
  }
  return encoded;
}
function streamUrl(baseUrl, streamPath) {
  const encodedPath = streamPath.split(`/`).map((segment) => encodeURIComponent(segment)).join(`/`);
  return `${normalizeBaseUrl(baseUrl)}/${encodedPath}`;
}
function controlListUrl(serviceUrl, cursor) {
  const url = new URL(`${normalizeBaseUrl(serviceUrl)}/__ds/streams`);
  url.searchParams.set(`limit`, String(LIST_PAGE_SIZE));
  if (cursor) url.searchParams.set(`cursor`, cursor);
  return url.href;
}
function isRecord(value) {
  return typeof value === `object` && value !== null && !Array.isArray(value);
}
function isIsoDate(value) {
  return !Number.isNaN(new Date(value).getTime());
}
function parseListedStream(value) {
  if (!isRecord(value))
    throw new Error(`Stream list contains a non-object item`);
  const { id, path, contentType, expiresAt, metadata, createdAt } = value;
  if (typeof id !== `string` || typeof path !== `string` || path.length === 0 || typeof contentType !== `string` || contentType.length === 0 || !isRecord(metadata) || typeof createdAt !== `string` || !isIsoDate(createdAt) || expiresAt !== null && (typeof expiresAt !== `string` || !isIsoDate(expiresAt))) {
    throw new Error(`Stream list contains invalid metadata`);
  }
  return {
    id,
    path,
    contentType,
    expiresAt,
    metadata,
    createdAt
  };
}
function parseListPage(value) {
  if (!isRecord(value) || !Array.isArray(value.streams)) {
    throw new Error(`Stream list response has an invalid shape`);
  }
  if (typeof value.hasMore !== `boolean` || value.nextCursor !== null && typeof value.nextCursor !== `string`) {
    throw new Error(`Stream list response has invalid pagination metadata`);
  }
  const hasMore = value.hasMore;
  const nextCursor = value.nextCursor;
  if (hasMore !== (nextCursor !== null)) {
    throw new Error(`Stream list response has inconsistent pagination metadata`);
  }
  return {
    streams: value.streams.map(parseListedStream),
    hasMore,
    nextCursor
  };
}
async function responseError(response, operation) {
  const body = (await response.text()).slice(0, 2e3).trim();
  const detail = body ? `: ${body}` : ``;
  return new Error(`${operation} failed with HTTP ${response.status}${detail}`);
}
async function listAllStreams(serviceUrl, readToken, fetchClient) {
  const streams = [];
  const seenCursors = /* @__PURE__ */ new Set();
  let cursor;
  while (true) {
    const response = await fetchClient(controlListUrl(serviceUrl, cursor), {
      headers: { Authorization: `Bearer ${readToken}` }
    });
    if (!response.ok) throw await responseError(response, `Listing streams`);
    const page = parseListPage(await response.json());
    streams.push(...page.streams);
    if (!page.nextCursor) return streams;
    if (seenCursors.has(page.nextCursor)) {
      throw new Error(`Stream list returned a repeated pagination cursor`);
    }
    seenCursors.add(page.nextCursor);
    cursor = page.nextCursor;
  }
}
function normalizeContentType(value) {
  return value.split(`;`, 1)[0].trim().toLowerCase();
}
function jsonArrayContents(value, streamPath) {
  const trimmed = value.trim();
  if (!trimmed) return ``;
  let parsed;
  try {
    parsed = JSON.parse(trimmed);
  } catch {
    throw new Error(`Stream ${streamPath} returned invalid JSON`);
  }
  if (!Array.isArray(parsed) || !trimmed.startsWith(`[`) || !trimmed.endsWith(`]`)) {
    throw new Error(`Stream ${streamPath} returned a non-array JSON page`);
  }
  return trimmed.slice(1, -1).trim();
}
async function writeResponseBody(file, response) {
  if (!response.body) return;
  const reader = response.body.getReader();
  try {
    while (true) {
      const chunk = await reader.read();
      if (chunk.done) return;
      await file.write(chunk.value);
    }
  } finally {
    reader.releaseLock();
  }
}
async function exportStreamData(args) {
  const authHeaders = { Authorization: `Bearer ${args.readToken}` };
  const head = await args.fetchClient(args.url, {
    method: `HEAD`,
    headers: authHeaders
  });
  if (!head.ok)
    throw await responseError(head, `Reading metadata for ${args.streamPath}`);
  const contentType = head.headers.get(`Content-Type`) ?? args.listedContentType;
  const isJson = normalizeContentType(contentType) === `application/json`;
  const file = await open(args.outputFile, `wx`);
  let offset = `-1`;
  let wroteJsonItems = false;
  let tailOffset = head.headers.get(STREAM_NEXT_OFFSET_HEADER);
  let closed = head.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
  try {
    if (isJson) await file.write(`[`);
    while (true) {
      const url = new URL(args.url);
      url.searchParams.set(`offset`, offset);
      const response = await args.fetchClient(url, { headers: authHeaders });
      if (!response.ok) {
        throw await responseError(response, `Reading ${args.streamPath}`);
      }
      if (isJson) {
        const contents = jsonArrayContents(
          await response.text(),
          args.streamPath
        );
        if (contents) {
          if (wroteJsonItems) await file.write(`,`);
          await file.write(contents);
          wroteJsonItems = true;
        }
      } else {
        await writeResponseBody(file, response);
      }
      const upToDate = response.headers.has(STREAM_UP_TO_DATE_HEADER);
      const nextOffset = response.headers.get(STREAM_NEXT_OFFSET_HEADER);
      tailOffset = nextOffset ?? tailOffset;
      closed = closed || response.headers.get(STREAM_CLOSED_HEADER)?.toLowerCase() === `true`;
      if (upToDate) break;
      if (!nextOffset || nextOffset === offset) {
        throw new Error(
          `Stream ${args.streamPath} did not provide a progressing next offset`
        );
      }
      offset = nextOffset;
    }
    if (isJson) await file.write(`]`);
  } finally {
    await file.close();
  }
  return {
    closed,
    tailOffset,
    ttlSeconds: head.headers.get(STREAM_TTL_HEADER),
    expiresAt: head.headers.get(STREAM_EXPIRES_AT_HEADER),
    totalSize: head.headers.get(STREAM_TOTAL_SIZE_HEADER),
    writeUnits: head.headers.get(STREAM_WRITE_UNITS_HEADER),
    archivedSize: (await stat(args.outputFile)).size,
    contentType
  };
}
async function ensureMissing(path) {
  try {
    await access(path);
  } catch (error) {
    if (error instanceof Error && `code` in error && error.code === `ENOENT`) {
      return;
    }
    throw error;
  }
  throw new Error(`Refusing to overwrite existing path: ${path}`);
}
async function exportDurableStreams(config, options = {}) {
  const fetchClient = options.fetch ?? globalThis.fetch;
  const log = options.log ?? console.log;
  const exportedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
  const directory = resolve(config.outputDirectory);
  const archive = `${directory}.tar.gz`;
  await ensureMissing(directory);
  await ensureMissing(archive);
  const streams = await listAllStreams(
    config.serviceUrl,
    config.readToken,
    fetchClient
  );
  await mkdir3(dirname(directory), { recursive: true });
  await mkdir3(directory);
  for (const [index, stream] of streams.entries()) {
    const encodedName = encodeStreamFilename(stream.path);
    const dataFile = join4(directory, `${encodedName}.bin`);
    const metadataFile = join4(directory, `${encodedName}.json`);
    log(`[${index + 1}/${streams.length}] Exporting ${stream.path}`);
    const readMetadata = await exportStreamData({
      url: streamUrl(config.serviceUrl, stream.path),
      readToken: config.readToken,
      outputFile: dataFile,
      listedContentType: stream.contentType,
      streamPath: stream.path,
      fetchClient
    });
    const metadata = {
      archiveVersion: ARCHIVE_VERSION,
      id: stream.id,
      path: stream.path,
      contentType: readMetadata.contentType,
      expiresAt: readMetadata.expiresAt ?? stream.expiresAt,
      metadata: stream.metadata,
      createdAt: stream.createdAt,
      exportedAt,
      closed: readMetadata.closed,
      tailOffset: readMetadata.tailOffset,
      ttlSeconds: readMetadata.ttlSeconds,
      totalSize: readMetadata.totalSize,
      writeUnits: readMetadata.writeUnits,
      archivedSize: readMetadata.archivedSize
    };
    await open(metadataFile, `wx`).then(async (file) => {
      try {
        await file.writeFile(`${JSON.stringify(metadata, null, 2)}
`);
      } finally {
        await file.close();
      }
    });
  }
  await createTar(
    {
      cwd: dirname(directory),
      file: archive,
      gzip: true,
      portable: true
    },
    [basename(directory)]
  );
  return { directory, archive, streamCount: streams.length };
}
function requiredString(value, key) {
  const item = value[key];
  if (typeof item !== `string` || item.length === 0) {
    throw new Error(`Archive metadata field ${key} must be a non-empty string`);
  }
  return item;
}
function parseArchiveMetadata(value) {
  if (!isRecord(value) || value.archiveVersion !== ARCHIVE_VERSION) {
    throw new Error(`Unsupported or missing archiveVersion`);
  }
  const expiresAt = value.expiresAt;
  if (expiresAt !== null && (typeof expiresAt !== `string` || !isIsoDate(expiresAt))) {
    throw new Error(`Archive metadata expiresAt must be null or an ISO date`);
  }
  if (!isRecord(value.metadata)) {
    throw new Error(`Archive metadata field metadata must be an object`);
  }
  if (typeof value.closed !== `boolean`) {
    throw new Error(`Archive metadata field closed must be a boolean`);
  }
  if (typeof value.archivedSize !== `number` || !Number.isSafeInteger(value.archivedSize) || value.archivedSize < 0) {
    throw new Error(
      `Archive metadata field archivedSize must be a non-negative integer`
    );
  }
  const nullableString = (key) => {
    const item = value[key];
    if (item === null) return null;
    if (typeof item !== `string`) {
      throw new Error(`Archive metadata field ${key} must be a string or null`);
    }
    return item;
  };
  const createdAt = requiredString(value, `createdAt`);
  const exportedAt = requiredString(value, `exportedAt`);
  if (!isIsoDate(createdAt) || !isIsoDate(exportedAt)) {
    throw new Error(`Archive creation timestamps must be ISO dates`);
  }
  return {
    archiveVersion: ARCHIVE_VERSION,
    id: requiredString(value, `id`),
    path: requiredString(value, `path`),
    contentType: requiredString(value, `contentType`),
    expiresAt,
    metadata: value.metadata,
    createdAt,
    exportedAt,
    closed: value.closed,
    tailOffset: nullableString(`tailOffset`),
    ttlSeconds: nullableString(`ttlSeconds`),
    totalSize: nullableString(`totalSize`),
    writeUnits: nullableString(`writeUnits`),
    archivedSize: value.archivedSize
  };
}
function safeArchiveEntry(entryPath, type) {
  const normalized = posix.normalize(entryPath);
  if (posix.isAbsolute(entryPath) || normalized === `..` || normalized.startsWith(`../`) || normalized.includes(`/../`)) {
    throw new Error(`Archive contains an unsafe path: ${entryPath}`);
  }
  if (![`File`, `OldFile`, `Directory`].includes(type)) {
    throw new Error(
      `Archive contains unsupported entry type ${type}: ${entryPath}`
    );
  }
  return true;
}
async function walkFiles(directory) {
  const files = [];
  for (const entry of await readdir(directory, { withFileTypes: true })) {
    const path = join4(directory, entry.name);
    if (entry.isDirectory()) {
      files.push(...await walkFiles(path));
    } else if (entry.isFile()) {
      files.push(path);
    } else {
      throw new Error(
        `Archive contains an unsupported filesystem entry: ${path}`
      );
    }
  }
  return files;
}
async function readImportEntries(directory) {
  const files = await walkFiles(directory);
  const fileSet = new Set(files);
  const metadataFiles = files.filter((file) => file.endsWith(`.json`)).sort();
  const dataFiles = files.filter((file) => file.endsWith(`.bin`));
  const unexpected = files.filter(
    (file) => !file.endsWith(`.json`) && !file.endsWith(`.bin`)
  );
  if (unexpected.length > 0) {
    throw new Error(`Archive contains an unexpected file: ${unexpected[0]}`);
  }
  const entries = [];
  const streamPaths = /* @__PURE__ */ new Set();
  for (const metadataFile of metadataFiles) {
    const stem = metadataFile.slice(0, -`.json`.length);
    const dataFile = `${stem}.bin`;
    if (!fileSet.has(dataFile)) {
      throw new Error(`Missing data file for ${metadataFile}`);
    }
    const metadata = parseArchiveMetadata(
      JSON.parse(await readFile3(metadataFile, `utf8`))
    );
    const encodedName = basename(stem);
    let decodedName;
    try {
      decodedName = decodeURIComponent(encodedName);
    } catch {
      throw new Error(`Invalid URL-encoded stream filename: ${encodedName}`);
    }
    if (decodedName !== metadata.path) {
      throw new Error(
        `Stream filename ${encodedName} does not match metadata path ${metadata.path}`
      );
    }
    if (streamPaths.has(metadata.path)) {
      throw new Error(
        `Archive contains duplicate stream path: ${metadata.path}`
      );
    }
    streamPaths.add(metadata.path);
    const dataSize = (await stat(dataFile)).size;
    if (dataSize !== metadata.archivedSize) {
      throw new Error(
        `Data size for ${metadata.path} is ${dataSize}, expected ${metadata.archivedSize}`
      );
    }
    entries.push({ metadata, dataFile });
  }
  if (dataFiles.length !== entries.length) {
    throw new Error(`Archive contains a data file without matching metadata`);
  }
  return entries;
}
function importHeaders(customHeaders, metadata) {
  const headers = new Headers(customHeaders);
  headers.set(`Content-Type`, metadata.contentType);
  headers.set(STREAM_METADATA_HEADER, JSON.stringify(metadata.metadata));
  if (metadata.expiresAt) {
    headers.set(STREAM_EXPIRES_AT_HEADER, metadata.expiresAt);
  } else if (metadata.ttlSeconds !== null) {
    headers.set(STREAM_TTL_HEADER, metadata.ttlSeconds);
  }
  if (metadata.closed) headers.set(STREAM_CLOSED_HEADER, `true`);
  return headers;
}
async function importEntry(config, entry, fetchClient) {
  const url = streamUrl(config.serverUrl, entry.metadata.path);
  const target = await fetchClient(url, {
    method: `HEAD`,
    headers: config.headers
  });
  if (target.ok) {
    throw new Error(`Target stream already exists: ${entry.metadata.path}`);
  }
  if (target.status !== 404) {
    throw await responseError(target, `Checking target ${entry.metadata.path}`);
  }
  const dataSize = (await stat(entry.dataFile)).size;
  const request = {
    method: `PUT`,
    headers: importHeaders(config.headers, entry.metadata),
    duplex: `half`
  };
  if (dataSize > 0) {
    request.body = createReadStream(
      entry.dataFile
    );
  }
  const response = await fetchClient(url, request);
  if (!response.ok) {
    throw await responseError(response, `Creating ${entry.metadata.path}`);
  }
}
async function importDurableStreams(config, options = {}) {
  const fetchClient = options.fetch ?? globalThis.fetch;
  const log = options.log ?? console.log;
  const temporaryDirectory = await mkdtemp(
    join4(tmpdir(), `durable-stream-import-`)
  );
  try {
    await extractTar({
      cwd: temporaryDirectory,
      file: config.archive,
      strict: true,
      filter: (entryPath, entry) => {
        const type = `type` in entry ? entry.type : entry.isDirectory() ? `Directory` : entry.isFile() ? `File` : `Unknown`;
        return safeArchiveEntry(entryPath, type);
      }
    });
    const entries = await readImportEntries(temporaryDirectory);
    for (const [index, entry] of entries.entries()) {
      log(`[${index + 1}/${entries.length}] Importing ${entry.metadata.path}`);
      await importEntry(config, entry, fetchClient);
    }
    return { streamCount: entries.length };
  } finally {
    await rm(temporaryDirectory, { recursive: true, force: true });
  }
}

// src/commands/durable-streams/export.ts
var durableStreamsExportCommand = new Command28(`export`).description(`Export all streams in a service to a tar.gz archive`).requiredOption(
  `--service-url <url>`,
  `Durable Streams service URL, ending in the service ID`
).requiredOption(`--read-token <token>`, `Service read token`).option(`--output <folder>`, `Output folder; defaults to a timestamped name`).action(async (options) => {
  const json = durableStreamsExportCommand.parent?.parent?.opts().json ?? false;
  try {
    const result = await exportDurableStreams(
      {
        serviceUrl: normalizeBaseUrl(options.serviceUrl),
        readToken: options.readToken,
        outputDirectory: resolve2(options.output ?? defaultExportDirectory())
      },
      {
        log: isQuiet() ? () => void 0 : (message) => process.stderr.write(`${message}
`)
      }
    );
    if (isQuiet()) {
      printQuiet(result.archive);
    } else if (json) {
      printJson(result);
    } else {
      printKeyValue([
        [`Streams:`, String(result.streamCount)],
        [`Directory:`, result.directory],
        [`Archive:`, result.archive]
      ]);
    }
  } catch (error) {
    await handleError(error, json, `durable-streams export`);
  }
});

// src/commands/durable-streams/import.ts
import { resolve as resolve3 } from "path";
import { Command as Command29 } from "commander";
function collectHeader(value, previous) {
  return [...previous, value];
}
var durableStreamsImportCommand = new Command29(`import`).description(`Recreate streams from a tar.gz archive`).requiredOption(`--archive <file>`, `Archive created by the export command`).requiredOption(`--server-url <url>`, `Target Durable Streams server URL`).option(
  `-H, --header <header>`,
  `Target request header in "Name: value" form; may be repeated`,
  collectHeader,
  []
).action(async (options) => {
  const json = durableStreamsImportCommand.parent?.parent?.opts().json ?? false;
  try {
    const headers = {};
    for (const value of options.header) {
      const [name, headerValue] = parseHeader(value);
      headers[name] = headerValue;
    }
    const result = await importDurableStreams(
      {
        archive: resolve3(options.archive),
        serverUrl: normalizeBaseUrl(options.serverUrl),
        headers
      },
      {
        log: isQuiet() ? () => void 0 : (message) => process.stderr.write(`${message}
`)
      }
    );
    if (isQuiet()) {
      printQuiet(String(result.streamCount));
    } else if (json) {
      printJson(result);
    } else {
      printKeyValue([[`Streams imported:`, String(result.streamCount)]]);
    }
  } catch (error) {
    await handleError(error, json, `durable-streams import`);
  }
});

// src/commands/claim/create.ts
import { Command as Command30 } from "commander";
var claimCreateCommand = new Command30(`create`).description(`Provision a Postgres database with Sync`).action(async () => {
  const json = claimCreateCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createPublicClient();
    const result = await client.claimable.create({});
    if (isQuiet()) {
      printQuiet(result.claimId);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      printKeyValue([[`Claim ID:`, result.claimId]]);
      process.stderr.write(
        `
Check provisioning status with: electric claimable status ${result.claimId}
`
      );
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/claim/status.ts
import { Command as Command31 } from "commander";
var claimStatusCommand = new Command31(`status`).description(`Check provisioning status`).argument(`<claimId>`, `Claim ID`).action(async (claimId) => {
  const json = claimStatusCommand.parent?.parent?.opts().json ?? false;
  try {
    const client = createPublicClient();
    const result = await client.claimable.get({ claimId });
    if (isQuiet()) {
      printQuiet(result.state);
      return;
    }
    if (json) {
      printJson(result);
    } else {
      const pairs = [[`State:`, result.state]];
      if (result.claim_link) {
        pairs.push([
          `Claim link:`,
          `${getDashboardUrl()}/claim?uuid=${claimId}`
        ]);
      }
      if (result.connection_uri) {
        pairs.push([`Database URL:`, result.connection_uri]);
      }
      if (result.service_type) {
        pairs.push([`Service type:`, result.service_type]);
      }
      if (result.error) {
        pairs.push([`Error:`, result.error]);
      }
      printKeyValue(pairs);
      if (result.state === `ready` && result.claim_link) {
        process.stderr.write(
          `
Ready to claim. Run: electric claimable claim ${claimId}
`
        );
      }
    }
  } catch (error) {
    await handleError(error, json);
  }
});

// src/commands/claim/claim.ts
import { Command as Command32 } from "commander";
var claimCommand = new Command32(`claim`).description(`Claim a provisioned service into your workspace`).argument(`<claimId>`, `Claim ID`).option(`--workspace <id>`, `Workspace ID`).option(
  `--environment <id>`,
  `Environment ID (optional \u2014 auto-created if omitted)`
).action(async (claimId, opts) => {
  const json = claimCommand.parent?.parent?.opts().json ?? false;
  try {
    await runClaim(claimId, opts, json);
  } catch (error) {
    await handleError(error, json);
  }
});
async function runClaim(claimId, opts, json) {
  const publicClient = createPublicClient();
  const status = await publicClient.claimable.get({ claimId });
  if (status.state === `claimed`) {
    exitWithError(`This service has already been claimed.`, EXIT_CONFLICT, json);
  }
  if (status.state !== `ready`) {
    exitWithError(
      `Claimable service is not ready (state: ${status.state}).`,
      EXIT_CONFLICT,
      json
    );
  }
  if (status.claim_link) {
    const claimUrl = `${getDashboardUrl()}/claim?uuid=${claimId}`;
    if (json) {
      printJson({ claimId, claim_url: claimUrl, claimed: false });
    } else {
      process.stderr.write(`Visit the link below to claim the database:

`);
      process.stdout.write(`${claimUrl}
`);
    }
    return;
  }
  if (!json) {
    process.stderr.write(`Database claimed. Adding service to workspace...
`);
  }
  const client = createClient();
  const workspaceId = await resolveWorkspace(client, opts.workspace);
  const result = await client.claimable.claim({
    claimId,
    workspaceId,
    ...opts.environment ? { environmentId: opts.environment } : {}
  });
  if (isQuiet()) {
    printQuiet(result.serviceId);
    return;
  }
  if (json) {
    printJson(result);
  } else {
    printSuccess(`Source claimed successfully.
`);
    printKeyValue([
      [`Service ID:`, result.serviceId],
      [`Service type:`, result.serviceType],
      [`Project ID:`, result.projectId],
      [`Environment ID:`, result.environmentId]
    ]);
  }
}

// src/index.ts
var jsonMode = process.argv.includes(`--json`);
var program = new Command33().name(`electric`).description(`CLI for Electric Cloud`).version(CLI_VERSION).option(`--json`, `Output as JSON`).configureOutput({
  writeErr: (str) => {
    if (jsonMode) {
      const message = str.replace(/^error:\s*/i, ``).trim();
      process.stderr.write(
        JSON.stringify({
          error: `VALIDATION_ERROR`,
          message,
          exitCode: EXIT_VALIDATION_ERROR
        }) + `
`
      );
    } else {
      process.stderr.write(str);
    }
  },
  outputError: (str, write) => write(str)
}).exitOverride((err) => {
  if (err.exitCode === 0) {
    process.exit(0);
  }
  process.exit(EXIT_VALIDATION_ERROR);
}).option(`-q, --quiet`, `Output only the primary resource ID`).option(`--verbose`, `Log HTTP request details to stderr`).option(`--token <token>`, `Use this API token for authentication`).option(`--no-input`, `Never prompt for input (fail instead)`).hook(`preAction`, (thisCommand) => {
  const opts = thisCommand.opts();
  if (opts.token) {
    setExplicitToken(opts.token);
  }
  if (opts.input === false) {
    setNoInput(true);
  }
  if (opts.verbose) {
    setVerbose(true);
  }
  if (opts.quiet) {
    setQuiet(true);
  }
  ;
  program.__startTime = performance.now();
}).hook(`postAction`, (thisCommand, actionCommand) => {
  const startTime = program.__startTime;
  const durationMs = startTime ? Math.round(performance.now() - startTime) : void 0;
  const rootOpts = program.opts();
  const flagsUsed = collectFlags(rootOpts, actionCommand.opts());
  capture({
    event: `cli_command_executed`,
    properties: {
      command: getFullCommandName(actionCommand),
      cli_version: CLI_VERSION,
      auth_type: getAuthType(),
      output_format: rootOpts.json ? `json` : `text`,
      exit_code: 0,
      duration_ms: durationMs,
      flags_used: flagsUsed
    }
  });
});
var auth = new Command33(`auth`).description(
  `Manage authentication and API tokens`
);
var token = new Command33(`token`).description(`Manage API tokens`);
token.addCommand(tokenCreateCommand);
token.addCommand(tokenListCommand);
token.addCommand(tokenRevokeCommand);
auth.addCommand(loginCommand);
auth.addCommand(logoutCommand);
auth.addCommand(whoamiCommand);
auth.addCommand(token);
program.addCommand(auth);
var workspaces = new Command33(`workspaces`).description(
  `List and inspect workspaces`
);
workspaces.addCommand(workspacesListCommand);
workspaces.addCommand(workspacesGetCommand);
program.addCommand(workspaces);
var projects = new Command33(`projects`).description(
  `Manage projects within a workspace`
);
projects.addCommand(projectsListCommand);
projects.addCommand(projectsCreateCommand);
projects.addCommand(projectsGetCommand);
projects.addCommand(projectsUpdateCommand);
projects.addCommand(projectsDeleteCommand);
program.addCommand(projects);
var environments = new Command33(`environments`).description(
  `Manage environments within a project`
);
environments.addCommand(environmentsListCommand);
environments.addCommand(environmentsCreateCommand);
environments.addCommand(environmentsGetCommand);
environments.addCommand(environmentsUpdateCommand);
environments.addCommand(environmentsDeleteCommand);
program.addCommand(environments);
var services = new Command33(`services`).description(
  `Manage services (Postgres, streams, proxy, yjs)`
);
var servicesCreate = new Command33(`create`).description(`Create a new service`);
servicesCreate.addCommand(servicesCreatePostgresCommand);
servicesCreate.addCommand(servicesCreateStreamsCommand);
servicesCreate.addCommand(servicesCreateProxyCommand);
servicesCreate.addCommand(servicesCreateYjsCommand);
services.addCommand(servicesListCommand);
services.addCommand(servicesGetCommand);
services.addCommand(servicesUpdateCommand);
services.addCommand(servicesDeleteCommand);
services.addCommand(servicesGetSecretCommand);
services.addCommand(servicesCreate);
program.addCommand(services);
var durableStreams = new Command33(`durable-streams`).description(
  `Export and import Durable Streams archives`
);
durableStreams.addCommand(durableStreamsExportCommand);
durableStreams.addCommand(durableStreamsImportCommand);
program.addCommand(durableStreams);
var claimable = new Command33(`claimable`).description(
  `Immediately provision a Postgres database with Sync (no auth required)`
);
claimable.addCommand(claimCreateCommand);
claimable.addCommand(claimStatusCommand);
claimable.addCommand(claimCommand);
program.addCommand(claimable);
function propagateErrorConfig(cmd) {
  for (const sub of cmd.commands) {
    sub.configureOutput(program.configureOutput());
    sub.exitOverride((err) => {
      if (err.exitCode === 0) {
        process.exit(0);
      }
      process.exit(EXIT_VALIDATION_ERROR);
    });
    propagateErrorConfig(sub);
  }
}
propagateErrorConfig(program);
function getFullCommandName(cmd) {
  const parts = [];
  let current = cmd;
  while (current && current !== program) {
    parts.unshift(current.name());
    current = current.parent;
  }
  return parts.join(` `);
}
function getAuthType() {
  const opts = program.opts();
  if (opts.token) return `api_token`;
  if (process.env.ELECTRIC_API_TOKEN) return `api_token`;
  try {
    const authFile = join5(getConfigDir(), `auth.json`);
    if (existsSync2(authFile)) return `jwt`;
  } catch {
  }
  return `none`;
}
function collectFlags(rootOpts, cmdOpts) {
  const flags = [];
  if (rootOpts.json) flags.push(`--json`);
  if (rootOpts.quiet) flags.push(`--quiet`);
  if (rootOpts.verbose) flags.push(`--verbose`);
  if (rootOpts.token) flags.push(`--token`);
  if (rootOpts.input === false) flags.push(`--no-input`);
  if (cmdOpts.wait) flags.push(`--wait`);
  if (cmdOpts.force) flags.push(`--force`);
  return flags;
}
program.parseAsync().then(() => flush());
//# sourceMappingURL=index.js.map