← Files Yaps Video ClippingARCHIVED FILE

scripts/yaps-cli-discovery.mjs

40.2 KB · Sep 30, 2026 · 23:14 UTC

↓ Download file

import { constants, accessSync, readFileSync, realpathSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { basename, delimiter, join, posix, win32 } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";

export const DEFAULT_PROBE_TIMEOUT_MS = 5_000;
export const DEFAULT_AUTH_TIMEOUT_MS = 2_000;
export const DEFAULT_AUTH_RECOVERY_TIMEOUT_MS = 8_000;
export const MIN_SAFE_AUTH_STATUS_VERSION = "2.3.124";
const MAX_PROBE_BYTES = 64 * 1024;
const MAX_VERSION_BYTES = 4 * 1024;
const MAX_PLIST_BYTES = 64 * 1024;
const MAX_WINDOWS_SHIM_BYTES = 4 * 1024;
const RUNNING_YAPS_PROCESS_NAMES = new Set(["yaps.exe", "yaps_mcp.exe", "yaps_cli.exe"]);
// Fixed WMI filter: never interpolate a discovered path into this command.
export const WINDOWS_RUNNING_YAPS_PROCESS_COMMAND = "Get-CimInstance -ClassName Win32_Process -Filter \"Name = 'yaps.exe' OR Name = 'yaps_mcp.exe' OR Name = 'yaps_cli.exe'\" | ForEach-Object { $_.ExecutablePath }";
const DEFAULT_AUTH_RETRY_DELAYS_MS = [250, 500, 1_000, 2_000];
const REFRESHABLE_ACCOUNT_STATES = new Set([
  "cached_offline",
  "credential_missing",
  "verification_unavailable",
]);
const REFRESHABLE_ACCOUNT_DIAGNOSTICS = new Set([
  "account_cache_incomplete",
  "credential_missing",
  "profile_lookup_failed",
  "refresh_failed",
]);

function pathApi(platform) {
  return platform === "win32" ? win32 : posix;
}

function uniqueCandidates(candidates) {
  const seen = new Set();
  return candidates.filter(({ path }) => {
    if (!path || seen.has(path)) return false;
    seen.add(path);
    return true;
  });
}

function pathCandidates(name, { platform, env }) {
  const path = pathApi(platform);
  let extensions = platform === "win32"
    ? (env.PATHEXT || ".EXE;.CMD").split(";").filter(Boolean)
    : [""];
  // Current Windows installs expose `yaps.cmd` as the PATH shim. Older Yaps
  // releases could leave a copied `yaps.exe` beside it, so prefer the shim
  // before that stale executable while retaining both as fallbacks.
  if (platform === "win32" && name === "yaps") {
    extensions = [...extensions].sort((left, right) =>
      Number(right.toLowerCase() === ".cmd") - Number(left.toLowerCase() === ".cmd"));
  }
  const candidates = [];
  for (const directory of (env.PATH || "").split(path.delimiter).filter(Boolean)) {
    for (const extension of extensions) {
      const suffix = platform === "win32" ? extension.toLowerCase() : extension;
      candidates.push({ path: path.join(directory, `${name}${suffix}`), source: "path" });
    }
  }
  return candidates;
}

function connectorPathCandidates({ platform, env }) {
  if (platform !== "win32") return pathCandidates("yaps_mcp", { platform, env });
  return (env.PATH || "")
    .split(win32.delimiter)
    .filter(Boolean)
    .map((directory) => ({ path: win32.join(directory, "yaps_mcp.exe"), source: "path" }));
}

function installedCandidates(binaryName, { platform, env, home }) {
  const path = pathApi(platform);
  if (platform === "darwin") {
    return [
      { path: `/Applications/Yaps.app/Contents/MacOS/${binaryName}`, source: "installed_app" },
      ...(home ? [{ path: path.join(home, "Applications", "Yaps.app", "Contents", "MacOS", binaryName), source: "installed_app_user" }] : []),
    ];
  }
  if (platform === "win32") {
    const executable = `${binaryName}.exe`;
    // Official NSIS per-machine layout. Plugin hosts (Codex/ChatGPT already
    // running at install time) often omit ProgramW6432/ProgramFiles, so also
    // keep the well-known Program Files locations. Never Local AppData.
    return uniqueCandidates(
      [
        env.ProgramW6432,
        env.ProgramFiles,
        env["ProgramFiles(x86)"],
        "C:\\Program Files",
        "C:\\Program Files (x86)",
      ]
        .filter(Boolean)
        .map((root) => ({ path: path.join(root, "Yaps", executable), source: "installed_app" })),
    );
  }
  if (platform === "linux") {
    // Tauri's verified deb/rpm layout places external binaries in
    // usr/bin. /usr/bin is the only non-PATH Linux fallback we can assert
    // without scanning arbitrary mounts or home directories.
    return [{ path: `/usr/bin/${binaryName}`, source: "installed_app" }];
  }
  return [];
}

export function parseRunningYapsExecutables(output) {
  if (typeof output !== "string") return [];
  const seen = new Set();
  const result = [];
  for (const line of output.split(/\r?\n/)) {
    const value = line.trim();
    if (!value || !win32.isAbsolute(value)) continue;
    const name = win32.basename(value).toLowerCase();
    if (!RUNNING_YAPS_PROCESS_NAMES.has(name)) continue;
    const key = value.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    result.push(value);
  }
  return result;
}

export function cliSidecarFromRunningExecutable(executablePath, { platform = "win32" } = {}) {
  if (platform !== "win32" || typeof executablePath !== "string") return null;
  const path = win32;
  const name = path.basename(executablePath).toLowerCase();
  if (!RUNNING_YAPS_PROCESS_NAMES.has(name) || !path.isAbsolute(executablePath)) return null;
  return path.join(path.dirname(executablePath), "yaps_cli.exe");
}

export function runningAppCliCandidates({
  platform = process.platform,
  runningExecutables = [],
} = {}) {
  if (platform !== "win32") return [];
  return uniqueCandidates(
    (runningExecutables || [])
      .map((executable) => cliSidecarFromRunningExecutable(executable, { platform }))
      .filter(Boolean)
      .map((path) => ({ path, source: "running_app" })),
  );
}

async function defaultListRunningYapsExecutables({
  platform = process.platform,
  env = process.env,
} = {}) {
  // Only enumerate processes on a real Windows host. Tests that pass
  // `platform: "win32"` on Linux/macOS stay inert unless they inject
  // `runningExecutables` or `listRunning`.
  if (platform !== "win32" || process.platform !== "win32") return [];
  const systemRoot = env.SystemRoot || env.WINDIR;
  if (!systemRoot) return [];
  const powershell = win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
  if (!defaultCanAccess(powershell, "win32")) return [];
  const output = await runTextCommand(powershell, [
    "-NoLogo",
    "-NoProfile",
    "-NonInteractive",
    "-Command",
    WINDOWS_RUNNING_YAPS_PROCESS_COMMAND,
  ], { env, timeoutMs: DEFAULT_AUTH_TIMEOUT_MS });
  return parseRunningYapsExecutables(output);
}

export function cliCandidates({
  override,
  platform = process.platform,
  env = process.env,
  home = env.HOME || env.USERPROFILE || homedir(),
  runningExecutables = [],
} = {}) {
  const path = pathApi(platform);
  const executable = platform === "win32" ? "yaps_cli.exe" : "yaps_cli";
  const explicit = [
    override?.trim(),
    env.YAPS_CLI_BINARY?.trim(),
    env.YAPS_INSTALL_DIR?.trim() ? path.join(env.YAPS_INSTALL_DIR.trim(), executable) : "",
  ].find(Boolean);
  return uniqueCandidates([
    ...(explicit ? [{ path: explicit, source: "override" }] : []),
    ...pathCandidates("yaps", { platform, env }),
    ...pathCandidates("yaps_cli", { platform, env }),
    ...runningAppCliCandidates({ platform, runningExecutables }),
    ...installedCandidates("yaps_cli", { platform, env, home }),
  ]);
}

export function isYapsCliCommand(command, { platform = process.platform } = {}) {
  if (typeof command !== "string") return false;
  const name = pathApi(platform).basename(command).toLowerCase();
  return ["yaps", "yaps_cli", "yaps.exe", "yaps_cli.exe", "yaps.cmd", "yaps_cli.cmd"].includes(name);
}

export function connectorCandidates({
  platform = process.platform,
  env = process.env,
  home = env.HOME || env.USERPROFILE || homedir(),
} = {}) {
  const path = pathApi(platform);
  const executable = platform === "win32" ? "yaps_mcp.exe" : "yaps_mcp";
  const explicit = [
    env.YAPS_MCP_BINARY?.trim(),
    env.YAPS_INSTALL_DIR?.trim() ? path.join(env.YAPS_INSTALL_DIR.trim(), executable) : "",
  ].find(Boolean);
  return uniqueCandidates([
    ...(explicit ? [{ path: explicit, source: "override" }] : []),
    ...connectorPathCandidates({ platform, env }),
    ...installedCandidates("yaps_mcp", { platform, env, home }),
  ]);
}

function defaultCanAccess(candidate, platform) {
  try {
    accessSync(candidate, platform === "win32" ? constants.F_OK : constants.X_OK);
    return statSync(candidate).isFile();
  } catch {
    return false;
  }
}

function defaultPathExists(candidate) {
  try {
    accessSync(candidate, constants.F_OK);
    return true;
  } catch {
    return false;
  }
}

function terminateChild(child) {
  try { child.stdout?.destroy(); } catch {}
  try { child.stderr?.destroy(); } catch {}
  try { child.kill("SIGTERM"); } catch {}
  const force = setTimeout(() => {
    if (child.exitCode == null && child.signalCode == null) {
      try { child.kill("SIGKILL"); } catch {}
    }
  }, 100);
  force.unref?.();
  child.once?.("close", () => clearTimeout(force));
}

function runJsonCommand(candidate, args, {
  timeoutMs,
  spawnImpl = spawn,
  env = process.env,
} = {}) {
  return new Promise((resolve) => {
    let settled = false;
    let output = Buffer.alloc(0);
    let timer;
    const finish = (result) => {
      if (settled) return;
      settled = true;
      if (timer) clearTimeout(timer);
      resolve(result);
    };
    let child;
    try {
      child = spawnImpl(candidate, args, {
        env,
        stdio: ["ignore", "pipe", "pipe"],
        windowsHide: true,
      });
    } catch {
      resolve({ ok: false, reason: "launch_failed" });
      return;
    }
    child.stdout?.on("data", (chunk) => {
      if (settled) return;
      const next = Buffer.concat([output, Buffer.from(chunk)]);
      if (next.length > MAX_PROBE_BYTES) {
        terminateChild(child);
        finish({ ok: false, reason: "output_too_large" });
        return;
      }
      output = next;
    });
    child.stderr?.on("data", () => {});
    child.on("error", () => finish({ ok: false, reason: "launch_failed" }));
    child.on("close", (code, signal) => {
      if (signal || code !== 0) {
        finish({ ok: false, reason: "status_failed" });
        return;
      }
      try {
        const value = JSON.parse(output.toString("utf8"));
        finish(value && typeof value === "object" && !Array.isArray(value)
          ? { ok: true, value }
          : { ok: false, reason: "invalid_status" });
      } catch {
        finish({ ok: false, reason: "invalid_status" });
      }
    });
    timer = setTimeout(() => {
      terminateChild(child);
      finish({ ok: false, reason: "timeout" });
    }, Math.max(1, timeoutMs || DEFAULT_PROBE_TIMEOUT_MS));
  });
}

function runTextCommand(candidate, args, {
  timeoutMs = DEFAULT_AUTH_TIMEOUT_MS,
  spawnImpl = spawn,
  env = process.env,
} = {}) {
  return new Promise((resolve) => {
    let settled = false;
    let output = Buffer.alloc(0);
    let timer;
    const finish = (value) => {
      if (settled) return;
      settled = true;
      if (timer) clearTimeout(timer);
      resolve(value);
    };
    let child;
    try {
      child = spawnImpl(candidate, args, {
        env,
        stdio: ["ignore", "pipe", "pipe"],
        windowsHide: true,
      });
    } catch {
      resolve(null);
      return;
    }
    child.stdout?.on("data", (chunk) => {
      if (settled) return;
      const next = Buffer.concat([output, Buffer.from(chunk)]);
      if (next.length > MAX_VERSION_BYTES) {
        terminateChild(child);
        finish(null);
        return;
      }
      output = next;
    });
    child.stderr?.on("data", () => {});
    child.on("error", () => finish(null));
    child.on("close", (code, signal) => finish(!signal && code === 0 ? output.toString("utf8").trim() : null));
    timer = setTimeout(() => {
      terminateChild(child);
      finish(null);
    }, Math.max(1, timeoutMs));
  });
}

function normalizedVersion(value) {
  if (typeof value !== "string") return null;
  const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:\D|$)/);
  if (!match) return null;
  const parts = match.slice(1).map(Number);
  if (parts.some((part) => !Number.isSafeInteger(part))) return null;
  return { value: parts.join("."), parts };
}

