← 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-plugin-runner.mjs

− removed · + added

--- before
+++ after
@@ -106,10 +106,17 @@
   if (value.includes("enoent") || value.includes("local_yaps_unreachable")) return "local_yaps_unreachable";
   if (value.includes("keychain_unavailable")) return "credential_unavailable";
   if (value.includes("verification_unavailable") || value.includes("refresh_failed")) return "account_cache_incomplete";
-  for (const code of ["settings_path_mismatch", "credential_unavailable", "credential_missing", "account_cache_incomplete", "profile_lookup_failed", "platform_mismatch", "unauthenticated"]) {
+  for (const code of ["settings_path_mismatch", "desktop_account_unavailable", "credential_unavailable", "credential_missing", "account_cache_incomplete", "profile_lookup_failed", "platform_mismatch", "unauthenticated"]) {
     if (value.includes(code)) return code;
   }
-  if (value.includes("permission") || value.includes("denied")) return "permission_denied";
+  if (
+    value.includes("permission") ||
+    value.includes("denied") ||
+    value.includes("eperm") ||
+    value.includes("eacces") ||
+    value.includes("operation not permitted") ||
+    value.includes("not permitted")
+  ) return "permission_denied";
   if (value.includes("model") && (value.includes("unavailable") || value.includes("not found"))) return "model_unavailable";
   if (value.includes("network") || value.includes("offline") || value.includes("econn")) return "network_unavailable";
   if (value.includes("timeout") || value.includes("timed out")) return "timeout";
@@ -119,6 +126,16 @@
   return "unknown";
 }
 
+// Yaps CLI exit codes (documented in `yaps --help`): 2 usage, 3 not_found,
+// 130 cancelled. Text classification wins when it is more specific.
+function classifyExit(code, output, launchError, yapsCli) {
+  const fromText = classifyError(output, launchError);
+  if (fromText !== "unknown" || !yapsCli) return fromText;
+  if (code === 2) return "invalid_input";
+  if (code === 3) return "not_found";
+  return "unknown";
+}
+
 function pruneInbox(inbox) {
   try {
     const entries = readdirSync(inbox)
@@ -177,10 +194,54 @@
 const context = ownerKey && identity ? { root, ownerKey, identity, host, action: parsed.action, stage: parsed.stage } : null;
 const operationId = randomUUID();
 const started = Date.now();
+let terminalWritten = false;
+let cancelled = false;
+let child = null;
+
+function finishOnce(status, errorCode = null) {
+  if (terminalWritten) return false;
+  terminalWritten = true;
+  writeEvent(context, status, operationId, errorCode, Date.now() - started);
+  return true;
+}
+
 writeEvent(context, "attempt", operationId);
 
+// Reasons and thrown values can contain customer content, paths, or command
+// output. Deliberately ignore them and persist only the reviewed `unknown`
+// diagnostic. `uncaughtExceptionMonitor` observes without changing Node's
+// normal fatal-exception behavior.
+process.once("uncaughtExceptionMonitor", () => {
+  finishOnce("failure", "unknown");
+});
+process.once("unhandledRejection", () => {
+  finishOnce("failure", "unknown");
+  process.exitCode = 1;
+});
+
+// Node runs `exit` listeners synchronously. This is the last cooperative
+// boundary for a runner that reaches process teardown without a child `close`
+// callback. It cannot run for SIGKILL, Windows TerminateProcess, power loss, or
+// an OS crash.
+process.once("exit", () => {
+  finishOnce(cancelled ? "cancelled" : "failure", cancelled ? "cancelled" : "unknown");
+});
+
+for (const signal of ["SIGINT", "SIGTERM"]) {
+  process.once(signal, () => {
+    cancelled = true;
+    finishOnce("cancelled", "cancelled");
+    if (child) {
+      try { child.kill(signal); } catch {}
+    } else {
+      process.exit(signal === "SIGINT" ? 130 : 143);
+    }
+  });
+}
+
 let [command, ...commandArguments] = parsed.command;
-if (isYapsCliCommand(command)) {
+const runsYapsCli = isYapsCliCommand(command);
+if (runsYapsCli) {
   const commandIsPath = command !== basename(command);
   const accountPreflight = isAuthStatusCommand(commandArguments);
   const requiresActiveAccount = commandRequiresActiveAccount(commandArguments);
@@ -191,14 +252,14 @@
   });
   if (!session.path) {
     const failure = classifyCliResolutionFailure(session);
-    writeEvent(context, "failure", operationId, failure.code, Date.now() - started);
+    finishOnce("failure", failure.code);
     process.stderr.write(`${failure.message}\n`);
     process.exit(failure.exitCode);
   }
   if (accountPreflight) {
     const account = diagnoseAccount(session);
     if (session.authStatusSafety !== "safe" || !session.auth) {
-      writeEvent(context, "failure", operationId, account.code, Date.now() - started);
+      finishOnce("failure", account.code);
       process.stderr.write(`${account.message}\n`);
       process.exit(78);
     }
@@ -208,36 +269,35 @@
       diagnostic_code: session.auth.diagnosticCode,
       credential_status: "not_accessed",
     };
-    writeEvent(context, "success", operationId, null, Date.now() - started);
+    if (account.code === "ready") finishOnce("success");
+    else finishOnce("failure", account.code);
     process.stdout.write(`${JSON.stringify(sanitized, null, commandArguments.includes("--pretty") ? 2 : 0)}\n`);
     process.exit(0);
   }
   if (requiresActiveAccount) {
     const account = diagnoseAccount(session);
     if (account.code !== "ready") {
-      writeEvent(context, "failure", operationId, account.code, Date.now() - started);
+      finishOnce("failure", account.code);
       process.stderr.write(`${account.message}\n`);
       process.exit(77);
     }
   }
   command = session.path;
-  commandArguments = applyResolvedSettings(commandArguments, session.settingsPath);
+  commandArguments = applyResolvedSettings(commandArguments, session.settingsPath, {
+    followDesktopAccount: session.auth?.diagnosticCode === "followed_desktop_account",
+  });
 }
 
 let outputTail = Buffer.alloc(0);
 let launchError = null;
