#!/usr/bin/env node

// src/connect-platform.ts
import { chmodSync, closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs";
import { parseArgs } from "util";

// src/members.ts
import { createHash, randomBytes } from "crypto";
import { join } from "path";
import { PERMISSIONS } from "@agentc7/sdk/types";
import { z } from "zod";
var DEFAULT_CONFIG_FILENAME = "ac7.json";
var NAME_REGEX = /^[a-zA-Z0-9._-]+$/;
var PRESET_KEY_REGEX = /^[a-zA-Z0-9._-]+$/;
var TOTP_SECRET_REGEX = /^(?:[A-Z2-7]+=*|enc-v1:[A-Za-z0-9_-]+:[A-Za-z0-9_-]+:[A-Za-z0-9_-]+)$/;
var PermissionLeafSchema = z.enum(PERMISSIONS);
var TeamNameSchema = z.string().min(1).max(128);
var TeamDirectiveSchema = z.string().min(1).max(512);
var TeamContextSchema = z.string().max(4096).default("");
var MemberNameSchema = z.string().min(1).max(128).regex(NAME_REGEX, "name must be alphanumeric with . _ - allowed");
var InstructionsSchema = z.string().max(8192).default("");
var RawPermissionsSchema = z.array(z.string().min(1).max(64)).max(32).default([]);
var TotpSecretSchema = z.string().min(16, "totpSecret must be at least 16 base32 characters").max(128).regex(TOTP_SECRET_REGEX, "totpSecret must be a base32-encoded string");
var PresetNameSchema = z.string().min(1).max(64).regex(PRESET_KEY_REGEX, "preset name must be alphanumeric with . _ - allowed");
var PresetLeavesSchema = z.array(PermissionLeafSchema).max(32);
var RoleSchema = z.object({
  title: z.string().min(1).max(64),
  description: z.string().max(512).default("")
});
var SelfSignedConfigSchema = z.object({
  lanIp: z.string().nullable().default(null),
  validityDays: z.number().int().positive().max(3650).default(365),
  regenerateIfExpiringWithin: z.number().int().nonnegative().max(365).default(30)
});
var CustomHttpsConfigSchema = z.object({
  certPath: z.string().nullable().default(null),
  keyPath: z.string().nullable().default(null)
});
var WebPushConfigSchema = z.object({
  vapidPublicKey: z.string().min(1),
  vapidPrivateKey: z.string().min(1),
  vapidSubject: z.string().min(1).default("mailto:admin@ac7.local")
});
var HttpsConfigSchema = z.object({
  mode: z.enum(["off", "self-signed", "custom"]).default("off"),
  bindHttp: z.number().int().min(1).max(65535).default(8717),
  bindHttps: z.number().int().min(1).max(65535).default(7443),
  redirectHttpToHttps: z.boolean().default(true),
  hsts: z.enum(["auto", "on", "off"]).default("auto"),
  selfSigned: SelfSignedConfigSchema.default({
    lanIp: null,
    validityDays: 365,
    regenerateIfExpiringWithin: 30
  }),
  custom: CustomHttpsConfigSchema.default({ certPath: null, keyPath: null })
});
var FilesConfigSchema = z.object({
  root: z.string().min(1).optional(),
  maxFileSize: z.number().int().positive().optional()
});
var JwtConfigSchema = z.object({
  issuer: z.string().url(),
  jwksUrl: z.string().url(),
  audience: z.string().min(1)
});
function defaultConfigPath(env = process.env, cwd = process.cwd()) {
  const explicit = env.AC7_CONFIG_PATH;
  if (explicit && explicit.length > 0) return explicit;
  return join(cwd, DEFAULT_CONFIG_FILENAME);
}
function platformOverlayPathFor(configPath) {
  const dotJson = /\.json$/i;
  if (dotJson.test(configPath)) {
    return configPath.replace(dotJson, ".platform.json");
  }
  return `${configPath}.platform.json`;
}

// src/connect-platform.ts
var USAGE = `ac7-connect-platform

usage:
  ac7-connect-platform --url <hosted-url> --member-name <name> --platform <url> [options]

options:
  --url <url>            Public URL of this ac7 server (required).
                         Must match what you'll connect to from the
                         platform's team rail \u2014 the registration pins
                         to this origin.
  --member-name <name>   Your member name on this server (required).
                         Must exist in config.json's members list.
  --platform <url>       Hosted control-plane origin (required).
  --config-path <path>   Path to the primary config. Defaults to
                         the same default as ac7-server (AC7_CONFIG_PATH
                         or ./ac7.json). The overlay is written as
                         a sibling with ".platform" before ".json".
  -h, --help             Print this message.
`;
function parseCliArgs(argv) {
  const { values } = parseArgs({
    args: argv,
    options: {
      url: { type: "string" },
      "member-name": { type: "string" },
      platform: { type: "string" },
      "config-path": { type: "string" },
      help: { type: "boolean", short: "h" }
    },
    allowPositionals: false
  });
  if (values.help === true) {
    process.stdout.write(USAGE);
    process.exit(0);
  }
  if (typeof values.url !== "string" || values.url.length === 0) {
    process.stderr.write(`ac7-connect-platform: --url is required

${USAGE}`);
    process.exit(2);
  }
  if (typeof values["member-name"] !== "string" || values["member-name"].length === 0) {
    process.stderr.write(`ac7-connect-platform: --member-name is required

${USAGE}`);
    process.exit(2);
  }
  if (typeof values.platform !== "string" || values.platform.length === 0) {
    process.stderr.write(`ac7-connect-platform: --platform is required

${USAGE}`);
    process.exit(2);
  }
  const platformOrigin = values.platform.replace(/\/+$/, "");
  const configPath = typeof values["config-path"] === "string" && values["config-path"].length > 0 ? values["config-path"] : defaultConfigPath();
  return {
    hostedAt: values.url.replace(/\/+$/, ""),
    memberName: values["member-name"],
    platformOrigin,
    configPath
  };
}
async function postJson(url, body) {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body)
  });
  const text = await res.text();
  let parsed = null;
  if (text.length > 0) {
    try {
      parsed = JSON.parse(text);
    } catch {
      parsed = null;
    }
  }
  return { status: res.status, body: parsed };
}
function sleepMs(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
async function start(args) {
  const res = await postJson(
    `${args.platformOrigin}/api/servers/register/start`,
    { hostedAt: args.hostedAt }
  );
  if (res.status === 409 && res.body?.error === "server already registered") {
    process.stderr.write(
      `
This server is already registered with ${args.platformOrigin}.
Ask the current team owner for an invite instead of re-registering.
`
    );
    process.exit(3);
  }
  if (res.status !== 201 || !res.body) {
    const msg = res.body?.detail ?? res.body?.error ?? `HTTP ${res.status}`;
    throw new Error(`failed to start registration: ${msg}`);
  }
  return res.body;
}
async function poll(args, deviceCode, intervalSec, expiresAt) {
  const intervalMs = Math.max(1e3, intervalSec * 1e3);
  while (true) {
    if (Date.now() >= expiresAt) {
      return { status: "expired" };
    }
    const res = await postJson(
      `${args.platformOrigin}/api/servers/register/status`,
      { deviceCode }
    );
    if (res.status === 200 && res.body) {
      if (res.body.status === "authorized" || res.body.status === "expired") {
        return res.body;
      }
    }
    await sleepMs(intervalMs);
  }
}
function writeOverlayAtomic(overlayPath, jwt) {
  const body = `${JSON.stringify({ jwt }, null, 2)}
`;
  const tmp = `${overlayPath}.tmp-${process.pid}`;
  writeFileSync(tmp, body, { encoding: "utf8", mode: 384 });
  const fd = openSync(tmp, "r");
  try {
    fsyncSync(fd);
  } finally {
    closeSync(fd);
  }
  renameSync(tmp, overlayPath);
  chmodSync(overlayPath, 384);
}
async function main() {
  const args = parseCliArgs(process.argv.slice(2));
  process.stdout.write(
    `ac7-connect-platform: registering ${args.hostedAt} with ${args.platformOrigin}

`
  );
  let started;
  try {
    started = await start(args);
  } catch (err) {
    process.stderr.write(`
${err.message}
`);
    process.exit(1);
  }
  const pretty = started.userCode.length === 8 ? `${started.userCode.slice(0, 4)}-${started.userCode.slice(4)}` : started.userCode;
  process.stdout.write(
    `\u2192 Open this URL while signed in to the platform:
    ${started.verificationUrl}

  Then verify the code:
    ${pretty}

  (Code expires in 10 minutes. Waiting for your confirmation\u2026)

`
  );
  const outcome = await poll(args, started.deviceCode, started.pollIntervalSec, started.expiresAt);
  if (outcome.status !== "authorized") {
    process.stderr.write(
      `
Registration expired before you confirmed. Re-run ac7-connect-platform to try again.
`
    );
    process.exit(4);
  }
  const overlayPath = platformOverlayPathFor(args.configPath);
  writeOverlayAtomic(overlayPath, outcome.jwt);
  process.stdout.write(
    `
\u2713 Registered. Wrote ${overlayPath}.
  Team id:  ${outcome.teamId}
  Audience: ${outcome.jwt.audience}
  Issuer:   ${outcome.jwt.issuer}

  Restart ac7-server (or send SIGHUP if hot-reload is wired) to pick up the new config.
  Open the team in the platform:
    ${args.platformOrigin}/t/${outcome.teamSlug}
`
  );
}
main().catch((err) => {
  process.stderr.write(`
ac7-connect-platform: ${err.message}
`);
  process.exit(1);
});
//# sourceMappingURL=connect-platform.js.map