export function authStatusSafetyForVersion(value) {
  const installed = normalizedVersion(value);
  const minimum = normalizedVersion(MIN_SAFE_AUTH_STATUS_VERSION);
  if (!installed || !minimum) return "unknown";
  for (let index = 0; index < minimum.parts.length; index += 1) {
    if (installed.parts[index] > minimum.parts[index]) return "safe";
    if (installed.parts[index] < minimum.parts[index]) return "unsafe";
  }
  return "safe";
}

function macApplicationForCli(cliPath, canonicalize) {
  let resolved;
  try {
    resolved = canonicalize(cliPath);
  } catch {
    resolved = cliPath;
  }
  const suffix = "/Contents/MacOS/yaps_cli";
  if (!resolved.endsWith(suffix)) return null;
  const application = resolved.slice(0, -suffix.length);
  return posix.basename(application) === "Yaps.app" ? application : null;
}

function knownInstallationVariant(cli, {
  platform = process.platform,
  env = process.env,
  home = env.HOME || env.USERPROFILE || homedir(),
  canonicalize = realpathSync,
} = {}) {
  if (platform !== "darwin" || !cli?.path) return null;
  const application = macApplicationForCli(cli.path, canonicalize);
  if (!application) return null;
  const setappApplications = new Set([
    "/Applications/Setapp/Yaps.app",
    ...(home ? [posix.join(home, "Applications", "Setapp", "Yaps.app")] : []),
  ]);
  return setappApplications.has(application) ? "setapp" : null;
}

