#!/usr/bin/env node

// src/index.ts
import { Command as Command12 } from "commander";

// src/commands/login.ts
import { Command } from "commander";

// src/auth/pkce.ts
import { randomBytes, createHash } from "crypto";
import { createInterface } from "readline";

// src/auth/constants.ts
import { homedir } from "os";
import { join } from "path";
var XAI_OAUTH_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
var XAI_OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
var XAI_OAUTH_ISSUER = "https://auth.x.ai";
var XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;
var XAI_OAUTH_CALLBACK_HOST = "127.0.0.1";
var XAI_OAUTH_CALLBACK_PORT = 56121;
var XAI_OAUTH_CALLBACK_PATH = "/callback";
var XAI_OAUTH_REDIRECT_URI = `http://${XAI_OAUTH_CALLBACK_HOST}:${XAI_OAUTH_CALLBACK_PORT}${XAI_OAUTH_CALLBACK_PATH}`;
var XAI_OAUTH_CORS_ORIGINS = ["auth.x.ai", "accounts.x.ai"];
var XAI_OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
var XAI_OAUTH_FETCH_TIMEOUT_MS = 30 * 1e3;
var XAI_DEVICE_CODE_POLL_INTERVAL_MS = 5 * 1e3;
var TOKEN_REFRESH_SKEW_MS = 2 * 60 * 1e3;
var XAI_API_BASE_URL = "https://api.x.ai/v1";
var DEFAULT_MODEL = "grok-4.3";
var PROXY_DEFAULT_PORT = 18645;
var PROXY_DEFAULT_HOST = "127.0.0.1";
var CHAT_DEFAULT_PORT = 18646;
var CONFIG_DIR = join(homedir(), ".progrok");
var AUTH_FILE = join(CONFIG_DIR, "auth.json");
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
var GITHUB_REPO = "lidge-jun/progrok";
var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;

// src/auth/discovery.ts
function requireTrustedEndpoint(url, label) {
  try {
    const parsed = new URL(url);
    if (parsed.protocol !== "https:") {
      throw new Error("not HTTPS");
    }
    if (parsed.hostname !== "x.ai" && !parsed.hostname.endsWith(".x.ai")) {
      throw new Error("not *.x.ai");
    }
    return url;
  } catch {
    throw new Error(`OIDC discovery returned untrusted ${label}: ${url}`);
  }
}
async function fetchOIDCDiscovery() {
  const res = await fetch(XAI_OAUTH_DISCOVERY_URL, {
    signal: AbortSignal.timeout(XAI_OAUTH_FETCH_TIMEOUT_MS)
  });
  if (!res.ok) {
    throw new Error(`OIDC discovery failed: HTTP ${res.status}`);
  }
  const data = await res.json();
  const authorizationEndpoint = requireTrustedEndpoint(
    data.authorization_endpoint,
    "authorization_endpoint"
  );
  const tokenEndpoint = requireTrustedEndpoint(
    data.token_endpoint,
    "token_endpoint"
  );
  const deviceAuthorizationEndpoint = data.device_authorization_endpoint ? requireTrustedEndpoint(
    data.device_authorization_endpoint,
    "device_authorization_endpoint"
  ) : void 0;
  return { authorizationEndpoint, tokenEndpoint, deviceAuthorizationEndpoint };
}

// src/auth/callback-server.ts
import { createServer } from "http";
function startCallbackServer(expectedState) {
  return new Promise((resolve3, reject) => {
    let server;
    const timeout = setTimeout(() => {
      server?.close();
      reject(new Error("OAuth callback timed out (5 minutes)"));
    }, XAI_OAUTH_TIMEOUT_MS);
    server = createServer((req, res) => {
      const url = new URL(
        req.url || "/",
        `http://${XAI_OAUTH_CALLBACK_HOST}`
      );
      if (req.method === "OPTIONS") {
        const origin = req.headers.origin || "";
        try {
          const originHost = new URL(origin).hostname;
          if (XAI_OAUTH_CORS_ORIGINS.includes(originHost)) {
            res.writeHead(204, {
              "Access-Control-Allow-Origin": origin,
              "Access-Control-Allow-Methods": "GET",
              "Access-Control-Allow-Headers": "Content-Type"
            });
          } else {
            res.writeHead(204);
          }
        } catch {
          res.writeHead(204);
        }
        res.end();
        return;
      }
      if (url.pathname !== XAI_OAUTH_CALLBACK_PATH) {
        res.writeHead(404);
        res.end("Not found");
        return;
      }
      const code = url.searchParams.get("code");
      const state = url.searchParams.get("state");
      const error = url.searchParams.get("error");
      if (error) {
        const desc = url.searchParams.get("error_description") || error;
        res.writeHead(200, { "Content-Type": "text/html" });
        res.end(
          `<html><body><h2>Login failed</h2><p>${desc}</p><p>You can close this tab.</p></body></html>`
        );
        clearTimeout(timeout);
        server?.close();
        reject(new Error(`OAuth error: ${desc}`));
        return;
      }
      if (!code || state !== expectedState) {
        res.writeHead(400, { "Content-Type": "text/html" });
        res.end("<html><body><h2>Invalid callback</h2></body></html>");
        return;
      }
      res.writeHead(200, { "Content-Type": "text/html" });
      res.end(
        `<html><body style="font-family:system-ui;text-align:center;padding:60px"><h2>Logged in to progrok</h2><p>You can close this tab and return to the terminal.</p></body></html>`
      );
      clearTimeout(timeout);
      server?.close();
      resolve3({ code, state });
    });
    server.listen(XAI_OAUTH_CALLBACK_PORT, XAI_OAUTH_CALLBACK_HOST, () => {
    });
    server.on("error", (err) => {
      clearTimeout(timeout);
      reject(new Error(`Callback server failed: ${err.message}`));
    });
  });
}

// src/auth/token-store.ts
import {
  readFileSync,
  writeFileSync,
  mkdirSync,
  existsSync,
  unlinkSync
} from "fs";

// src/utils/logger.ts
var RESET = "\x1B[0m";
var GREEN = "\x1B[32m";
var RED = "\x1B[31m";
var DIM = "\x1B[2m";
var BOLD = "\x1B[1m";
var log = {
  info: (msg) => {
    console.log(msg);
  },
  success: (msg) => {
    console.log(`${GREEN}${msg}${RESET}`);
  },
  error: (msg) => {
    console.error(`${RED}${msg}${RESET}`);
  },
  dim: (msg) => {
    console.log(`${DIM}${msg}${RESET}`);
  },
  bold: (msg) => {
    console.log(`${BOLD}${msg}${RESET}`);
  }
};

// src/auth/token-store.ts
function ensureConfigDir() {
  if (!existsSync(CONFIG_DIR)) {
    mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
  }
}
async function saveTokens(input) {
  ensureConfigDir();
  const data = {
    accessToken: input.accessToken,
    refreshToken: input.refreshToken,
    expiresAt: input.expiresIn ? Date.now() + input.expiresIn * 1e3 : void 0,
    tokenEndpoint: input.tokenEndpoint,
    idToken: input.idToken
  };
  if (input.idToken) {
    try {
      const parts = input.idToken.split(".");
      if (parts[1]) {
        const payload = JSON.parse(
          Buffer.from(parts[1], "base64url").toString()
        );
        if (typeof payload.email === "string") {
          data.email = payload.email;
        }
      }
    } catch {
    }
  }
  writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
}
function loadTokens() {
  try {
    const raw = readFileSync(AUTH_FILE, "utf8");
    return JSON.parse(raw);
  } catch {
    return null;
  }
}
function deleteTokens() {
  try {
    unlinkSync(AUTH_FILE);
  } catch {
  }
}
async function getValidBearer() {
  const tokens = loadTokens();
  if (!tokens?.accessToken) {
    throw new Error("Not logged in. Run `progrok login` first.");
  }
  if (tokens.expiresAt && Date.now() + TOKEN_REFRESH_SKEW_MS >= tokens.expiresAt) {
    if (!tokens.refreshToken || !tokens.tokenEndpoint) {
      throw new Error(
        "Token expired and no refresh token available. Run `progrok login` again."
      );
    }
    log.dim("Refreshing access token...");
    const res = await fetch(tokens.tokenEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "refresh_token",
        client_id: XAI_OAUTH_CLIENT_ID,
        refresh_token: tokens.refreshToken
      }),
      signal: AbortSignal.timeout(XAI_OAUTH_FETCH_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error("Token refresh failed. Run `progrok login` again.");
    }
    const refreshed = await res.json();
    if (typeof refreshed.access_token !== "string" || !refreshed.access_token) {
      throw new Error(
        "Token refresh returned invalid access_token. Run `progrok login` again."
      );
    }
    await saveTokens({
      accessToken: refreshed.access_token,
      refreshToken: (typeof refreshed.refresh_token === "string" ? refreshed.refresh_token : void 0) || tokens.refreshToken,
      expiresIn: typeof refreshed.expires_in === "number" ? refreshed.expires_in : void 0,
      tokenEndpoint: tokens.tokenEndpoint
    });
    return refreshed.access_token;
  }
  return tokens.accessToken;
}