-let cancelled = false;
-const child = spawn(command, commandArguments, { env: process.env, stdio: ["inherit", "pipe", "pipe"], windowsHide: true });
+child = spawn(command, commandArguments, { env: process.env, stdio: ["inherit", "pipe", "pipe"], windowsHide: true });
 child.stdout.on("data", (chunk) => { outputTail = appendTail(outputTail, chunk); process.stdout.write(chunk); });
 child.stderr.on("data", (chunk) => { outputTail = appendTail(outputTail, chunk); process.stderr.write(chunk); });
 child.on("error", (error) => { launchError = error; });
-for (const signal of ["SIGINT", "SIGTERM"]) {
-  process.on(signal, () => { cancelled = true; try { child.kill(signal); } catch {} });
-}
 child.on("close", (code, signal) => {
-  const duration = Date.now() - started;
-  if (cancelled || signal) writeEvent(context, "cancelled", operationId, "cancelled", duration);
-  else if (code === 0) writeEvent(context, "success", operationId, null, duration);
-  else writeEvent(context, "failure", operationId, classifyError(outputTail.toString("utf8"), launchError), duration);
-  process.exitCode = code ?? (signal ? 1 : 127);
+  // A Yaps CLI stopped by Ctrl-C/SIGTERM (or `jobs cancel`) cleans up and exits 130.
+  if (cancelled || signal || code === 130) finishOnce("cancelled", "cancelled");
+  else if (code === 0) finishOnce("success");
+  else finishOnce("failure", classifyExit(code, outputTail.toString("utf8"), launchError, runsYapsCli));
+  process.exitCode ??= code ?? (signal ? 1 : 127);
 });
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