/**
 * Read only version metadata from verified package locations. This never runs
 * the app or CLI, never scans the filesystem, and never interpolates a path
 * into a shell command.
 */
export async function readInstalledYapsVersion(cli, {
  platform = process.platform,
  env = process.env,
  canAccess = (candidate) => defaultCanAccess(candidate, platform),
  pathExists = defaultPathExists,
  canonicalize = realpathSync,
  statFile = statSync,
  readFile = readFileSync,
  readText = (command, args, commandEnv = env) => runTextCommand(command, args, { env: commandEnv }),
} = {}) {
  if (!cli?.path) return null;
  if (platform === "darwin") {
    const application = macApplicationForCli(cli.path, canonicalize);
    if (!application) return null;
    const plist = posix.join(application, "Contents", "Info.plist");
    try {
      const metadata = statFile(plist);
      if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null;
    } catch {
      return null;
    }
    try {
      const contents = readFile(plist, "utf8");
      const identifier = contents.match(/<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/)?.[1]?.trim();
      const version = contents.match(/<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/)?.[1];
      const parsed = normalizedVersion(version);
      if (identifier === "com.yaps.app" && parsed) return parsed.value;
      if (identifier && identifier !== "com.yaps.app") return null;
    } catch {
      // Binary plists are handled by the fixed, bounded system utility below.
    }
    if (!pathExists("/usr/bin/plutil")) return null;
    const identifier = await readText("/usr/bin/plutil", [
      "-extract", "CFBundleIdentifier", "raw", "-o", "-", plist,
    ]);
    if (identifier?.trim() !== "com.yaps.app") return null;
    return normalizedVersion(await readText("/usr/bin/plutil", [
      "-extract", "CFBundleShortVersionString", "raw", "-o", "-", plist,
    ]))?.value || null;
  }
  if (platform === "win32") {
    const path = win32;
    if (path.basename(cli.path).toLowerCase() !== "yaps_cli.exe") return null;
    const systemRoot = env.SystemRoot || env.WINDIR;
    if (!systemRoot || !canAccess(cli.path)) return null;
    const powershell = path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
    if (!canAccess(powershell)) return null;
    const result = await readText(powershell, [
      "-NoLogo",
      "-NoProfile",
      "-NonInteractive",
      "-Command",
      "(Get-Item -LiteralPath $env:YAPS_PLUGIN_VERSION_TARGET).VersionInfo.ProductVersion",
    ], { ...env, YAPS_PLUGIN_VERSION_TARGET: cli.path });
    return normalizedVersion(result)?.value || null;
  }
  if (platform === "linux") {
    let resolved;
    try {
      resolved = canonicalize(cli.path);
    } catch {
      resolved = cli.path;
    }
    if (resolved !== "/usr/bin/yaps_cli") return null;
    if (canAccess("/usr/bin/dpkg-query")) {
      const owner = await readText("/usr/bin/dpkg-query", ["-S", "/usr/bin/yaps_cli"]);
      const packageMatch = owner?.match(/^(yaps(?::[a-z0-9-]+)?):\s+\/usr\/bin\/yaps_cli$/);
      if (packageMatch) {
        const debian = normalizedVersion(await readText("/usr/bin/dpkg-query", ["-W", "-f=${Version}", packageMatch[1]]));
        if (debian) return debian.value;
      }
    }
    if (canAccess("/usr/bin/rpm")) {
      const owned = await readText("/usr/bin/rpm", ["-qf", "--queryformat", "%{NAME} %{VERSION}", "/usr/bin/yaps_cli"]);
      const match = owned?.match(/^yaps\s+(\S+)$/);
      return normalizedVersion(match?.[1])?.value || null;
    }
  }
  return null;
}