// src/utils/open-url.ts
import open from "open";
async function openUrl(url) {
  try {
    await open(url);
  } catch {
  }
}

// src/auth/pkce.ts
function generatePKCE() {
  const verifier = randomBytes(32).toString("hex");
  const challenge = createHash("sha256").update(verifier).digest("base64url");
  return { verifier, challenge };
}
function extractCodeFromInput(input) {
  const trimmed = input.trim();
  try {
    const url = new URL(trimmed);
    const code = url.searchParams.get("code");
    if (code) return code;
  } catch {
  }
  if (trimmed.startsWith("?")) {
    const params = new URLSearchParams(trimmed.slice(1));
    const code = params.get("code");
    if (code) return code;
  }
  if (trimmed.length > 10 && !trimmed.includes(" ")) return trimmed;
  return null;
}
async function promptForCode() {
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  return new Promise((resolve3, reject) => {
    rl.question("\n  Paste the callback URL or authorization code: ", (answer) => {
      rl.close();
      const code = extractCodeFromInput(answer);
      if (!code) {
        reject(new Error("Could not extract authorization code from input."));
      } else {
        resolve3(code);
      }
    });
  });
}
async function loginWithPKCE(options = {}) {
  const discovery = await fetchOIDCDiscovery();
  const pkce = generatePKCE();
  const state = randomBytes(16).toString("hex");
  const authorizeUrl = new URL(discovery.authorizationEndpoint);
  authorizeUrl.searchParams.set("client_id", XAI_OAUTH_CLIENT_ID);
  authorizeUrl.searchParams.set("redirect_uri", XAI_OAUTH_REDIRECT_URI);
  authorizeUrl.searchParams.set("response_type", "code");
  authorizeUrl.searchParams.set("scope", XAI_OAUTH_SCOPE);
  authorizeUrl.searchParams.set("state", state);
  authorizeUrl.searchParams.set("code_challenge", pkce.challenge);
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
  let code;
  if (options.manualPaste) {
    log.info("Opening browser for xAI login (manual-paste mode)...");
    log.dim(
      `Visit this URL to authorize:
${authorizeUrl.toString()}`
    );
    log.dim(
      `
After approving, paste the callback URL or the authorization code shown on the page.`
    );
    await openUrl(authorizeUrl.toString());
    code = await promptForCode();
  } else {
    log.info("Opening browser for xAI login...");
    log.dim(
      `If the browser doesn't open, visit:
${authorizeUrl.toString()}`
    );
    const callbackPromise = startCallbackServer(state);
    await openUrl(authorizeUrl.toString());
    const result = await callbackPromise;
    code = result.code;
  }
  log.info("Exchanging authorization code...");
  const tokenRes = await fetch(discovery.tokenEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: XAI_OAUTH_CLIENT_ID,
      code,
      redirect_uri: XAI_OAUTH_REDIRECT_URI,
      code_verifier: pkce.verifier
    }),
    signal: AbortSignal.timeout(XAI_OAUTH_FETCH_TIMEOUT_MS)
  });
  if (!tokenRes.ok) {
    const err = await tokenRes.text();
    throw new Error(`Token exchange failed: ${err}`);
  }
  const tokens = await tokenRes.json();
  await saveTokens({
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    expiresIn: tokens.expires_in,
    idToken: tokens.id_token,
    tokenEndpoint: discovery.tokenEndpoint
  });
  log.success("Logged in to xAI successfully!");
}

// src/auth/device-code.ts
async function loginWithDeviceCode() {
  const discovery = await fetchOIDCDiscovery();
  if (!discovery.deviceAuthorizationEndpoint) {
    throw new Error(
      "xAI does not support device code flow via OIDC discovery"
    );
  }
  log.info("Requesting device code...");
  const dcRes = await fetch(discovery.deviceAuthorizationEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      client_id: XAI_OAUTH_CLIENT_ID,
      scope: XAI_OAUTH_SCOPE
    }),
    signal: AbortSignal.timeout(XAI_OAUTH_FETCH_TIMEOUT_MS)
  });
  if (!dcRes.ok) {
    throw new Error(`Device code request failed: ${await dcRes.text()}`);
  }
  const dc = await dcRes.json();
  const url = dc.verification_uri_complete || dc.verification_uri;
  log.info(`
Open this URL in your browser:
  ${url}
`);
  log.info(`Enter code: ${dc.user_code}
`);
  const intervalMs = Math.max(
    (dc.interval || 5) * 1e3,
    XAI_DEVICE_CODE_POLL_INTERVAL_MS
  );
  const deadline = Date.now() + dc.expires_in * 1e3;
  while (Date.now() < deadline) {
    await new Promise((r) => setTimeout(r, intervalMs));
    const pollRes = await fetch(discovery.tokenEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "urn:ietf:params:oauth:grant-type:device_code",
        client_id: XAI_OAUTH_CLIENT_ID,
        device_code: dc.device_code
      }),
      signal: AbortSignal.timeout(XAI_OAUTH_FETCH_TIMEOUT_MS)
    });
    if (pollRes.ok) {
      const tokens = await pollRes.json();
      await saveTokens({
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token,
        expiresIn: tokens.expires_in,
        idToken: tokens.id_token,
        tokenEndpoint: discovery.tokenEndpoint
      });
      log.success("Logged in to xAI successfully!");
      return;
    }
    const err = await pollRes.json();
    if (err.error === "authorization_pending") continue;
    if (err.error === "slow_down") {
      await new Promise((r) => setTimeout(r, 5e3));
      continue;
    }
    throw new Error(
      `Device code poll failed: ${err.error_description || err.error}`
    );
  }
  throw new Error("Device code expired. Please try again.");
}

