← Yaps Text to SpeechCONTENT HISTORY

Update to Yaps Text to Speech

Snapshot Nov 12, 2026 · 06:08 UTC · version 0.1.12

WHAT CHANGED

Package or technical metadata updated

Package or technical metadata updated

Observed in package metadata. These changes alone do not establish a new customer-facing feature.

Save Yaps Text to Speech and check future changes in Following.

Up to 3 plugins free · no card required.

All fields and original values.

These are saved file metadata. Open a changed file to read its text edits.

Package file

Before

Sep 30, 2026 · 23:13 UTC

Not present

After

Nov 12, 2026 · 06:08 UTC

{"sha256":"47101c8cadd537823635774c3953dfb8663f60e5d5c5bc355d066829fd8deaf5","size":493}

Package file

Before

Sep 30, 2026 · 23:13 UTC

{"sha256":"d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18","size":7865}

After

Nov 12, 2026 · 06:08 UTC

Not present

Package file

Before

Sep 30, 2026 · 23:13 UTC

{"sha256":"d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18","size":7865}

After

Nov 12, 2026 · 06:08 UTC

Not present

Package file

Before

Sep 30, 2026 · 23:13 UTC

{"sha256":"d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18","size":7865}

After

Nov 12, 2026 · 06:08 UTC

Not present

Compare saved observations

Download comparison JSON

scripts/yaps-cli-discovery.mjs

− removed · + added