export function resolveWindowsShim(candidate, {
  platform = process.platform,
  canAccess = (path) => defaultCanAccess(path, platform),
  statFile = statSync,
  readFile = readFileSync,
} = {}) {
  if (platform !== "win32" || !/\.cmd$/i.test(candidate)) return candidate;
  try {
    const metadata = statFile(candidate);
    if (!metadata.isFile() || metadata.size > MAX_WINDOWS_SHIM_BYTES) return null;
    for (const line of readFile(candidate, "utf8").split(/\r?\n/)) {
      const match = line.trim().match(/^"([^"]+)"\s+%\*\s*$/i);
      if (!match) continue;
      const target = match[1].replace(/%%/g, "%");
      if (canAccess(target)) return target;
    }
  } catch {
    // The inaccessible shim will be rejected by the normal validation path.
  }
  return null;
}

export async function probeYapsCli(candidate, options = {}) {
  const result = await runJsonCommand(candidate, ["status", "--pretty"], {
    ...options,
    timeoutMs: options.timeoutMs || DEFAULT_PROBE_TIMEOUT_MS,
  });
  if (!result.ok) return result;
  const value = result.value;
  const valid = typeof value.settings_path === "string"
    && typeof value.settings_exists === "boolean"
    && typeof value.auth_store_path === "string"
    && typeof value.models_dir === "string";
  return valid ? { ok: true } : { ok: false, reason: "invalid_status" };
}

async function candidateAuthStatusSafety(cliPath, readAppVersion) {
  try {
    const version = normalizedVersion(await readAppVersion({ path: cliPath }))?.value;
    return authStatusSafetyForVersion(version);
  } catch {
    return "unknown";
  }
}

function rejectedHasStaleCli(rejected) {
  return Array.isArray(rejected) && rejected.some((entry) => entry?.reason === "stale_cli");
}

function accountStatusUnsafeDiagnosis(appVersion) {
  return {
    code: "account_status_unsafe",
    message: `Yaps ${appVersion || "before 2.3.124"} uses an older credential-based account check, so this plugin deliberately did not run it. Update Yaps to 2.3.124 or newer; the plugin will then reuse the desktop sign-in, trial, or Yaps Pro automatically. Do not approve a Keychain prompt or create a separate plugin account.`,
  };
}

/**
 * Runner contract when resolve found no bindable current CLI.
 * Leftover-only skip-stale (PF 2.1.4, nothing current) is a distinct stale
 * account signal — never local_yaps_unreachable / cli_missing / cli_invalid.
 */