// src/commands/login.ts
function loginCommand() {
  const cmd = new Command("login").description(
    `Log in to xAI via OAuth (SuperGrok subscription required).
  Default: device-code flow \u2014 displays URL + code for manual entry.
  Browser: --browser \u2014 opens browser, callback on 127.0.0.1:56121.
  Manual:  --manual-paste \u2014 PKCE flow but paste the callback code manually.
  Tokens saved to ~/.progrok/auth.json, auto-refreshed before expiry.`
  ).option(
    "--device-code",
    "Use device code flow (default)"
  ).option(
    "--browser",
    "Use PKCE browser flow with loopback callback"
  ).option(
    "--manual-paste",
    "Use PKCE flow but paste the authorization code manually"
  ).action(async (opts) => {
    try {
      if (opts.browser) {
        await loginWithPKCE();
      } else if (opts.manualPaste) {
        await loginWithPKCE({ manualPaste: true });
      } else {
        await loginWithDeviceCode();
      }
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
  return cmd;
}

// src/commands/logout.ts
import { Command as Command2 } from "commander";
function logoutCommand() {
  return new Command2("logout").description("Remove stored xAI credentials").action(() => {
    deleteTokens();
    log.success("Logged out. Credentials removed.");
  });
}

// src/commands/proxy.ts
import { Command as Command3 } from "commander";

// src/proxy/server.ts
import express from "express";
var HOP_BY_HOP = /* @__PURE__ */ new Set([
  "host",
  "content-length",
  "connection",
  "keep-alive",
  "proxy-authenticate",
  "proxy-authorization",
  "te",
  "trailers",
  "transfer-encoding",
  "upgrade",
  "authorization"
]);
function filterHeaders(headers) {
  const out = {};
  for (const [key, value] of Object.entries(headers)) {
    if (!HOP_BY_HOP.has(key.toLowerCase()) && value) {
      out[key] = Array.isArray(value) ? value[0] : value;
    }
  }
  return out;
}
function createProxyApp() {
  const app = express();
  app.get("/health", (_req, res) => {
    res.json({ status: "ok", upstream: "xAI Grok", proxy: "progrok" });
  });
  app.all("/v1/*", (req, res) => {
    void handleProxy(req, res);
  });
  return app;
}
async function handleProxy(req, res) {
  const relPath = req.path.replace(/^\/v1/, "");
  let bearer;
  try {
    bearer = await getValidBearer();
  } catch (err) {
    res.status(401).json({
      error: { message: err.message, type: "auth_error" }
    });
    return;
  }
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  }
  const body = Buffer.concat(chunks);
  const qs = req.url.includes("?") ? "?" + req.url.split("?")[1] : "";
  const upstreamUrl = `${XAI_API_BASE_URL}${relPath}${qs}`;
  const fwdHeaders = filterHeaders(
    req.headers
  );
  fwdHeaders["Authorization"] = `Bearer ${bearer}`;
  try {
    const upstream = await fetch(upstreamUrl, {
      method: req.method,
      headers: fwdHeaders,
      body: body.length > 0 ? body : void 0
    });
    res.status(upstream.status);
    for (const [key, value] of upstream.headers) {
      const lower = key.toLowerCase();
      if (!HOP_BY_HOP.has(lower) && lower !== "content-encoding" && lower !== "content-length") {
        res.setHeader(key, value);
      }
    }
    if (upstream.body) {
      const reader = upstream.body.getReader();
      try {
        for (; ; ) {
          const { done, value } = await reader.read();
          if (done) break;
          res.write(value);
        }
      } catch {
      }
    }
    res.end();
  } catch (err) {
    if (!res.headersSent) {
      res.status(502).json({
        error: {
          message: `Upstream error: ${err.message}`,
          type: "upstream_error"
        }
      });
    }
  }
}
async function startProxy(port = PROXY_DEFAULT_PORT, host = PROXY_DEFAULT_HOST) {
  const app = createProxyApp();
  return new Promise((resolve3, reject) => {
    const server = app.listen(port, host, () => {
      log.success("progrok proxy running");
      log.info(`  Listening:    http://${host}:${port}/v1`);
      log.info(`  Forwarding:   ${XAI_API_BASE_URL}`);
      log.dim(
        "  Client auth:  any bearer token (proxy injects your OAuth credential)"
      );
      log.info("\nPress Ctrl+C to stop.\n");
      resolve3();
    });
    server.on("error", (err) => {
      reject(new Error(`Proxy failed to start: ${err.message}`));
    });
    process.on("SIGINT", () => {
      log.info("\nprogrok proxy stopped.");
      server.close();
      process.exit(0);
    });
  });
}

// src/commands/proxy.ts
function proxyCommand() {
  return new Command3("proxy").description(
    `Start OpenAI-compatible proxy server for Grok.
  Forwards ALL /v1/* requests to api.x.ai with your OAuth token injected.
  Any client (curl, OpenAI SDK, LangChain, etc.) can connect \u2014 no API key needed.
  Supports streaming (SSE), multipart (stt), and binary responses (tts).`
  ).option("-p, --port <port>", "Port number", String(PROXY_DEFAULT_PORT)).option("--host <host>", "Host to bind", PROXY_DEFAULT_HOST).action(async (opts) => {
    const tokens = loadTokens();
    if (!tokens?.accessToken) {
      log.error("Not logged in. Run `progrok login` first.");
      process.exit(1);
    }
    try {
      await startProxy(parseInt(opts.port, 10), opts.host);
      await new Promise(() => {
      });
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
}

// src/commands/chat.ts
import { Command as Command4 } from "commander";

// src/chat/server.ts
import express2 from "express";
import { join as join2, dirname } from "path";
import { fileURLToPath } from "url";
var __dirname = dirname(fileURLToPath(import.meta.url));
async function startChat(port = CHAT_DEFAULT_PORT, host = PROXY_DEFAULT_HOST) {
  const app = createProxyApp();
  const publicDir = join2(__dirname, "public");
  app.use(express2.static(publicDir));
  app.get("/", (_req, res) => {
    res.sendFile(join2(publicDir, "index.html"));
  });
  return new Promise((resolve3, reject) => {
    const server = app.listen(port, host, () => {
      const url = `http://${host}:${port}`;
      log.success(`progrok chat running at ${url}`);
      log.dim(`Proxy also available at ${url}/v1`);
      log.info("Press Ctrl+C to stop.\n");
      void openUrl(url);
      resolve3();
    });
    server.on("error", (err) => {
      reject(new Error(`Chat server failed: ${err.message}`));
    });
    process.on("SIGINT", () => {
      log.info("\nprogrok chat stopped.");
      server.close();
      process.exit(0);
    });
  });
}

// src/commands/chat.ts
function chatCommand() {
  return new Command4("chat").description(
    `Open Grok chat in your browser.
  Starts a web UI + proxy server on the same port.
  Features: sessions, markdown, code highlighting, tool results, model switching.
  Proxy is also available at the same port under /v1/*.`
  ).option("-p, --port <port>", "Port number", String(CHAT_DEFAULT_PORT)).option("--host <host>", "Host to bind", PROXY_DEFAULT_HOST).action(async (opts) => {
    const tokens = loadTokens();
    if (!tokens?.accessToken) {
      log.error("Not logged in. Run `progrok login` first.");
      process.exit(1);
    }
    try {
      await startChat(parseInt(opts.port, 10), opts.host);
      await new Promise(() => {
      });
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
}

// src/commands/models.ts
import { Command as Command5 } from "commander";
function formatPrice(ticks) {
  if (!ticks) return "-";
  return `$${(ticks / 1e4).toFixed(2)}`;
}
function modelsCommand() {
  return new Command5("models").description("List available Grok models").option("--detail", "Show pricing and aliases from /v1/language-models").action(async (opts) => {
    try {
      const bearer = await getValidBearer();
      if (opts.detail) {
        const res2 = await fetch(`${XAI_API_BASE_URL}/language-models`, {
          headers: { Authorization: `Bearer ${bearer}` }
        });
        if (!res2.ok) throw new Error(`HTTP ${res2.status}`);
        const data2 = await res2.json();
        log.info("Grok Models (detailed)\n");
        for (const m of data2.models) {
          const input = (m.input_modalities || []).join(", ");
          const inPrice = formatPrice(m.prompt_text_token_price);
          const outPrice = formatPrice(m.completion_text_token_price);
          console.log(`  ${m.id}`);
          console.log(`    Input: ${input}  |  Price: ${inPrice}/1M in, ${outPrice}/1M out`);
          if (m.aliases && m.aliases.length > 0) {
            console.log(`    Aliases: ${m.aliases.slice(0, 5).join(", ")}${m.aliases.length > 5 ? ` (+${m.aliases.length - 5} more)` : ""}`);
          }
          console.log();
        }
        return;
      }
      const res = await fetch(`${XAI_API_BASE_URL}/models`, {
        headers: { Authorization: `Bearer ${bearer}` }
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      log.info("Available Grok models:\n");
      for (const m of data.data) {
        const tag = m.id.includes("reasoning") ? " [reasoning]" : m.id.includes("non-reasoning") ? " [fast]" : m.id.includes("build") || m.id.includes("code") ? " [code]" : m.id.includes("imagine-video") ? " [video]" : m.id.includes("imagine") ? " [image]" : "";
        console.log(`  ${m.id}${tag}`);
      }
      log.dim("\n  Use --detail for pricing and aliases");
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
}

// src/commands/status.ts
import { Command as Command6 } from "commander";
function statusCommand() {
  return new Command6("status").description("Show authentication status").action(() => {
    const tokens = loadTokens();
    if (!tokens?.accessToken) {
      log.info("Status: Not logged in");
      log.dim("Run `progrok login` to authenticate.");
      return;
    }
    const expired = tokens.expiresAt !== void 0 && Date.now() >= tokens.expiresAt;
    const hasRefresh = !!tokens.refreshToken;
    log.info("Status: Logged in");
    if (tokens.email) log.info(`  Account: ${tokens.email}`);
    if (tokens.expiresAt) {
      const remaining = Math.max(
        0,
        Math.floor((tokens.expiresAt - Date.now()) / 6e4)
      );
      log.info(
        `  Token:   ${expired ? "expired" : `valid (${remaining} min remaining)`}`
      );
    }
    log.info(`  Refresh: ${hasRefresh ? "available" : "none"}`);
  });
}

// src/commands/skill.ts
import { Command as Command7 } from "commander";
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
import { fileURLToPath as fileURLToPath2 } from "url";
import { dirname as dirname2, join as join3, relative } from "path";
var ROOT = join3(dirname2(fileURLToPath2(import.meta.url)), "..");
var SKILL_PATH = join3(ROOT, "skills", "progrok", "SKILL.md");
var PACKAGE_PATH = join3(ROOT, "package.json");
function readPackageVersion() {
  try {
    const pkg = JSON.parse(readFileSync2(PACKAGE_PATH, "utf-8"));
    return pkg.version || "?";
  } catch {
    return "?";
  }
}
function readSkill() {
  if (!existsSync2(SKILL_PATH)) {
    console.error(`packaged skill not found: ${SKILL_PATH}`);
    process.exit(5);
  }
  return readFileSync2(SKILL_PATH, "utf-8");
}
function skillCommand() {
  return new Command7("skill").description(
    `Print the packaged progrok Markdown skill for agents.
  Designed to be piped into agent context or read programmatically.`
  ).argument("[subcommand]", '"path" to print the resolved skill file path').option("--json", "Output as JSON wrapper around the Markdown skill").action((subcommand2, opts) => {
    if (subcommand2 === "path") {
      console.log(SKILL_PATH);
      return;
    }
    const content = readSkill();
    if (opts.json) {
      console.log(
        JSON.stringify(
          {
            name: "progrok",
            format: "markdown-skill",
            formatVersion: "1",
            packageVersion: readPackageVersion(),
            path: relative(ROOT, SKILL_PATH),
            source: "package",
            content
          },
          null,
          2
        )
      );
      return;
    }
    console.log(content.replace(/\n$/, ""));
  });
}

// src/commands/capabilities.ts
import { Command as Command8 } from "commander";
import { readFileSync as readFileSync3 } from "fs";
import { fileURLToPath as fileURLToPath3 } from "url";
import { dirname as dirname3, join as join4 } from "path";
var ROOT2 = join4(dirname3(fileURLToPath3(import.meta.url)), "..");
function readPackageVersion2() {
  try {
    const pkg = JSON.parse(
      readFileSync3(join4(ROOT2, "package.json"), "utf-8")
    );
    return pkg.version || "?";
  } catch {
    return "?";
  }
}
var ENDPOINTS = [
  // Chat & Responses
  { category: "chat", method: "POST", path: "/v1/chat/completions", type: "streaming", description: "OpenAI-compatible chat + image understanding" },
  { category: "chat", method: "POST", path: "/v1/responses", type: "streaming", description: "Responses API \u2014 tools, reasoning, citations, stateful" },
  { category: "chat", method: "POST", path: "/v1/responses/compact", type: "sync", description: "Context compaction \u2014 shrink history for reuse" },
  { category: "chat", method: "GET", path: "/v1/responses/{id}", type: "sync", description: "Retrieve a stored response (kept 30 days)" },
  { category: "chat", method: "DELETE", path: "/v1/responses/{id}", type: "sync", description: "Delete a stored response" },
  { category: "chat", method: "GET", path: "/v1/chat/deferred-completion/{id}", type: "poll", description: "Fetch a deferred completion (202 pending / 200 done)" },
  // Images (Imagine API)
  { category: "images", method: "POST", path: "/v1/images/generations", type: "sync", description: "Text-to-image \u2014 aspect_ratio, resolution 1k/2k, n, url|b64_json" },
  { category: "images", method: "POST", path: "/v1/images/edits", type: "sync", description: "Edit / multi-image compose \u2014 image{} or images[] + prompt" },
  // Videos (Imagine API, async)
  { category: "videos", method: "POST", path: "/v1/videos/generations", type: "async", description: "T2V / I2V / R2V \u2014 duration 1-15s, resolution 480p/720p/1080p" },
  { category: "videos", method: "POST", path: "/v1/videos/edits", type: "async", description: "Edit a source video by prompt" },
  { category: "videos", method: "POST", path: "/v1/videos/extensions", type: "async", description: "Extend a video (1-10s continuation)" },
  { category: "videos", method: "GET", path: "/v1/videos/{id}", type: "poll", description: "Poll generation: pending(progress) \u2192 done(video.url)" },
  // Voice — HTTP
  { category: "voice", method: "POST", path: "/v1/tts", type: "binary", description: "Text-to-speech \u2014 voice_id, output_format, speed, speech tags" },
  { category: "voice", method: "GET", path: "/v1/tts/voices", type: "sync", description: "List built-in voices (ara, eve, leo, rex, sal)" },
  { category: "voice", method: "GET", path: "/v1/tts/voices/{voice_id}", type: "sync", description: "Get one built-in voice" },
  { category: "voice", method: "POST", path: "/v1/stt", type: "multipart", description: "Speech-to-text \u2014 diarize, multichannel, keyterm, word timestamps" },
  { category: "voice", method: "POST", path: "/v1/realtime/client_secrets", type: "sync", description: "Mint ephemeral token for browser Realtime/Voice Agent" },
  { category: "voice", method: "POST", path: "/v1/custom-voices", type: "multipart", description: "Clone a custom voice from reference audio (\u2264120s)" },
  { category: "voice", method: "GET", path: "/v1/custom-voices", type: "sync", description: "List custom voices (paginated)" },
  { category: "voice", method: "GET", path: "/v1/custom-voices/{voice_id}", type: "sync", description: "Get one custom voice" },
  { category: "voice", method: "PATCH", path: "/v1/custom-voices/{voice_id}", type: "sync", description: "Update custom voice metadata" },
  { category: "voice", method: "DELETE", path: "/v1/custom-voices/{voice_id}", type: "sync", description: "Delete a custom voice" },
  { category: "voice", method: "GET", path: "/v1/custom-voices/{voice_id}/audio", type: "binary", description: "Download a custom voice's reference audio" },
  // Models
  { category: "models", method: "GET", path: "/v1/models", type: "sync", description: "List models (id + pricing)" },
  { category: "models", method: "GET", path: "/v1/models/{model_id}", type: "sync", description: "Get one model" },
  { category: "models", method: "GET", path: "/v1/language-models", type: "sync", description: "Chat models with modalities + aliases + pricing" },
  { category: "models", method: "GET", path: "/v1/language-models/{model_id}", type: "sync", description: "Get one language model" },
  { category: "models", method: "GET", path: "/v1/image-generation-models", type: "sync", description: "Image models with per-image pricing" },
  { category: "models", method: "GET", path: "/v1/image-generation-models/{model_id}", type: "sync", description: "Get one image model" },
  { category: "models", method: "GET", path: "/v1/video-generation-models", type: "sync", description: "Video models with modalities" },
  { category: "models", method: "GET", path: "/v1/video-generation-models/{model_id}", type: "sync", description: "Get one video model" },
  // Batches
  { category: "batches", method: "POST", path: "/v1/batches", type: "sync", description: "Create a batch" },
  { category: "batches", method: "GET", path: "/v1/batches", type: "sync", description: "List batches" },
  { category: "batches", method: "GET", path: "/v1/batches/{id}", type: "sync", description: "Get batch state" },
  { category: "batches", method: "GET", path: "/v1/batches/{id}/requests", type: "sync", description: "List requests in a batch" },
  { category: "batches", method: "POST", path: "/v1/batches/{id}/requests", type: "sync", description: "Add requests to a batch" },
  { category: "batches", method: "GET", path: "/v1/batches/{id}/results", type: "sync", description: "List batch results" },
  { category: "batches", method: "POST", path: "/v1/batches/{id}:cancel", type: "sync", description: "Cancel all requests in a batch" },
  // Files
  { category: "files", method: "POST", path: "/v1/files", type: "multipart", description: "Upload a file (\u226450MB) \u2014 referenced by file_id" },
  { category: "files", method: "GET", path: "/v1/files", type: "sync", description: "List files (AIP-160 filter, paginated)" },
  { category: "files", method: "GET", path: "/v1/files/{file_id}", type: "sync", description: "Get file metadata" },
  { category: "files", method: "DELETE", path: "/v1/files/{file_id}", type: "sync", description: "Delete a file" },
  // Collections (search only via api.x.ai; management is on management-api.x.ai)
  { category: "collections", method: "POST", path: "/v1/documents/search", type: "sync", description: "Semantic search over collections (RAG)" },
  // Other
  { category: "other", method: "GET", path: "/v1/api-key", type: "sync", description: "Inspect the active API key / ACLs" },
  { category: "other", method: "POST", path: "/v1/tokenize-text", type: "sync", description: "Tokenize text for a model" },
  { category: "other", method: "POST", path: "/v1/embeddings", type: "sync", description: "Text embeddings" },
  { category: "other", method: "*", path: "/v1/*", type: "passthrough", description: "Any other path forwarded to api.x.ai unchanged" }
];
var WEBSOCKET_ENDPOINTS = [
  { url: "wss://api.x.ai/v1/realtime", description: "Voice Agent \u2014 realtime speech conversations + MCP tools" },
  { url: "wss://api.x.ai/v1/tts", description: "Streaming text-to-speech (incremental text \u2192 audio)" },
  { url: "wss://api.x.ai/v1/stt", description: "Streaming speech-to-text (binary audio \u2192 transcript)" }
];
function buildCapabilities() {
  return {
    ok: true,
    name: "progrok",
    version: readPackageVersion2(),
    source: "local",
    upstream: XAI_API_BASE_URL,
    commands: [
      "login",
      "logout",
      "proxy",
      "chat",
      "models",
      "status",
      "skill",
      "capabilities"
    ],
    proxy: {
      host: PROXY_DEFAULT_HOST,
      port: PROXY_DEFAULT_PORT,
      baseUrl: `http://${PROXY_DEFAULT_HOST}:${PROXY_DEFAULT_PORT}/v1`,
      forwarding: "all HTTP /v1/* paths \u2014 no whitelist"
    },
    chat: {
      port: CHAT_DEFAULT_PORT,
      url: `http://${PROXY_DEFAULT_HOST}:${CHAT_DEFAULT_PORT}`
    },
    defaults: {
      model: DEFAULT_MODEL
    },
    endpoints: ENDPOINTS,
    websocketEndpoints: WEBSOCKET_ENDPOINTS,
    models: [
      {
        id: "grok-4.3",
        type: "reasoning",
        use: "Flagship \u2014 chat, agentic tool calling, search, vision",
        input: ["text", "image"],
        output: ["text"],
        context: "1M tokens",
        pricing: { inputPer1M: 1.25, cachedInputPer1M: 0.2, outputPer1M: 2.5, longContext: { thresholdTokens: 2e5, inputPer1M: 2.5, cachedInputPer1M: 0.4, outputPer1M: 5 }, searchPer1KSources: 25, unit: "USD" },
        reasoning: { configurable: true, nonReasoningMode: true, note: "Supports non-reasoning mode for instant replies" },
        structuredOutput: true,
        functionCalling: true,
        tools: ["web_search", "x_search", "code_interpreter", "collections_search", "mcp", "function"],
        aliases: ["grok-4.3-latest", "grok-latest", "grok-4", "grok-4-latest", "grok-4-fast-reasoning", "grok-3", "grok-3-latest", "grok-3-mini"]
      },
      {
        id: "grok-4.20-0309-reasoning",
        type: "deep-reasoning",
        use: "Deep analysis, complex coding (legacy 4.20 line)",
        input: ["text", "image"],
        output: ["text"],
        context: "200K+ (long-context pricing above 200K tokens)",
        pricing: { inputPer1M: 1.25, cachedInputPer1M: 0.2, outputPer1M: 2.5, longContext: { thresholdTokens: 2e5, inputPer1M: 2.5, cachedInputPer1M: 0.4, outputPer1M: 5 }, searchPer1KSources: 25, unit: "USD" },
        reasoning: { effort: ["low", "high"], default: "low" },
        structuredOutput: true,
        functionCalling: true,
        tools: ["web_search", "x_search", "code_interpreter", "collections_search", "mcp", "function"],
        aliases: ["grok-4.20", "grok-4.20-reasoning", "grok-4.20-reasoning-latest", "grok-4.20-beta", "grok-4.20-beta-latest"]
      },
      {
        id: "grok-4.20-0309-non-reasoning",
        type: "fast",
        use: "Fast responses, no thinking overhead (legacy 4.20 line)",
        input: ["text", "image"],
        output: ["text"],
        context: "200K+ (long-context pricing above 200K tokens)",
        pricing: { inputPer1M: 1.25, cachedInputPer1M: 0.2, outputPer1M: 2.5, longContext: { thresholdTokens: 2e5, inputPer1M: 2.5, cachedInputPer1M: 0.4, outputPer1M: 5 }, searchPer1KSources: 25, unit: "USD" },
        reasoning: false,
        structuredOutput: true,
        functionCalling: true,
        tools: ["web_search", "x_search", "code_interpreter", "collections_search", "mcp", "function"],
        aliases: ["grok-4.20-non-reasoning", "grok-4.20-non-reasoning-latest", "grok-4.20-beta-non-reasoning", "grok-4.20-beta-latest-non-reasoning"]
      },
      {
        id: "grok-4.20-multi-agent-0309",
        type: "multi-agent",
        use: "Deep research \u2014 4 or 16 parallel agents (beta)",
        input: ["text", "image"],
        output: ["text"],
        context: "200K+ (long-context pricing above 200K tokens)",
        pricing: { inputPer1M: 1.25, cachedInputPer1M: 0.2, outputPer1M: 2.5, longContext: { thresholdTokens: 2e5, inputPer1M: 2.5, cachedInputPer1M: 0.4, outputPer1M: 5 }, searchPer1KSources: 25, unit: "USD" },
        reasoning: { effort: ["low", "medium", "high", "xhigh"], default: "low", note: "effort selects agent count: low/medium=4, high/xhigh=16" },
        structuredOutput: true,
        functionCalling: false,
        tools: ["web_search", "x_search", "code_interpreter", "collections_search", "mcp"],
        notes: "Responses API / xAI SDK only \u2014 not Chat Completions. No client-side function calling. No max_tokens.",
        aliases: ["grok-4.20-multi-agent", "grok-4.20-multi-agent-latest", "grok-4.20-multi-agent-beta-latest"]
      },
      {
        id: "grok-build-0.1",
        type: "code",
        use: "Fast agentic coding",
        input: ["text", "image"],
        output: ["text"],
        context: "256K tokens",
        pricing: { inputPer1M: 1, cachedInputPer1M: 0.2, outputPer1M: 2, longContext: { thresholdTokens: 2e5, inputPer1M: 2, cachedInputPer1M: 0.4, outputPer1M: 4 }, searchPer1KSources: 25, unit: "USD" },
        reasoning: { effort: ["low", "high"], default: "low" },
        structuredOutput: true,
        functionCalling: true,
        tools: ["web_search", "x_search", "code_interpreter", "function"],
        aliases: ["grok-code-fast-1", "grok-code-fast", "grok-code-fast-1-0825"]
      },
      {
        id: "grok-imagine-image",
        type: "image",
        use: "Fast image generation / editing",
        input: ["text", "image"],
        output: ["image"],
        maxPromptLength: 8e3,
        pricing: { perImage: 0.02, unit: "USD", note: "1k or 2k resolution" },
        aliases: ["grok-imagine-image-2026-03-02"]
      },
      {
        id: "grok-imagine-image-quality",
        type: "image-hq",
        use: "High-quality image generation / editing",
        input: ["text", "image"],
        output: ["image"],
        maxPromptLength: 8e3,
        pricing: { perImage: 0.04, unit: "USD", note: "1k or 2k resolution" },
        aliases: ["grok-imagine-image-quality-latest", "grok-imagine-image-pro"]
      },
      {
        id: "grok-imagine-video",
        type: "video",
        use: "Video generation / edit / extension (async)",
        input: ["text", "image"],
        output: ["video"],
        pricing: { perSecond: 0.05, unit: "USD", note: "480p or 720p" },
        aliases: []
      },
      {
        id: "grok-imagine-video-1.5-preview",
        type: "video",
        use: "Video v1.5 (preview) \u2014 improved I2V, T2V via white-canvas workaround",
        input: ["text", "image"],
        output: ["video"],
        pricing: { perSecond: 0.05, unit: "USD", note: "480p or 720p, T2V needs image injection" },
        aliases: []
      }
    ],
    voiceModels: [
      { id: "grok-voice-latest", use: "Voice Agent / realtime \u2014 recommended" },
      { id: "grok-voice-fast-1.0", use: "Lower-latency voice agent" },
      { id: "grok-voice-think-fast-1.0", use: "Reasoning-capable voice agent" }
    ],
    voicePricing: {
      agentPerHour: 3,
      ttsPer1MChars: 15,
      sttBatchPerHour: 0.1,
      sttStreamingPerHour: 0.2,
      unit: "USD"
    },
    tools: [
      { type: "web_search", description: "Web search + browse. Params: allowed_domains, excluded_domains, enable_image_understanding, enable_image_search" },
      { type: "x_search", description: "X (Twitter) search with citations" },
      { type: "code_interpreter", description: "Server-side Python execution (a.k.a. Code Execution)" },
      { type: "collections_search", description: "RAG over your uploaded collections" },
      { type: "mcp", description: "Remote MCP server. Params: server_url, server_label, allowed_tools, authorization, headers" },
      { type: "function", description: "Custom client-side function calling (max 128)" }
    ],
    searchParameters: {
      mode: ["off", "on", "auto"],
      sources: ["web", "x", "news", "rss"],
      params: ["from_date", "to_date", "max_search_results", "return_citations"]
    },
    limitations: [
      "WebSocket endpoints (wss /v1/realtime, /v1/tts, /v1/stt) are NOT proxied \u2014 connect directly with an ephemeral token from POST /v1/realtime/client_secrets.",
      "Collection management lives on management-api.x.ai (Management API key) and is not reachable through this proxy; only POST /v1/documents/search is.",
      "The multi-agent model requires the Responses API (not Chat Completions)."
    ],
    guidance: {
      auth: "Run `progrok login` once. progrok activates the local xAI OAuth session for clients and commands.",
      proxy: "`progrok proxy` forwards all HTTP /v1/* paths. Set OPENAI_BASE_URL=http://127.0.0.1:18645/v1 and OPENAI_API_KEY=anything.",
      streaming: "Use stream:true for responses/chat. The proxy passes SSE through verbatim.",
      video: "Video gen is async: POST /v1/videos/generations \u2192 poll GET /v1/videos/{id} until status=done.",
      models: "Use the bare model name or `<model>-latest` to auto-track the newest version; `<model>-<date>` pins a specific release."
    }
  };
}
function printText(cap) {
  console.log(`progrok capabilities (${cap.source})`);
  console.log(`version: ${cap.version}`);
  console.log(`upstream: ${cap.upstream}`);
  console.log("");
  console.log(`proxy: ${cap.proxy.baseUrl}`);
  console.log(`chat:  ${cap.chat.url}`);
  console.log(`model: ${cap.defaults.model}`);
  console.log("");
  console.log("endpoints (HTTP \u2014 proxied):");
  let lastCategory = "";
  for (const ep of cap.endpoints) {
    if (ep.category !== lastCategory) {
      console.log(`  [${ep.category}]`);
      lastCategory = ep.category;
    }
    const label = `    ${ep.method.padEnd(6)} ${ep.path.padEnd(38)}`;
    console.log(`${label} ${ep.description}`);
  }
  console.log("");
  console.log("websocket (NOT proxied \u2014 connect directly):");
  for (const ws of cap.websocketEndpoints) {
    console.log(`    ${ws.url.padEnd(30)} ${ws.description}`);
  }
  console.log("");
  console.log("models:");
  for (const m of cap.models) {
    console.log(`  ${m.id.padEnd(30)} ${m.use}`);
  }
  console.log("");
  console.log("tools:");
  for (const t of cap.tools) {
    console.log(`  ${t.type.padEnd(20)} ${t.description}`);
  }
  console.log("");
  console.log("limitations:");
  for (const l of cap.limitations) {
    console.log(`  - ${l}`);
  }
  console.log("");
  console.log(`commands: ${cap.commands.join(", ")}`);
}
function capabilitiesCommand() {
  return new Command8("capabilities").description("Print agent-friendly capability metadata.").option("--json", "Output as JSON").action((opts) => {
    const cap = buildCapabilities();
    if (opts.json) {
      console.log(JSON.stringify(cap, null, 2));
    } else {
      printText(cap);
    }
  });
}

// src/commands/search.ts
import { Command as Command9 } from "commander";
function buildSearchTools(opts) {
  const explicit = Boolean(opts.web || opts.x);
  const useWeb = explicit ? Boolean(opts.web) : true;
  const useX = explicit ? Boolean(opts.x) : true;
  const tools = [];
  if (useWeb) tools.push({ type: "web_search" });
  if (useX) tools.push({ type: "x_search" });
  return tools;
}
function extractSearchResult(data) {
  const citations = [];
  const queries = [];
  const seen = /* @__PURE__ */ new Set();
  let answer = "";
  for (const item of data.output ?? []) {
    if (item.type === "message") {
      for (const c of item.content ?? []) {
        if (c.type === "output_text" && c.text) {
          answer += c.text;
          for (const a of c.annotations ?? []) {
            if (a.type === "url_citation" && a.url && !seen.has(a.url)) {
              seen.add(a.url);
              citations.push({ url: a.url, title: a.title ?? "" });
            }
          }
        }
      }
    } else if ((item.type === "web_search_call" || item.type === "x_search_call") && item.action?.query) {
      queries.push(item.action.query);
    }
  }
  return { answer: answer.trim(), citations, queries, usage: data.usage };
}
function searchCommand() {
  return new Command9("search").description("Search the web and X with Grok \u2014 AI summary + citations (no API key)").argument("<query>", "what to search for").option("--web", "web search only (default: web + X)").option("--x", "X (Twitter) search only").option("--json", "output structured JSON").option("--model <id>", "model to use", DEFAULT_MODEL).option(
    "--reasoning <effort>",
    "reasoning effort: none|low|medium|high|xhigh (xhigh = multi-agent 16)"
  ).action(async (query, opts) => {
    try {
      const VALID_EFFORTS = /* @__PURE__ */ new Set(["none", "low", "medium", "high", "xhigh"]);
      if (opts.reasoning && !VALID_EFFORTS.has(opts.reasoning)) {
        throw new Error(
          `invalid --reasoning '${opts.reasoning}'. use: none|low|medium|high|xhigh`
        );
      }
      const tools = buildSearchTools(opts);
      const bearer = await getValidBearer();
      const res = await fetch(`${XAI_API_BASE_URL}/responses`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${bearer}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          model: opts.model ?? DEFAULT_MODEL,
          input: query,
          tools,
          stream: false,
          ...opts.reasoning ? { reasoning: { effort: opts.reasoning } } : {}
        })
      });
      if (!res.ok) {
        const body = await res.text();
        throw new Error(`HTTP ${res.status}: ${body.slice(0, 300)}`);
      }
      const result = extractSearchResult(await res.json());
      if (opts.json) {
        console.log(JSON.stringify(result, null, 2));
        return;
      }
      log.bold(`
Search: ${query}
`);
      console.log(result.answer || "(no answer returned)");
      if (result.citations.length > 0) {
        log.dim("\nSources:");
        result.citations.forEach((c, i) => {
          console.log(`  [${i + 1}] ${c.url}`);
        });
      }
      const d = result.usage?.server_side_tool_usage_details;
      if (d) {
        log.dim(
          `
(${d.web_search_calls ?? 0} web \xB7 ${d.x_search_calls ?? 0} X searches)`
        );
      }
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
}

// src/commands/video.ts
import { Command as Command10 } from "commander";
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
import { resolve } from "path";
var DEFAULT_VIDEO_MODEL = "grok-imagine-video";
var POLL_INTERVAL_MS = 5e3;
var WHITE_PIXEL_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/58BAwAHBQKhPX8EOAAAAABJRU5ErkJggg==";
function imageToDataUri(filePath) {
  const abs = resolve(filePath);
  const buf = readFileSync4(abs);
  const ext = abs.toLowerCase().endsWith(".png") ? "png" : "jpeg";
  return `data:image/${ext};base64,${buf.toString("base64")}`;
}
async function pollUntilDone(requestId, bearer, timeout, json) {
  const deadline = Date.now() + timeout;
  let lastProgress = -1;
  while (Date.now() < deadline) {
    await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
    const poll = await fetch(`${XAI_API_BASE_URL}/videos/${requestId}`, {
      headers: { Authorization: `Bearer ${bearer}` }
    });
    if (!poll.ok) {
      throw new Error(`Poll HTTP ${poll.status}: ${(await poll.text()).slice(0, 200)}`);
    }
    const data = await poll.json();
    const status = data.status;
    if (status === "done") return data;
    if (status === "failed") {
      const err = data.error;
      throw new Error(`Generation failed: ${err?.code ?? "unknown"} \u2014 ${err?.message ?? ""}`);
    }
    if (status === "expired") throw new Error("Generation expired");
    if (!json && typeof data.progress === "number" && data.progress !== lastProgress) {
      lastProgress = data.progress;
      process.stdout.write(`\r  Progress: ${Math.round(lastProgress * 100)}%`);
    }
  }
  throw new Error(`Timeout after ${timeout / 1e3}s \u2014 video still pending`);
}
async function downloadAndSave(videoUrl, outPath) {
  const videoRes = await fetch(videoUrl);
  if (!videoRes.ok) throw new Error(`Download failed: HTTP ${videoRes.status}`);
  const videoBuffer = Buffer.from(await videoRes.arrayBuffer());
  writeFileSync2(outPath, videoBuffer);
}
function videoCommand() {
  const cmd = new Command10("video").description(
    `Generate, edit, or extend video with Grok Imagine Video.

  Subcommands:
    generate (default)  Text-to-video or image-to-video
    edit                Edit existing video with text prompt (V2V)
    extend              Continue video from its last frame

  Examples:
    $ progrok video "A cat playing piano"
    $ progrok video "Animate this" --image photo.jpg
    $ progrok video edit "Make it sunset colors" --video input.mp4
    $ progrok video extend "Camera pulls back slowly" --video input.mp4 --duration 5`
  );
  cmd.argument("[prompt]", "video generation prompt").option("--model <id>", "video model", DEFAULT_VIDEO_MODEL).option("--duration <s>", "duration in seconds (1-15)", "5").option("--aspect <ratio>", "aspect ratio", "16:9").option("--resolution <r>", "480p or 720p", "480p").option("--image <path>", "source image for image-to-video").option("--output <path>", "output file path").option("--json", "output structured JSON").option("--timeout <s>", "polling timeout in seconds", "600").action(async (prompt, opts) => {
    if (!prompt) {
      cmd.help();
      return;
    }
    await generateAction(prompt, opts);
  });
  cmd.command("edit <prompt>").description("Edit existing video with a text prompt (real V2V). Model: grok-imagine-video only.").requiredOption("--video <url-or-path>", "source video URL or local .mp4 path").option("--model <id>", "video model (must be grok-imagine-video)", DEFAULT_VIDEO_MODEL).option("--output <path>", "output file path").option("--json", "output structured JSON").option("--timeout <s>", "polling timeout in seconds", "600").action(async (prompt, opts) => {
    await editAction(prompt, opts);
  });
  cmd.command("extend <prompt>").description("Extend video from its last frame. Model: grok-imagine-video only.").requiredOption("--video <url-or-path>", "source video URL or local .mp4 path").option("--model <id>", "video model (must be grok-imagine-video)", DEFAULT_VIDEO_MODEL).option("--duration <s>", "extension duration 2-10s (default 6)", "6").option("--output <path>", "output file path").option("--json", "output structured JSON").option("--timeout <s>", "polling timeout in seconds", "600").action(async (prompt, opts) => {
    await extendAction(prompt, opts);
  });
  return cmd;
}
async function generateAction(prompt, opts) {
  try {
    const bearer = await getValidBearer();
    const duration = parseInt(opts.duration ?? "5", 10);
    const timeout = parseInt(opts.timeout ?? "600", 10) * 1e3;
    const body = {
      model: opts.model ?? DEFAULT_VIDEO_MODEL,
      prompt,
      duration,
      aspect_ratio: opts.aspect ?? "16:9",
      resolution: opts.resolution ?? "480p"
    };
    if (opts.image) {
      const uri = opts.image.startsWith("data:") ? opts.image : imageToDataUri(opts.image);
      body.image = { url: uri };
    } else if (opts.model?.includes("1.5") && !opts.image) {
      body.image = { url: WHITE_PIXEL_PNG };
      body.prompt = `${prompt}

This is not a start frame \u2014 generate freely as a new video.`;
    }
    const res = await fetch(`${XAI_API_BASE_URL}/videos/generations`, {
      method: "POST",
      headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
      body: JSON.stringify(body)
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
    const { request_id } = await res.json();
    if (!opts.json) {
      log.info(`Video generation started (${request_id})`);
      log.dim(`Model: ${body.model} | ${duration}s | ${opts.aspect} | ${opts.resolution}`);
    }
    const data = await pollUntilDone(request_id, bearer, timeout, opts.json);
    if (opts.json) {
      console.log(JSON.stringify(data, null, 2));
      return;
    }
    const video = data.video;
    if (!video) throw new Error("No video in response");
    const outPath = opts.output ?? `progrok-video-${request_id.slice(0, 8)}.mp4`;
    await downloadAndSave(video.url, outPath);
    if (!opts.json) process.stdout.write("\n");
    log.success(`Video saved: ${outPath}`);
    log.info(`Duration: ${video.duration}s`);
    const usage = data.usage;
    if (usage?.cost_in_usd_ticks) log.dim(`Cost: $${(usage.cost_in_usd_ticks / 1e10).toFixed(4)}`);
  } catch (err) {
    log.error(err.message);
    process.exit(1);
  }
}
async function editAction(prompt, opts) {
  try {
    const bearer = await getValidBearer();
    const timeout = parseInt(opts.timeout ?? "600", 10) * 1e3;
    const model = opts.model ?? DEFAULT_VIDEO_MODEL;
    if (model.includes("1.5")) {
      throw new Error("Video editing is only supported by grok-imagine-video (not 1.5-preview).");
    }
    const videoUrl = opts.video.startsWith("http") ? opts.video : (() => {
      throw new Error("--video must be a URL (local file upload not yet supported for editing).");
    })();
    const res = await fetch(`${XAI_API_BASE_URL}/videos/edits`, {
      method: "POST",
      headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
      body: JSON.stringify({ model, prompt, video: { url: videoUrl } })
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
    const { request_id } = await res.json();
    if (!opts.json) {
      log.info(`Video edit started (${request_id})`);
      log.dim(`Model: ${model} | Edit: "${prompt.slice(0, 60)}"`);
    }
    const data = await pollUntilDone(request_id, bearer, timeout, opts.json);
    if (opts.json) {
      console.log(JSON.stringify(data, null, 2));
      return;
    }
    const video = data.video;
    if (!video) throw new Error("No video in response");
    const outPath = opts.output ?? `progrok-edit-${request_id.slice(0, 8)}.mp4`;
    await downloadAndSave(video.url, outPath);
    if (!opts.json) process.stdout.write("\n");
    log.success(`Edited video saved: ${outPath}`);
    log.info(`Duration: ${video.duration}s`);
  } catch (err) {
    log.error(err.message);
    process.exit(1);
  }
}
async function extendAction(prompt, opts) {
  try {
    const bearer = await getValidBearer();
    const timeout = parseInt(opts.timeout ?? "600", 10) * 1e3;
    const duration = parseInt(opts.duration ?? "6", 10);
    const model = opts.model ?? DEFAULT_VIDEO_MODEL;
    if (model.includes("1.5")) {
      throw new Error("Video extension is only supported by grok-imagine-video (not 1.5-preview).");
    }
    if (duration < 2 || duration > 10) {
      throw new Error("Extension duration must be 2-10 seconds.");
    }
    const videoUrl = opts.video.startsWith("http") ? opts.video : (() => {
      throw new Error("--video must be a URL (local file upload not yet supported for extension).");
    })();
    const res = await fetch(`${XAI_API_BASE_URL}/videos/extensions`, {
      method: "POST",
      headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
      body: JSON.stringify({ model, prompt, duration, video: { url: videoUrl } })
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
    const { request_id } = await res.json();
    if (!opts.json) {
      log.info(`Video extension started (${request_id})`);
      log.dim(`Model: ${model} | Extend: +${duration}s | "${prompt.slice(0, 60)}"`);
    }
    const data = await pollUntilDone(request_id, bearer, timeout, opts.json);
    if (opts.json) {
      console.log(JSON.stringify(data, null, 2));
      return;
    }
    const video = data.video;
    if (!video) throw new Error("No video in response");
    const outPath = opts.output ?? `progrok-extend-${request_id.slice(0, 8)}.mp4`;
    await downloadAndSave(video.url, outPath);
    if (!opts.json) process.stdout.write("\n");
    log.success(`Extended video saved: ${outPath}`);
    log.info(`Total duration: ${video.duration}s (original + ${duration}s extension)`);
  } catch (err) {
    log.error(err.message);
    process.exit(1);
  }
}

// src/commands/image.ts
import { Command as Command11 } from "commander";
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
import { resolve as resolve2 } from "path";
var DEFAULT_IMAGE_MODEL = "grok-imagine-image";
function imageToDataUri2(filePath) {
  const abs = resolve2(filePath);
  const buf = readFileSync5(abs);
  const ext = abs.toLowerCase().endsWith(".png") ? "png" : "jpeg";
  return `data:image/${ext};base64,${buf.toString("base64")}`;
}
function imageCommand() {
  return new Command11("image").description(
    `Generate or edit images with Grok Imagine.

  Examples:
    $ progrok image "A sunset over mountains"
    $ progrok image "Make it winter" --ref photo.jpg
    $ progrok image "prompt" --model grok-imagine-image-quality --resolution 2k`
  ).argument("<prompt>", "image generation/edit prompt").option("--model <id>", "image model", DEFAULT_IMAGE_MODEL).option("--aspect <ratio>", "aspect ratio (1:1, 16:9, 9:16, etc.)", "1:1").option("--resolution <r>", "1k or 2k", "1k").option("--ref <path>", "reference image(s) for editing (repeatable)", collectRefs, []).option("--output <path>", "output file path").option("--json", "output structured JSON").option("--n <count>", "number of images", "1").action(async (prompt, opts) => {
    try {
      const bearer = await getValidBearer();
      const refs = opts.ref ?? [];
      const n = parseInt(opts.n ?? "1", 10);
      const isEdit = refs.length > 0;
      const endpoint = isEdit ? "images/edits" : "images/generations";
      const body = {
        model: opts.model ?? DEFAULT_IMAGE_MODEL,
        prompt,
        n,
        response_format: "b64_json",
        aspect_ratio: opts.aspect ?? "1:1",
        resolution: opts.resolution ?? "1k"
      };
      if (isEdit) {
        if (refs.length === 1) {
          body.image = { type: "image_url", url: imageToDataUri2(refs[0]) };
        } else {
          body.images = refs.map((r) => ({ type: "image_url", url: imageToDataUri2(r) }));
        }
      }
      const res = await fetch(`${XAI_API_BASE_URL}/${endpoint}`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${bearer}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify(body)
      });
      if (!res.ok) {
        const errBody = await res.text();
        throw new Error(`HTTP ${res.status}: ${errBody.slice(0, 300)}`);
      }
      const data = await res.json();
      if (opts.json) {
        console.log(JSON.stringify(data, null, 2));
        return;
      }
      for (let i = 0; i < data.data.length; i++) {
        const item = data.data[i];
        if (item.b64_json) {
          const suffix = data.data.length > 1 ? `-${i + 1}` : "";
          const outPath = opts.output ? opts.output.replace(/(\.\w+)$/, `${suffix}$1`) : `progrok-image${suffix}.png`;
          writeFileSync3(outPath, Buffer.from(item.b64_json, "base64"));
          log.success(`Image saved: ${outPath}`);
        } else if (item.url) {
          log.info(`Image URL: ${item.url}`);
        }
        if (item.revised_prompt) {
          log.dim(`Revised: ${item.revised_prompt.slice(0, 100)}`);
        }
      }
      if (data.usage?.cost_in_usd_ticks) {
        log.dim(`Cost: $${(data.usage.cost_in_usd_ticks / 1e10).toFixed(4)}`);
      }
    } catch (err) {
      log.error(err.message);
      process.exit(1);
    }
  });
}
function collectRefs(value, prev) {
  return [...prev, value];
}

// src/utils/config.ts
import {
  readFileSync as readFileSync6,
  writeFileSync as writeFileSync4,
  existsSync as existsSync3,
  mkdirSync as mkdirSync2
} from "fs";
function ensureConfigDir2() {
  if (!existsSync3(CONFIG_DIR)) {
    mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
  }
}
function readConfig() {
  try {
    return JSON.parse(readFileSync6(CONFIG_FILE, "utf8"));
  } catch {
    return {};
  }
}
function writeConfig(config) {
  ensureConfigDir2();
  writeFileSync4(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
}

// src/utils/star-prompt.ts
async function showStarPrompt() {
  const config = readConfig();
  if (config.onboarding?.starPrompted) return;
  log.dim("\u2500".repeat(50));
  log.info("\u2B50 If progrok is useful, star the repo!");
  log.dim(`   ${GITHUB_URL}`);
  log.dim("\u2500".repeat(50) + "\n");
  writeConfig({
    ...config,
    onboarding: { ...config.onboarding, starPrompted: true }
  });
}

// src/index.ts
import { readFileSync as readFileSync7 } from "fs";
import { fileURLToPath as fileURLToPath4 } from "url";
import { dirname as dirname4, join as join5 } from "path";
var __dirname2 = dirname4(fileURLToPath4(import.meta.url));
function getVersion() {
  try {
    const pkg = JSON.parse(
      readFileSync7(join5(__dirname2, "..", "package.json"), "utf8")
    );
    return pkg.version;
  } catch {
    return "0.0.0";
  }
}
var program = new Command12();
program.name("progrok").description(
  `Activate your xAI OAuth session as a local Grok API proxy and CLI tool surface.

  SuperGrok OAuth \u2192 local proxy \u2192 OpenAI-compatible API for any client.
  All xAI endpoints forwarded: responses, chat, images, video, tts, stt.

  Quick start:
    $ progrok login          # OAuth via browser (or --device-code for SSH)
    $ progrok proxy          # Start proxy on 127.0.0.1:18645
    $ curl localhost:18645/v1/chat/completions -d '{"model":"grok-4.3",...}'

  Proxy forwards ALL /v1/* paths to api.x.ai \u2014 no whitelist.
  Supported xAI surfaces:
    /v1/responses            Responses API (streaming, tools, reasoning)
    /v1/chat/completions     OpenAI-compatible chat
    /v1/models               Model list
    /v1/language-models      Detailed models (pricing, aliases, modalities)
    /v1/images/generations   Image generation (grok-imagine-image)
    /v1/videos/generations   Video generation (async, poll /v1/videos/{id})
    /v1/tts                  Text-to-speech
    /v1/stt                  Speech-to-text
    /v1/batch/completions    Batch processing
    /v1/embeddings           Embeddings`
).version(getVersion());
program.addCommand(loginCommand());
program.addCommand(logoutCommand());
program.addCommand(proxyCommand());
program.addCommand(chatCommand());
program.addCommand(modelsCommand());
program.addCommand(statusCommand());
program.addCommand(skillCommand());
program.addCommand(capabilitiesCommand());
program.addCommand(searchCommand());
program.addCommand(videoCommand());
program.addCommand(imageCommand());
var REAL_COMMANDS = /* @__PURE__ */ new Set(["login", "logout", "proxy", "chat", "models", "status", "skill", "capabilities", "search", "video", "image"]);
var subcommand = process.argv[2];
if (subcommand && REAL_COMMANDS.has(subcommand)) {
  await showStarPrompt();
}
program.parse();
//# sourceMappingURL=index.js.map