--- before
+++ after
@@ -1,15 +1,60 @@
-import { constants, accessSync, readFileSync, realpathSync, statSync } from "node:fs";
+import { constants, accessSync, closeSync, openSync, readFileSync, readSync, 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";
 
+// Read only named non-secret settings by default. Callers can supply their own
+// explicit environment; discovery never needs an agent provider credential.
+export function defaultYapsEnvironment() {
+  return Object.fromEntries(Object.entries({
+    "PATH": process.env["PATH"],
+    "Path": process.env["Path"],
+    "PATHEXT": process.env["PATHEXT"],
+    "HOME": process.env["HOME"],
+    "USERPROFILE": process.env["USERPROFILE"],
+    "HOMEDRIVE": process.env["HOMEDRIVE"],
+    "HOMEPATH": process.env["HOMEPATH"],
+    "APPDATA": process.env["APPDATA"],
+    "LOCALAPPDATA": process.env["LOCALAPPDATA"],
+    "ProgramFiles": process.env["ProgramFiles"],
+    "ProgramFiles(x86)": process.env["ProgramFiles(x86)"],
+    "ProgramW6432": process.env["ProgramW6432"],
+    "SystemRoot": process.env["SystemRoot"],
+    "SYSTEMROOT": process.env["SYSTEMROOT"],
+    "WINDIR": process.env["WINDIR"],
+    "COMSPEC": process.env["COMSPEC"],
+    "ComSpec": process.env["ComSpec"],
+    "TMPDIR": process.env["TMPDIR"],
+    "TMP": process.env["TMP"],
+    "TEMP": process.env["TEMP"],
+    "LANG": process.env["LANG"],
+    "LANGUAGE": process.env["LANGUAGE"],
+    "LC_ALL": process.env["LC_ALL"],
+    "LC_CTYPE": process.env["LC_CTYPE"],
+    "TZ": process.env["TZ"],
+    "XDG_CONFIG_HOME": process.env["XDG_CONFIG_HOME"],
+    "XDG_DATA_HOME": process.env["XDG_DATA_HOME"],
+    "XDG_CACHE_HOME": process.env["XDG_CACHE_HOME"],
+    "XDG_RUNTIME_DIR": process.env["XDG_RUNTIME_DIR"],
+    "DISPLAY": process.env["DISPLAY"],
+    "WAYLAND_DISPLAY": process.env["WAYLAND_DISPLAY"],
+    "DBUS_SESSION_BUS_ADDRESS": process.env["DBUS_SESSION_BUS_ADDRESS"],
+    "YAPS_CLI_BINARY": process.env["YAPS_CLI_BINARY"],
+    "YAPS_INSTALL_DIR": process.env["YAPS_INSTALL_DIR"],
+    "YAPS_MCP_BINARY": process.env["YAPS_MCP_BINARY"],
+    "YAPS_SETTINGS_PATH": process.env["YAPS_SETTINGS_PATH"],
+    "YAPS_CLI_PROGRESS": process.env["YAPS_CLI_PROGRESS"],
+  }).filter(([, value]) => typeof value === "string"));
+}
+
 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_PE_RESOURCE_BYTES = 2 * 1024 * 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"]);
@@ -145,7 +190,7 @@
 
 async function defaultListRunningYapsExecutables({
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
 } = {}) {
   // Only enumerate processes on a real Windows host. Tests that pass
   // `platform: "win32"` on Linux/macOS stay inert unless they inject
@@ -168,7 +213,7 @@
 export function cliCandidates({
   override,
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
   home = env.HOME || env.USERPROFILE || homedir(),
   runningExecutables = [],
 } = {}) {
@@ -179,8 +224,14 @@
     env.YAPS_CLI_BINARY?.trim(),
     env.YAPS_INSTALL_DIR?.trim() ? path.join(env.YAPS_INSTALL_DIR.trim(), executable) : "",
   ].find(Boolean);
+  // A PATH shim can survive an upgrade and still point to a credential-free
+  // helper from a different installation. Start with the running desktop's
+  // sidecar. A running CLI or MCP helper alone is not a desktop install signal.
+  const runningDesktop = (runningExecutables || []).filter((executablePath) =>
+    typeof executablePath === "string" && win32.basename(executablePath).toLowerCase() === "yaps.exe");
   return uniqueCandidates([
     ...(explicit ? [{ path: explicit, source: "override" }] : []),
+    ...runningAppCliCandidates({ platform, runningExecutables: runningDesktop }),
     ...pathCandidates("yaps", { platform, env }),
     ...pathCandidates("yaps_cli", { platform, env }),
     ...runningAppCliCandidates({ platform, runningExecutables }),
@@ -196,7 +247,7 @@
 
 export function connectorCandidates({
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
   home = env.HOME || env.USERPROFILE || homedir(),
 } = {}) {
   const path = pathApi(platform);
@@ -246,7 +297,7 @@
 function runJsonCommand(candidate, args, {
   timeoutMs,
   spawnImpl = spawn,
-  env = process.env,
+  env = defaultYapsEnvironment(),
 } = {}) {
   return new Promise((resolve) => {
     let settled = false;
@@ -305,7 +356,7 @@
 function runTextCommand(candidate, args, {
   timeoutMs = DEFAULT_AUTH_TIMEOUT_MS,
   spawnImpl = spawn,
-  env = process.env,
+  env = defaultYapsEnvironment(),
 } = {}) {
   return new Promise((resolve) => {
     let settled = false;
@@ -383,7 +434,7 @@
 
 function knownInstallationVariant(cli, {
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
   home = env.HOME || env.USERPROFILE || homedir(),
   canonicalize = realpathSync,
 } = {}) {
@@ -397,6 +448,82 @@
   return setappApplications.has(application) ? "setapp" : null;
 }
 
+function peResourceEntry(resources, directory, id) {
+  const count = resources.readUInt16LE(directory + 12) + resources.readUInt16LE(directory + 14);
+  const start = directory + 16;
+  if (start + count * 8 > resources.length) return null;
+  for (let entry = start; entry < start + count * 8; entry += 8) {
+    if (id === undefined || resources.readUInt32LE(entry) === id) {
+      return resources.readUInt32LE(entry + 4);
+    }
+  }
+  return null;
+}
+
+// Read the numeric PRODUCTVERSION in the PE RT_VERSION resource. The shipped
+// helper stamps this alongside the display string. No PowerShell startup or
+// execution of the binary is needed to decide whether auth status is safe.
+function readWindowsProductVersion(executable) {
+  let fd;
+  try {
+    fd = openSync(executable, "r");
+    const readAt = (length, position) => {
+      const bytes = Buffer.alloc(length);
+      if (readSync(fd, bytes, 0, length, position) !== length) throw new Error("Truncated PE data");
+      return bytes;
+    };
+    const dos = readAt(64, 0);
+    if (dos.readUInt16LE(0) !== 0x5a4d) return null;
+    const peOffset = dos.readUInt32LE(0x3c);
+    const coff = readAt(24, peOffset);
+    if (coff.readUInt32LE(0) !== 0x00004550) return null;
+    const sectionCount = coff.readUInt16LE(6);
+    const optionalSize = coff.readUInt16LE(20);
+    if (sectionCount === 0 || sectionCount > 96) return null;
+    const headers = readAt(optionalSize + sectionCount * 40, peOffset + 24);
+    const magic = headers.readUInt16LE(0);
+    const directories = magic === 0x20b ? 112 : magic === 0x10b ? 96 : 0;
+    if (!directories || optionalSize < directories + 24 || headers.readUInt32LE(directories - 4) < 3) return null;
+    const resourceRva = headers.readUInt32LE(directories + 16);
+    const resourceSize = headers.readUInt32LE(directories + 20);
+    if (!resourceRva || !resourceSize || resourceSize > MAX_PE_RESOURCE_BYTES) return null;
+
+    let resources;
+    for (let section = optionalSize; section < headers.length; section += 40) {
+      const offset = resourceRva - headers.readUInt32LE(section + 12);
+      if (offset >= 0 && offset + resourceSize <= headers.readUInt32LE(section + 16)) {
+        resources = readAt(resourceSize, headers.readUInt32LE(section + 20) + offset);
+        break;
+      }
+    }
+    if (!resources) return null;
+    const directoryFlag = 0x80000000;
+    const type = peResourceEntry(resources, 0, 16);
+    if (type === null || type < directoryFlag) return null;
+    const name = peResourceEntry(resources, type - directoryFlag);
+    if (name === null || name < directoryFlag) return null;
+    const data = peResourceEntry(resources, name - directoryFlag);
+    if (data === null || data >= directoryFlag || data + 16 > resources.length) return null;
+    const offset = resources.readUInt32LE(data) - resourceRva;
+    const size = resources.readUInt32LE(data + 4);
+    if (offset < 0 || size < 92 || offset + size > resources.length) return null;
+
+    const version = resources.subarray(offset, offset + size);
+    const fixedOffset = 40;
+    if (version.readUInt16LE(0) < fixedOffset + 52 || version.readUInt16LE(0) > size
+      || version.readUInt16LE(2) !== 52 || version.toString("utf16le", 6, 38) !== "VS_VERSION_INFO\0"
+      || version.readUInt32LE(fixedOffset) !== 0xfeef04bd) return null;
+    const majorMinor = version.readUInt32LE(fixedOffset + 16);
+    const buildRevision = version.readUInt32LE(fixedOffset + 20);
+    if (!majorMinor && !buildRevision) return null;
+    return `${majorMinor >>> 16}.${majorMinor & 0xffff}.${buildRevision >>> 16}`;
+  } catch {
+    return null;
+  } finally {
+    if (fd !== undefined) closeSync(fd);
+  }
+}
+
 /**
  * Read only version metadata from verified package locations. This never runs
  * the app or CLI, never scans the filesystem, and never interpolates a path
@@ -404,7 +531,7 @@
  */
 export async function readInstalledYapsVersion(cli, {
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
   canAccess = (candidate) => defaultCanAccess(candidate, platform),
   pathExists = defaultPathExists,
   canonicalize = realpathSync,
@@ -444,9 +571,11 @@
   }
   if (platform === "win32") {
     const path = win32;
-    if (path.basename(cli.path).toLowerCase() !== "yaps_cli.exe") return null;
+    if (path.basename(cli.path).toLowerCase() !== "yaps_cli.exe" || !canAccess(cli.path)) return null;
+    const productVersion = readWindowsProductVersion(cli.path);
+    if (productVersion) return productVersion;
     const systemRoot = env.SystemRoot || env.WINDIR;
-    if (!systemRoot || !canAccess(cli.path)) return null;
+    if (!systemRoot) return null;
     const powershell = path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
     if (!canAccess(powershell)) return null;
     const result = await readText(powershell, [
@@ -565,6 +694,7 @@
   const now = options.now || Date.now;
   const deadline = now() + (options.totalTimeoutMs || 15_000);
   const rejected = [];
+  let provisional = null;
   const runningExecutables = options.runningExecutables
     ?? (options.listRunning ? await options.listRunning() : await defaultListRunningYapsExecutables(options));
   const candidates = cliCandidates({ ...options, runningExecutables });
@@ -611,21 +741,34 @@
       continue;
     }
     const resolved = { ...candidate, path: resolvedPath, rejected };
-    // An explicit override stays authoritative even if it is older than 2.3.124.
+    // An explicit override stays authoritative even if it is older than
+    // 2.3.124 or its version is unreadable. Return it after a successful
+    // status probe.
     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") {
+    const safety = await candidateAuthStatusSafety(resolvedPath, readAppVersion);
+    // The finder already probed `status --pretty`. A leftover helper is
+    // therefore executed for that probe. Then reject a proven-old helper
+    // (older than MIN_SAFE_AUTH_STATUS_VERSION 2.3.124, the credential-free
+    // account-check floor — not a claim the helper is current or supports
+    // every tool) before account checks or the requested operation.
+    if (safety === "unsafe") {
       rejected.push({ source: candidate.source, reason: "stale_cli" });
       continue;
     }
+    // Unknown versions (including a Windows PATH yaps.exe, because version
+    // metadata is only read for a filename of yaps_cli.exe) stay provisional.
+    // Do not bind yet and do not treat unknown as stale_cli. Keep searching
+    // for a verified >= 2.3.124 candidate; the first verified helper wins
+    // even if it is later in the list (running_app sidecar after PATH).
+    if (safety === "unknown") {
+      if (!provisional) provisional = resolved;
+      continue;
+    }
     return resolved;
   }
+  // If nothing verified, bind the first provisional unknown that passed
+  // status. Do not turn a lone unknown PATH hit into a miss.
+  if (provisional) return { ...provisional, rejected };
   return { path: null, source: null, rejected };
 }
 
@@ -659,11 +802,23 @@
 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;
+  // Older helpers report a missing or inaccessible auth store as signed out.
+  // Keep a readable sign-out receipt distinct from a host that cannot see it.
+  const missingDesktopAccount = value.status === "unauthenticated"
+    && value.diagnostic_code === "signed_out" && value.auth_store_exists === false;
+  const diagnosticCode = missingDesktopAccount
+    ? "desktop_account_unavailable"
+    : typeof value.diagnostic_code === "string" ? value.diagnostic_code : null;
+  const followedSettingsPath = diagnosticCode === "followed_desktop_account"
+    ? normalizedRecommendedSettingsPath(value.settings_path, platform)
+    : null;
+  if (diagnosticCode === "followed_desktop_account" && !followedSettingsPath) return null;
   return {
     authenticated: value.authenticated,
-    status: value.status,
-    diagnosticCode: typeof value.diagnostic_code === "string" ? value.diagnostic_code : null,
+    status: missingDesktopAccount ? "verification_unavailable" : value.status,
+    diagnosticCode,
     recommendedSettingsPath: normalizedRecommendedSettingsPath(value.recommended_settings_path, platform),
+    followedSettingsPath,
   };
 }
 
@@ -686,7 +841,7 @@
 }
 
 function refreshableAccount(auth) {
-  return Boolean(auth) && (
+  return Boolean(auth) && auth.diagnosticCode !== "desktop_account_unavailable" && (
     REFRESHABLE_ACCOUNT_STATES.has(auth.status)
     || REFRESHABLE_ACCOUNT_DIAGNOSTICS.has(auth.diagnosticCode)
   );
@@ -755,7 +910,7 @@
 
 export function launchInstalledYaps(cli, {
   platform = process.platform,
-  env = process.env,
+  env = defaultYapsEnvironment(),
   home = env.HOME || env.USERPROFILE || homedir(),
   spawnImpl = spawn,
   canAccess = (candidate) => defaultCanAccess(candidate, platform),
@@ -776,6 +931,7 @@
     };
     try {
       const child = spawnImpl(launch.command, launch.args, {
+        env,
         detached: launch.detached,
         stdio: "ignore",
         windowsHide: true,
@@ -800,8 +956,20 @@
   });
 }
 
-export function applyResolvedSettings(args, settingsPath, { env = process.env } = {}) {
-  if (!settingsPath || explicitSettingsSelected(args, env)) return [...args];
+export function applyResolvedSettings(args, settingsPath, { env = defaultYapsEnvironment(), followDesktopAccount = false } = {}) {
+  if (!settingsPath) return [...args];
+  if (followDesktopAccount) {
+    const withoutOldPath = [];
+    for (let index = 0; index < args.length; index += 1) {
+      if (args[index] === "--settings-path") {
+        index += 1;
+      } else if (!args[index].startsWith("--settings-path=")) {
+        withoutOldPath.push(args[index]);
+      }
+    }
+    return ["--settings-path", settingsPath, ...withoutOldPath];
+  }
+  if (explicitSettingsSelected(args, env)) return [...args];
   return ["--settings-path", settingsPath, ...args];
 }
 
@@ -821,8 +989,9 @@
 }
 
 export function isAuthStatusCommand(args) {
-  const command = normalizedCliCommand(args);
-  return command[0] === "auth" && command[1] === "status" && command.length === 2;
+  const [group, action, ...flags] = normalizedCliCommand(args);
+  return group === "auth" && action === "status"
+    && (flags.length === 0 || (flags.length === 1 && flags[0] === "--redact"));
 }
 
 export function commandRequiresActiveAccount(args) {
@@ -830,12 +999,15 @@
   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;
+  // Reading, waiting on or cancelling already-queued background jobs must keep
+  // working if access lapses mid-job; only `jobs retry` queues new work.
+  if (command[0] === "jobs" && command[1] !== "retry") return false;
   return true;
 }
 
 export async function resolveYapsSession(options = {}) {
   const platform = options.platform || process.platform;
-  const env = options.env || process.env;
+  const env = options.env || defaultYapsEnvironment();
   const commandArguments = options.commandArguments || [];
   const cli = options.cli || await resolveYapsCli(options);
   if (!cli.path) {
@@ -894,12 +1066,14 @@
   let settingsPath = null;
   const authResult = await readAuth(explicitSettingsPath, DEFAULT_AUTH_TIMEOUT_MS);
   let auth = authResult.ok ? authResult.auth : null;
+  if (auth?.followedSettingsPath) settingsPath = auth.followedSettingsPath;
 
   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;
+      if (auth.followedSettingsPath) settingsPath = auth.followedSettingsPath;
     }
   }
 
@@ -926,6 +1100,7 @@
         );
         if (!retry.ok) continue;
         auth = retry.auth;
+        if (auth.followedSettingsPath) settingsPath = auth.followedSettingsPath;
         if (!refreshableAccount(auth)) break;
       }
     }
@@ -962,18 +1137,18 @@
     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.",
+        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 agent task on this computer. 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.",
+      message: "The Yaps CLI could not be found from this local session. Install or update Yaps using the official download link in the plugin README, open it once, then start a new agent task on this computer. 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.",
+      message: "The Yaps CLI is installed and working, but the private-vault connector is unavailable. Update or reinstall Yaps using the official download link in the plugin README, open it once, then start a new agent task on this computer. Do not reinstall the plugin, install a separate CLI, or edit PATH.",
     };
   }
   return { code: "ready", message: "Yaps is ready." };
@@ -988,7 +1163,7 @@
   }
   const auth = session?.auth;
   if (!auth) {
-    if (session?.authStatusSafety === "unsafe" || rejectedHasStaleCli(session?.rejected)) {
+    if (session?.authStatusSafety === "unsafe" || (!session?.path && rejectedHasStaleCli(session?.rejected))) {
       return accountStatusUnsafeDiagnosis(session?.appVersion);
     }
     if (session?.authStatusSafety === "unknown") {
@@ -1026,6 +1201,12 @@
       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 (auth.diagnosticCode === "desktop_account_unavailable") {
+    return {
+      code: "desktop_account_unavailable",
+      message: "This session could not read the Yaps desktop account. Open Yaps and use a local session under the same operating system 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. "
Full technical diff · 13 changed fields

added /files/.claude-plugin~1plugin.json

BEFORE
Field was absent
AFTER
{
  "sha256": "47101c8cadd537823635774c3953dfb8663f60e5d5c5bc355d066829fd8deaf5",
  "size": 493
}

removed /files/.codex-plugin~1assets~1composer-icon.png

BEFORE
{
  "sha256": "d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18",
  "size": 7865
}
AFTER
Field is absent

removed /files/.codex-plugin~1assets~1logo-dark.png

BEFORE
{
  "sha256": "d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18",
  "size": 7865
}
AFTER
Field is absent

removed /files/.codex-plugin~1assets~1logo.png

BEFORE
{
  "sha256": "d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18",
  "size": 7865
}
AFTER
Field is absent

changed /files/.codex-plugin~1plugin.json/sha256

BEFORE
"e817fdce4232369d0d6ebf1a1bb0cf17ea6adf7d3451d94d82ebb4fabf10e114"
AFTER
"cffdfe739409db857808fb24108fbffcf14f597f23e2af89aab06d6bdfe6aab7"

changed /files/.codex-plugin~1plugin.json/size

BEFORE
2267
AFTER
2243

added /files/plugin.json

BEFORE
Field was absent
AFTER
{
  "sha256": "a42c88528cec093da9384b09460404c5868d1e226b00c32b6396e7a187b4e75c",
  "size": 2453
}

changed /files/scripts~1yaps-cli-discovery.mjs/sha256

BEFORE
"2264b812911bf5275f31d1f27f24f483269577a213fc97be7f36fc91a6b1bfc8"
AFTER
"1d3808134d03a4d19f2b2eac3acea2d6af4b7538bcc0da84afe43bce09eca257"

changed /files/scripts~1yaps-cli-discovery.mjs/size

BEFORE
41185
AFTER
50423

changed /files/scripts~1yaps-plugin-runner.mjs/sha256

BEFORE
"c5959de172d2c855d7edb39c433dd117dd1946719fe217486483d7b445a9619d"
AFTER
"af9fc263e016cb959379f68fea158891d65e774b0457ed475f0a156f12ce34c9"

changed /files/scripts~1yaps-plugin-runner.mjs/size

BEFORE
10993
AFTER
12836

changed /files/skills~1yaps-text-to-speech~1SKILL.md/sha256

BEFORE
"2444c498c34d00508d4e5c4c95ad775d417034d813d909847c2dcd981c72eeab"
AFTER
"91b3f7cfb4d837fa1efc9636d433498fa5871b4503b8525439d512a68f724e6a"

changed /files/skills~1yaps-text-to-speech~1SKILL.md/size

BEFORE
14082
AFTER
14260
Full snapshot data
{
  "files": {
    ".claude-plugin/plugin.json": {
      "sha256": "47101c8cadd537823635774c3953dfb8663f60e5d5c5bc355d066829fd8deaf5",
      "size": 493
    },
    ".codex-plugin/plugin.json": {
      "sha256": "cffdfe739409db857808fb24108fbffcf14f597f23e2af89aab06d6bdfe6aab7",
      "size": 2243
    },
    "README.md": {
      "sha256": "c85956e7bc06e2a9c93eda61db445714845347f9db4a050df182b8369bb60f82",
      "size": 1715
    },
    "assets/yaps-icon.png": {
      "sha256": "d693f4e16e2a73d7f8765d97642fa8f01c2fbb8e29bb383fcf1ecd797afb0b18",
      "size": 7865
    },
    "plugin.json": {
      "sha256": "a42c88528cec093da9384b09460404c5868d1e226b00c32b6396e7a187b4e75c",
      "size": 2453
    },
    "scripts/yaps-cli-discovery.mjs": {
      "sha256": "1d3808134d03a4d19f2b2eac3acea2d6af4b7538bcc0da84afe43bce09eca257",
      "size": 50423
    },
    "scripts/yaps-plugin-runner.mjs": {
      "sha256": "af9fc263e016cb959379f68fea158891d65e774b0457ed475f0a156f12ce34c9",
      "size": 12836
    },
    "skills/yaps-text-to-speech/SKILL.md": {
      "sha256": "91b3f7cfb4d837fa1efc9636d433498fa5871b4503b8525439d512a68f724e6a",
      "size": 14260
    },
    "skills/yaps-text-to-speech/agents/openai.yaml": {
      "sha256": "eec36e2986965a6d1aa2745d193d649b459a749a5a84c90c5237587034a2e7cd",
      "size": 199
    }
  }
}

SHA-256 of public snapshot: 1fcc8f74956bd90163fae1c5585f757ce39ffcdc75c5ed7adb07dac9d22f626b