export function classifyCliResolutionFailure(session) {
  if (session?.path) return null;
  if (rejectedHasStaleCli(session?.rejected)) {
    return { ...accountStatusUnsafeDiagnosis(session?.appVersion), exitCode: 78 };
  }
  return {
    code: "local_yaps_unreachable",
    message: diagnoseConnection({ cli: session }).message,
    exitCode: 127,
  };
}

export async function resolveYapsCli(options = {}) {
  const platform = options.platform || process.platform;
  const canAccess = options.canAccess || ((path) => defaultCanAccess(path, platform));
  const canonicalize = options.canonicalize || realpathSync;
  const probe = options.probe || ((path, probeOptions) => probeYapsCli(path, { ...options, ...probeOptions }));
  const readAppVersion = options.readAppVersion || ((cli) => readInstalledYapsVersion(cli, options));
  const now = options.now || Date.now;
  const deadline = now() + (options.totalTimeoutMs || 15_000);
  const rejected = [];
  const runningExecutables = options.runningExecutables
    ?? (options.listRunning ? await options.listRunning() : await defaultListRunningYapsExecutables(options));
  const candidates = cliCandidates({ ...options, runningExecutables });
  const authoritativeOverride = candidates[0]?.source === "override";
  let probes = 0;
  for (const candidate of candidates) {
    if (now() >= deadline || probes >= 8) break;
    if (!canAccess(candidate.path)) {
      if (authoritativeOverride) {
        rejected.push({ source: candidate.source, reason: "not_accessible" });
        break;
      }
      continue;
    }
    if (platform === "darwin") {
      let canonical = candidate.path;
      try {
        canonical = canonicalize(candidate.path);
      } catch {
        // The raw path is still enough to reject a direct GUI-binary path.
      }
      if (canonical.endsWith("/Yaps.app/Contents/MacOS/yaps")) {
        rejected.push({ source: candidate.source, reason: "gui_binary" });
        if (authoritativeOverride) break;
        continue;
      }
    }
    const resolvedPath = resolveWindowsShim(candidate.path, {
      platform,
      canAccess,
      statFile: options.statFile,
      readFile: options.readFile,
    });
    if (!resolvedPath) {
      rejected.push({ source: candidate.source, reason: "invalid_shim" });
      if (authoritativeOverride) break;
      continue;
    }
    probes += 1;
    const validation = await probe(resolvedPath, { timeoutMs: Math.min(DEFAULT_PROBE_TIMEOUT_MS, Math.max(1, deadline - now())) });
    if (!validation?.ok) {
      rejected.push({ source: candidate.source, reason: validation?.reason || "invalid_status" });
      if (authoritativeOverride) break;
      continue;
    }
    const resolved = { ...candidate, path: resolvedPath, rejected };
    // An explicit override stays authoritative even if it is older than 2.3.124.
    if (authoritativeOverride) return resolved;
    // A leftover Program Files 2.1.4 CLI still answers `status --pretty`.
    // Skip proven-old helpers and keep looking for a current sidecar beside a
    // running yaps.exe / yaps_mcp.exe. If every remaining candidate is
    // skip-stale rejected, do not bind the rejected path (do not execute
    // leftover 2.1.4). Diagnosis stays a distinct stale/unsafe account
    // signal — never cli_missing, cli_invalid, or local_yaps_unreachable
    // for a helper that was found.
    if (await candidateAuthStatusSafety(resolvedPath, readAppVersion) === "unsafe") {
      rejected.push({ source: candidate.source, reason: "stale_cli" });
      continue;
    }
    return resolved;
  }
  return { path: null, source: null, rejected };
}

function explicitSettingsSelected(args, env) {
  return Boolean(env.YAPS_SETTINGS_PATH?.trim())
    || args.some((value) => value === "--settings-path" || value.startsWith("--settings-path="));
}

function settingsPathArgument(args) {
  for (let index = 0; index < args.length; index += 1) {
    if (args[index] === "--settings-path") {
      const value = args[index + 1];
      return typeof value === "string" && value ? value : null;
    }
    if (typeof args[index] === "string" && args[index].startsWith("--settings-path=")) {
      return args[index].slice("--settings-path=".length) || null;
    }
  }
  return null;
}

function normalizedRecommendedSettingsPath(value, platform) {
  if (typeof value !== "string") return null;
  const candidate = value.trim();
  const path = pathApi(platform);
  if (!candidate || candidate.length > 4_096 || candidate.includes("\0")) return null;
  if (!path.isAbsolute(candidate) || path.basename(candidate).toLowerCase() !== "settings.json") return null;
  return candidate;
}

function sanitizedAuthStatus(value, platform) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  if (typeof value.authenticated !== "boolean" || typeof value.status !== "string") return null;
  return {
    authenticated: value.authenticated,
    status: value.status,
    diagnosticCode: typeof value.diagnostic_code === "string" ? value.diagnostic_code : null,
    recommendedSettingsPath: normalizedRecommendedSettingsPath(value.recommended_settings_path, platform),
  };
}

export async function readYapsAuthStatus(cliPath, {
  settingsPath,
  platform = process.platform,
  timeoutMs = DEFAULT_AUTH_TIMEOUT_MS,
  ...options
} = {}) {
  const args = [
    ...(settingsPath ? ["--settings-path", settingsPath] : []),
    "--pretty",
    "auth",
    "status",
  ];
  const result = await runJsonCommand(cliPath, args, { ...options, timeoutMs });
  if (!result.ok) return result;
  const auth = sanitizedAuthStatus(result.value, platform);
  return auth ? { ok: true, auth } : { ok: false, reason: "invalid_auth_status" };
}

function refreshableAccount(auth) {
  return Boolean(auth) && (
    REFRESHABLE_ACCOUNT_STATES.has(auth.status)
    || REFRESHABLE_ACCOUNT_DIAGNOSTICS.has(auth.diagnosticCode)
  );
}

function installedAppLaunch(cli, {
  platform,
  env,
  home,
  canAccess,
  pathExists,
  canonicalize,
}) {
  if (!cli?.path) return null;
  const path = pathApi(platform);
  if (platform === "darwin") {
    let cliPath = cli.path;
    try {
      cliPath = canonicalize(cli.path);
    } catch {
      // The already-validated raw path remains the only safe fallback.
    }
    const suffix = "/Contents/MacOS/yaps_cli";
    if (!cliPath.endsWith(suffix)) return null;
    const application = cliPath.slice(0, -suffix.length);
    const allowedApplications = new Set([
      "/Applications/Yaps.app",
      ...(home ? [posix.join(home, "Applications", "Yaps.app")] : []),
    ]);
    if (!allowedApplications.has(application) || !pathExists(application)) return null;
    return { command: "/usr/bin/open", args: ["-g", application], detached: false };
  }
  if (platform === "win32") {
    let cliPath = cli.path;
    try {
      cliPath = canonicalize(cli.path);
    } catch {
      // The already-validated raw path remains the only safe fallback.
    }
    if (path.basename(cliPath).toLowerCase() !== "yaps_cli.exe") return null;
    const allowedDirectories = new Set(
      [env.ProgramW6432, env.ProgramFiles]
        .filter(Boolean)
        .map((root) => path.normalize(path.join(root, "Yaps")).toLowerCase()),
    );
    const directory = path.normalize(path.dirname(cliPath));
    if (!allowedDirectories.has(directory.toLowerCase())) return null;
    const application = path.join(directory, "Yaps.exe");
    if (!canAccess(application)) return null;
    return { command: application, args: [], detached: true };
  }
  if (platform === "linux") {
    let cliPath = cli.path;
    try {
      cliPath = canonicalize(cli.path);
    } catch {
      // The already-validated raw path remains the only safe fallback.
    }
    if (cliPath !== "/usr/bin/yaps_cli") return null;
    const application = "/usr/bin/yaps";
    if (!canAccess(application)) return null;
    return { command: application, args: [], detached: true };
  }
  return null;
}

export function launchInstalledYaps(cli, {
  platform = process.platform,
  env = process.env,
  home = env.HOME || env.USERPROFILE || homedir(),
  spawnImpl = spawn,
  canAccess = (candidate) => defaultCanAccess(candidate, platform),
  pathExists = defaultPathExists,
  canonicalize = realpathSync,
  timeoutMs = 3_000,
} = {}) {
  const launch = installedAppLaunch(cli, { platform, env, home, canAccess, pathExists, canonicalize });
  if (!launch) return Promise.resolve(false);
  return new Promise((resolve) => {
    let settled = false;
    let timer;
    const finish = (value) => {
      if (settled) return;
      settled = true;
      if (timer) clearTimeout(timer);
      resolve(value);
    };
    try {
      const child = spawnImpl(launch.command, launch.args, {
        detached: launch.detached,
        stdio: "ignore",
        windowsHide: true,
      });
      child.on("error", () => finish(false));
      child.on("spawn", () => {
        if (launch.detached) {
          child.unref?.();
          finish(true);
        }
      });
      child.on("close", (code, signal) => {
        if (!launch.detached) finish(!signal && code === 0);
      });
      timer = setTimeout(() => {
        terminateChild(child);
        finish(false);
      }, Math.max(1, timeoutMs));
    } catch {
      finish(false);
    }
  });
}

export function applyResolvedSettings(args, settingsPath, { env = process.env } = {}) {
  if (!settingsPath || explicitSettingsSelected(args, env)) return [...args];
  return ["--settings-path", settingsPath, ...args];
}

function normalizedCliCommand(args) {
  const command = [];
  for (let index = 0; index < args.length; index += 1) {
    const value = args[index];
    if (["--settings-path", "--vault-root", "--mcp-binary"].includes(value)) {
      index += 1;
      continue;
    }
    if (["--pretty"].includes(value) || value.startsWith("--settings-path=")
      || value.startsWith("--vault-root=") || value.startsWith("--mcp-binary=")) continue;
    command.push(value);
  }
  return command;
}

export function isAuthStatusCommand(args) {
  const command = normalizedCliCommand(args);
  return command[0] === "auth" && command[1] === "status" && command.length === 2;
}

export function commandRequiresActiveAccount(args) {
  const command = normalizedCliCommand(args);
  if (command.includes("--help") || command.includes("-h")) return false;
  if (!command.length || ["status", "settings", "auth"].includes(command[0])) return false;
  if (command[0] === "features" && command[1] === "list") return false;
  return true;
}

export async function resolveYapsSession(options = {}) {
  const platform = options.platform || process.platform;
  const env = options.env || process.env;
  const commandArguments = options.commandArguments || [];
  const cli = options.cli || await resolveYapsCli(options);
  if (!cli.path) {
    const staleRejected = rejectedHasStaleCli(cli.rejected);
    return {
      ...cli,
      settingsPath: null,
      auth: null,
      appVersion: null,
      installationVariant: null,
      authStatusSafety: staleRejected ? "unsafe" : "unknown",
      appLaunchAttempted: false,
      appLaunchSucceeded: false,
    };
  }

  const installationVariant = options.installationVariant || knownInstallationVariant(cli, options);
  if (installationVariant === "setapp") {
    return {
      ...cli,
      settingsPath: null,
      auth: null,
      appVersion: null,
      installationVariant,
      authStatusSafety: "unknown",
      appLaunchAttempted: false,
      appLaunchSucceeded: false,
    };
  }

  const readAppVersion = options.readAppVersion || ((target) => readInstalledYapsVersion(target, options));
  const appVersion = normalizedVersion(options.appVersion)?.value || await readAppVersion(cli);
  const authStatusSafety = authStatusSafetyForVersion(appVersion);
  if (authStatusSafety !== "safe") {
    return {
      ...cli,
      settingsPath: null,
      auth: null,
      appVersion,
      installationVariant,
      authStatusSafety,
      appLaunchAttempted: false,
      appLaunchSucceeded: false,
    };
  }

  const readAuth = options.readAuth || ((settingsPath, timeoutMs) => readYapsAuthStatus(cli.path, {
    ...options,
    platform,
    env,
    settingsPath,
    timeoutMs,
  }));
  const explicitSettings = explicitSettingsSelected(commandArguments, env);
  const explicitSettingsPath = settingsPathArgument(commandArguments);
  let settingsPath = null;
  const authResult = await readAuth(explicitSettingsPath, DEFAULT_AUTH_TIMEOUT_MS);
  let auth = authResult.ok ? authResult.auth : null;

  if (!explicitSettings && auth?.recommendedSettingsPath && auth.status === "settings_path_mismatch") {
    const retry = await readAuth(auth.recommendedSettingsPath, DEFAULT_AUTH_TIMEOUT_MS);
    if (retry.ok) {
      settingsPath = auth.recommendedSettingsPath;
      auth = retry.auth;
    }
  }

  let appLaunchAttempted = false;
  let appLaunchSucceeded = false;
  if (options.recoverAccount === true && refreshableAccount(auth)) {
    appLaunchAttempted = true;
    const launchApp = options.launchApp || ((target) => launchInstalledYaps(target, options));
    appLaunchSucceeded = await launchApp(cli);
    if (appLaunchSucceeded) {
      const now = options.now || Date.now;
      const sleep = options.sleep || ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
      const deadline = now() + (options.authRecoveryTimeoutMs || DEFAULT_AUTH_RECOVERY_TIMEOUT_MS);
      const delays = options.authRetryDelaysMs || DEFAULT_AUTH_RETRY_DELAYS_MS;
      for (const delay of delays) {
        const beforeSleep = deadline - now();
        if (beforeSleep <= 0) break;
        await sleep(Math.min(delay, beforeSleep));
        const remaining = deadline - now();
        if (remaining <= 0) break;
        const retry = await readAuth(
          explicitSettingsPath || settingsPath,
          Math.min(DEFAULT_AUTH_TIMEOUT_MS, remaining),
        );
        if (!retry.ok) continue;
        auth = retry.auth;
        if (!refreshableAccount(auth)) break;
      }
    }
  }

  return {
    ...cli,
    settingsPath,
    auth,
    appVersion,
    installationVariant,
    authStatusSafety,
    appLaunchAttempted,
    appLaunchSucceeded,
  };
}

export function resolveYapsConnector(options = {}) {
  const platform = options.platform || process.platform;
  const canAccess = options.canAccess || ((path) => defaultCanAccess(path, platform));
  const candidates = connectorCandidates(options).filter((candidate) =>
    platform !== "win32" || win32.basename(candidate.path).toLowerCase() === "yaps_mcp.exe");
  if (candidates[0]?.source === "override") {
    return canAccess(candidates[0].path) ? candidates[0] : { path: null, source: null };
  }
  return candidates.find((candidate) => canAccess(candidate.path)) || { path: null, source: null };
}

export function diagnoseConnection({ cli, connector, needsConnector = false }) {
  if (!cli?.path) {
    if (rejectedHasStaleCli(cli?.rejected)) {
      return accountStatusUnsafeDiagnosis(cli.appVersion);
    }
    if (cli?.rejected?.length) {
      return {
        code: "cli_invalid",
        message: "A Yaps CLI candidate was found, but it did not pass the safe status check. If you set YAPS_CLI_BINARY, correct or remove that override. Otherwise update or reinstall Yaps, open it once, then start a new local ChatGPT or Codex task. Do not install a separate CLI or edit PATH.",
      };
    }
    return {
      code: "cli_missing",
      message: "The Yaps CLI could not be found from this local session. Install or update Yaps from https://yaps.ai/download, open it once, then start a new local ChatGPT or Codex task. The CLI is included with Yaps; no separate CLI or PATH setup is needed.",
    };
  }
  if (needsConnector && !connector?.path) {
    return {
      code: "vault_connector_unavailable",
      message: "The Yaps CLI is installed and working, but the private-vault connector is unavailable. Update or reinstall Yaps from https://yaps.ai/download, open it once, then start a new local ChatGPT or Codex task. Do not reinstall the plugin, install a separate CLI, or edit PATH.",
    };
  }
  return { code: "ready", message: "Yaps is ready." };
}

export function diagnoseAccount(session) {
  if (session?.installationVariant === "setapp") {
    return {
      code: "setapp_unsupported",
      message: "The Setapp edition of Yaps was found, but current plugins cannot safely reuse its separate activation state yet. Use the standard Yaps download for plugin automation, or wait for a future Yaps app update. Do not reconnect the plugin, edit PATH, or approve a Keychain prompt.",
    };
  }
  const auth = session?.auth;
  if (!auth) {
    if (session?.authStatusSafety === "unsafe" || rejectedHasStaleCli(session?.rejected)) {
      return accountStatusUnsafeDiagnosis(session?.appVersion);
    }
    if (session?.authStatusSafety === "unknown") {
      return {
        code: "account_status_unverified",
        message: "The Yaps CLI is working, but this plugin could not verify that its account check is credential-free, so it deliberately did not run it. Update or reinstall the official Yaps app; the plugin will then reuse its desktop account automatically. Do not reconnect the plugin or approve a Keychain prompt.",
      };
    }
    return {
      code: "account_status_unavailable",
      message: "The Yaps CLI is installed, but this plugin could not read the desktop account status. Update Yaps, open it once, and retry. No separate plugin connection or account is required.",
    };
  }
  if (auth.authenticated && auth.status === "active") {
    return {
      code: "ready",
      message: "The signed-in Yaps account is active. An active trial or Yaps Pro both count.",
    };
  }
  if (auth.status === "platform_mismatch") {
    return {
      code: "platform_mismatch",
      message: "Yaps is signed in, but this account only has mobile access. Activate desktop-compatible access in Yaps; the plugin will use it automatically.",
    };
  }
  if (auth.status === "expired") {
    return {
      code: "account_expired",
      message: "Yaps is signed in, but its trial or Yaps Pro access is not active. Open Yaps to review the available trial or Yaps Pro options; the plugin will pick up the change automatically.",
    };
  }
  if (auth.status === "credential_unavailable" || auth.diagnosticCode === "keychain_unavailable") {
    return {
      code: "account_status_unsupported",
      message: "This installed Yaps helper uses an older credential-based account check. Update Yaps and retry. Do not approve a Keychain prompt or create a separate plugin account.",
    };
  }
  if (refreshableAccount(auth)) {
    const attempted = session.appLaunchSucceeded
      ? "The plugin opened the installed Yaps app automatically, but the account cache did not refresh in time. "
      : session.appLaunchAttempted
        ? "The plugin could not safely open the installed Yaps app in the background. "
        : "";
    return {
      code: "account_cache_incomplete",
      message: `${attempted}Yaps found the desktop sign-in but could not verify current trial or Yaps Pro access. Check the internet connection and retry; do not create a separate plugin account.`,
    };
  }
  if (auth.status === "settings_path_mismatch") {
    return {
      code: "settings_path_mismatch",
      message: "Yaps is installed, but the alternate desktop settings file could not be validated automatically. Update or reinstall Yaps and retry; do not edit PATH or reconnect the plugin.",
    };
  }
  if (["signed_out", "unauthenticated"].includes(auth.status)) {
    return {
      code: "unauthenticated",
      message: "Yaps is installed, but no active desktop account is signed in. Sign in inside Yaps and start an available trial or activate Yaps Pro; the plugin then uses that same session automatically, with no separate connection.",
    };
  }
  return {
    code: "account_status_unavailable",
    message: "Yaps returned an account state this plugin does not recognize. Update Yaps and retry; do not reconnect the plugin or create another account.",
  };
}

async function main(argv) {
  if (!["--resolve-cli", "--resolve-session"].includes(argv[0])) return 2;
  const overrideIndex = argv.indexOf("--override");
  const override = overrideIndex >= 0 ? argv[overrideIndex + 1] : undefined;
  const result = argv[0] === "--resolve-session"
    ? await resolveYapsSession({ override, recoverAccount: true })
    : await resolveYapsCli({ override });
  if (!result.path) {
    process.stderr.write(`${diagnoseConnection({ cli: result }).message}\n`);
    return 1;
  }
  const account = argv[0] === "--resolve-session" ? diagnoseAccount(result) : null;
  process.stdout.write(`${JSON.stringify({
    path: result.path,
    source: result.source,
    ...(argv[0] === "--resolve-session" ? {
      settings_path: result.settingsPath,
      authenticated: result.auth?.authenticated === true,
      account_status: result.auth?.status || "unknown",
      diagnostic_code: result.auth?.diagnosticCode || null,
      app_launch_attempted: result.appLaunchAttempted,
      app_launch_succeeded: result.appLaunchSucceeded,
      app_version: result.appVersion,
      auth_status_safety: result.authStatusSafety,
      account_code: account.code,
      account_message: account.message,
    } : {}),
  })}\n`);
  return 0;
}

if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
  process.exitCode = await main(process.argv.slice(2));
}

SHA-256: 2264b812911bf5275f31d1f27f24f483269577a213fc97be7f36fc91a6b1bfc8