ai-whisper 0.5.8 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5951,7 +5951,7 @@ function createLiveSessionRuntime(input) {
5951
5951
  input.interactiveSession.sendLocalMessage(CLEAR_LINE2);
5952
5952
  }
5953
5953
  let inputLineBuffer = "";
5954
- function feedLineBufferedInput(data) {
5954
+ async function feedLineBufferedInput(data) {
5955
5955
  for (const char of data) {
5956
5956
  if (char === "") {
5957
5957
  if (inputLineBuffer.length > 0) {
@@ -5970,7 +5970,7 @@ function createLiveSessionRuntime(input) {
5970
5970
  const completed = inputLineBuffer;
5971
5971
  inputLineBuffer = "";
5972
5972
  const session = input.interactiveSession;
5973
- if (completed.length > 0 && session.echoUserInput) {
5973
+ if (completed.length > 0) {
5974
5974
  const cols = ttyCols(input.stdout);
5975
5975
  const len = Array.from(completed).length;
5976
5976
  const plainRows = Math.max(1, Math.ceil((STRIPE_COLS + len) / cols));
@@ -5978,13 +5978,18 @@ function createLiveSessionRuntime(input) {
5978
5978
  if (plainRows > 1) erase += `\x1B[${plainRows - 1}A`;
5979
5979
  erase += "\x1B[J";
5980
5980
  input.stdout.write(erase);
5981
- session.echoUserInput(completed, cols);
5981
+ if (await session.tryConsumeLocalCommand?.(completed)) {
5982
+ continue;
5983
+ }
5984
+ if (session.echoUserInput) {
5985
+ session.echoUserInput(completed, cols);
5986
+ } else {
5987
+ input.stdout.write("\n");
5988
+ }
5989
+ input.interactiveSession.writeUserInput(completed);
5982
5990
  } else {
5983
5991
  input.stdout.write("\n");
5984
5992
  }
5985
- if (completed.length > 0) {
5986
- input.interactiveSession.writeUserInput(completed);
5987
- }
5988
5993
  continue;
5989
5994
  }
5990
5995
  if (char === "\b" || char === "\x7F") {
@@ -6054,7 +6059,7 @@ function createLiveSessionRuntime(input) {
6054
6059
  clearRelayPreview();
6055
6060
  if (decision.kind === "passthrough") {
6056
6061
  if (input.lineBufferedInput) {
6057
- feedLineBufferedInput(decision.data);
6062
+ await feedLineBufferedInput(decision.data);
6058
6063
  } else {
6059
6064
  input.interactiveSession.writeUserInput(decision.data);
6060
6065
  }
@@ -8596,6 +8601,360 @@ ${RED}\u258C ${event.message}${RESET2}`);
8596
8601
  };
8597
8602
  }
8598
8603
 
8604
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/skills.js
8605
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
8606
+ import { join as join12 } from "node:path";
8607
+ function configBase(env) {
8608
+ return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join12(env.home, ".config");
8609
+ }
8610
+ function skillDirs(env) {
8611
+ const base = configBase(env);
8612
+ return [
8613
+ { source: "project", path: join12(env.cwd, ".agents", "skills"), engineVisible: true },
8614
+ {
8615
+ source: "ai-ezio-global",
8616
+ path: join12(base, "ai-ezio", "skills"),
8617
+ // Engine-visible as of M4: ai-ezio sets HAX_EXTRA_SKILLS_DIR to this dir
8618
+ // on launch, so hax injects these skills into the model prompt.
8619
+ engineVisible: true
8620
+ },
8621
+ { source: "hax-global", path: join12(base, "hax", "skills"), engineVisible: true }
8622
+ ];
8623
+ }
8624
+ function parseSkillDescription(md) {
8625
+ const text = md.replace(/^/, "");
8626
+ if (!/^---\r?\n/.test(text))
8627
+ return null;
8628
+ const end = text.indexOf("\n---", 4);
8629
+ if (end === -1)
8630
+ return null;
8631
+ const front = text.slice(text.indexOf("\n") + 1, end);
8632
+ for (const raw of front.split(/\r?\n/)) {
8633
+ const m = raw.match(/^description:\s*(.*)$/);
8634
+ if (m) {
8635
+ let value = (m[1] ?? "").trim();
8636
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8637
+ value = value.slice(1, -1);
8638
+ }
8639
+ return value.length > 0 ? value : null;
8640
+ }
8641
+ }
8642
+ return null;
8643
+ }
8644
+ function discoverSkills(env, fs2) {
8645
+ const byName = /* @__PURE__ */ new Map();
8646
+ for (const dir of skillDirs(env)) {
8647
+ if (!fs2.isDirectory(dir.path))
8648
+ continue;
8649
+ for (const name of fs2.listDirs(dir.path)) {
8650
+ if (byName.has(name))
8651
+ continue;
8652
+ const skillMdPath = join12(dir.path, name, "SKILL.md");
8653
+ const md = fs2.readFile(skillMdPath);
8654
+ if (md === null)
8655
+ continue;
8656
+ byName.set(name, {
8657
+ name,
8658
+ description: parseSkillDescription(md),
8659
+ skillMdPath,
8660
+ source: dir.source,
8661
+ engineVisible: dir.engineVisible
8662
+ });
8663
+ }
8664
+ }
8665
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
8666
+ }
8667
+ function nodeSkillFs() {
8668
+ return {
8669
+ isDirectory: (path) => {
8670
+ try {
8671
+ return statSync3(path).isDirectory();
8672
+ } catch {
8673
+ return false;
8674
+ }
8675
+ },
8676
+ listDirs: (path) => {
8677
+ try {
8678
+ return readdirSync2(path, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
8679
+ } catch {
8680
+ return [];
8681
+ }
8682
+ },
8683
+ readFile: (path) => {
8684
+ try {
8685
+ return readFileSync4(path, "utf8");
8686
+ } catch {
8687
+ return null;
8688
+ }
8689
+ }
8690
+ };
8691
+ }
8692
+
8693
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/clipboard.js
8694
+ import { spawn as spawn6 } from "node:child_process";
8695
+ function tryCopy(argv, text, spawnFn) {
8696
+ return new Promise((resolve2, reject) => {
8697
+ const child = spawnFn(argv[0], argv.slice(1), { stdio: ["pipe", "ignore", "ignore"] });
8698
+ child.on("error", reject);
8699
+ child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${argv[0]} exited ${code}`)));
8700
+ child.stdin?.end(text);
8701
+ });
8702
+ }
8703
+ function makeClipboard(platform, spawnFn = spawn6) {
8704
+ const candidates = platform === "darwin" ? [["pbcopy"]] : [["wl-copy"], ["xclip", "-selection", "clipboard"]];
8705
+ return async (text) => {
8706
+ let lastErr = new Error("no clipboard tool available");
8707
+ for (const argv of candidates) {
8708
+ try {
8709
+ await tryCopy(argv, text, spawnFn);
8710
+ return;
8711
+ } catch (e) {
8712
+ lastErr = e;
8713
+ }
8714
+ }
8715
+ throw lastErr;
8716
+ };
8717
+ }
8718
+
8719
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/transcript-view.js
8720
+ import { join as join13 } from "node:path";
8721
+ async function showTranscript(deps) {
8722
+ const text = deps.path ? deps.readText(deps.path) : void 0;
8723
+ if (text === void 0 || text === "") {
8724
+ deps.write("\x1B[2m\u2500 no transcript yet \u2500\x1B[0m\n");
8725
+ return;
8726
+ }
8727
+ if (!deps.interactive) {
8728
+ deps.write(text.endsWith("\n") ? text : `${text}
8729
+ `);
8730
+ return;
8731
+ }
8732
+ deps.suspendRaw();
8733
+ try {
8734
+ await deps.spawnPager(deps.path);
8735
+ } catch {
8736
+ deps.write(text.endsWith("\n") ? text : `${text}
8737
+ `);
8738
+ } finally {
8739
+ deps.restoreRaw();
8740
+ }
8741
+ }
8742
+
8743
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/slash.js
8744
+ var NAME_RE = /^[a-zA-Z][\w-]*$/;
8745
+ function classifyLine(line, known) {
8746
+ if (!line.startsWith("/"))
8747
+ return { kind: "submit" };
8748
+ if (line.includes("\n"))
8749
+ return { kind: "submit" };
8750
+ const body = line.slice(1);
8751
+ const ws = body.search(/\s/);
8752
+ const rawName = ws === -1 ? body : body.slice(0, ws);
8753
+ const args = ws === -1 ? "" : body.slice(ws).trim();
8754
+ if (rawName === "")
8755
+ return { kind: "submit" };
8756
+ if (!NAME_RE.test(rawName))
8757
+ return { kind: "submit" };
8758
+ const name = rawName.toLowerCase();
8759
+ return known.has(name) ? { kind: "command", name, args } : { kind: "unknown", name };
8760
+ }
8761
+ function renderHelp(ctx, cmds) {
8762
+ for (const c of cmds)
8763
+ ctx.write(` /${c.name} ${c.summary}
8764
+ `);
8765
+ ctx.write("\nshortcuts: Enter submit \xB7 Alt+Enter newline \xB7 paste multiline \xB7 Ctrl-C interrupt \xB7 Ctrl-D exit\n");
8766
+ }
8767
+ function formatUsage(u) {
8768
+ if (!u)
8769
+ return null;
8770
+ const parts = [];
8771
+ if (u.contextTokens !== void 0)
8772
+ parts.push(`context ${u.contextTokens}`);
8773
+ if (u.outputTokens !== void 0)
8774
+ parts.push(`output ${u.outputTokens}`);
8775
+ if (u.cachedTokens !== void 0)
8776
+ parts.push(`cached ${u.cachedTokens}`);
8777
+ if (u.contextLimit !== void 0)
8778
+ parts.push(`limit ${u.contextLimit}`);
8779
+ if (u.contextTokens !== void 0 && u.contextLimit !== void 0 && u.contextLimit > 0) {
8780
+ parts.push(`${Math.round(u.contextTokens / u.contextLimit * 100)}%`);
8781
+ }
8782
+ return parts.length ? parts.join(" \xB7 ") : null;
8783
+ }
8784
+ function builtinCommands(listCommands) {
8785
+ return [
8786
+ {
8787
+ name: "help",
8788
+ summary: "list commands and keyboard shortcuts",
8789
+ run: (ctx) => renderHelp(ctx, listCommands())
8790
+ },
8791
+ {
8792
+ name: "new",
8793
+ aliases: ["clear"],
8794
+ summary: "start a new conversation",
8795
+ run: async (ctx) => {
8796
+ ctx.recorder?.noteNewConversation();
8797
+ await ctx.session.newConversation();
8798
+ ctx.write("\u2014 new conversation \u2014\n");
8799
+ }
8800
+ },
8801
+ {
8802
+ name: "status",
8803
+ summary: "show provider, model, and effort",
8804
+ run: async (ctx) => {
8805
+ const s = await ctx.session.status();
8806
+ const effort = s.effort ? ` \xB7 ${s.effort}` : "";
8807
+ ctx.write(`${s.provider} \xB7 ${s.model}${effort}
8808
+ `);
8809
+ }
8810
+ },
8811
+ {
8812
+ name: "skills",
8813
+ summary: "list discovered skills",
8814
+ run: (ctx) => {
8815
+ const skills = ctx.skills();
8816
+ if (skills.length === 0) {
8817
+ ctx.write("(no skills found)\n");
8818
+ return;
8819
+ }
8820
+ for (const s of skills)
8821
+ ctx.write(` ${s.name} \xB7 ${s.source}
8822
+ `);
8823
+ }
8824
+ },
8825
+ {
8826
+ name: "copy",
8827
+ summary: "copy the last response to the clipboard",
8828
+ run: async (ctx) => {
8829
+ const text = ctx.lastContent();
8830
+ if (text === "") {
8831
+ ctx.write("no response to copy\n");
8832
+ return;
8833
+ }
8834
+ try {
8835
+ await ctx.clipboard(text);
8836
+ ctx.write(`copied ${Buffer.byteLength(text, "utf8")} bytes
8837
+ `);
8838
+ } catch (e) {
8839
+ ctx.write(`clipboard unavailable: ${e.message}
8840
+ `);
8841
+ }
8842
+ }
8843
+ },
8844
+ {
8845
+ name: "usage",
8846
+ summary: "show the last turn's token usage",
8847
+ run: (ctx) => {
8848
+ const formatted = formatUsage(ctx.lastUsage());
8849
+ ctx.write(formatted ? `${formatted}
8850
+ ` : "no usage yet\n");
8851
+ }
8852
+ },
8853
+ {
8854
+ name: "transcript",
8855
+ summary: "view the model-perspective transcript (same as Ctrl+T)",
8856
+ run: async (ctx) => {
8857
+ if (!ctx.showTranscript) {
8858
+ ctx.write("transcript unavailable\n");
8859
+ return;
8860
+ }
8861
+ await ctx.showTranscript();
8862
+ }
8863
+ },
8864
+ {
8865
+ name: "compact",
8866
+ summary: "summarize old history and free context",
8867
+ run: async (ctx) => {
8868
+ if (!ctx.compactor) {
8869
+ ctx.write("compaction unavailable\n");
8870
+ return;
8871
+ }
8872
+ const out = await ctx.compactor.compactNow();
8873
+ if (out.kind === "skipped" && out.reason === "in-progress") {
8874
+ ctx.write("compaction already in progress\n");
8875
+ }
8876
+ }
8877
+ },
8878
+ {
8879
+ name: "quit",
8880
+ aliases: ["exit"],
8881
+ summary: "exit ezio",
8882
+ run: () => {
8883
+ }
8884
+ // the controller maps /quit to the exit outcome before run()
8885
+ }
8886
+ ];
8887
+ }
8888
+ var SlashController = class {
8889
+ ctx;
8890
+ /** name OR alias → command (so classifyLine's `known` set is keys()). */
8891
+ byKey = /* @__PURE__ */ new Map();
8892
+ constructor(ctx, opts) {
8893
+ this.ctx = ctx;
8894
+ const exclude = new Set(opts?.excludeCommands ?? []);
8895
+ for (const cmd of builtinCommands(() => this.summaries())) {
8896
+ if (exclude.has(cmd.name))
8897
+ continue;
8898
+ this.register(cmd);
8899
+ }
8900
+ }
8901
+ /** Register (or override) a command and its aliases. Last registration wins
8902
+ * per key: any command that already owns this command's NAME is fully evicted
8903
+ * (all of its keys removed) so an override replaces it cleanly; an alias key
8904
+ * collision is resolved key-by-key (the alias now points at the new command,
8905
+ * but the prior owner keeps its other keys). */
8906
+ register(cmd) {
8907
+ const displaced = this.byKey.get(cmd.name);
8908
+ if (displaced) {
8909
+ for (const [k, v] of this.byKey)
8910
+ if (v === displaced)
8911
+ this.byKey.delete(k);
8912
+ }
8913
+ this.byKey.set(cmd.name, cmd);
8914
+ for (const a of cmd.aliases ?? [])
8915
+ this.byKey.set(a, cmd);
8916
+ }
8917
+ /** Deduped canonical command list for /help. A command is listed only if it
8918
+ * still OWNS its own name key — this filters out any command reachable only
8919
+ * through a stolen alias key (e.g. another command claimed its name), so
8920
+ * /help never shows a stale entry. */
8921
+ summaries() {
8922
+ const seen = /* @__PURE__ */ new Set();
8923
+ const out = [];
8924
+ for (const cmd of this.byKey.values()) {
8925
+ if (seen.has(cmd))
8926
+ continue;
8927
+ seen.add(cmd);
8928
+ if (this.byKey.get(cmd.name) !== cmd)
8929
+ continue;
8930
+ out.push({ name: cmd.name, summary: cmd.summary });
8931
+ }
8932
+ return out;
8933
+ }
8934
+ async handle(line) {
8935
+ const c = classifyLine(line, new Set(this.byKey.keys()));
8936
+ if (c.kind === "submit")
8937
+ return { action: "submit", text: line };
8938
+ if (c.kind === "unknown") {
8939
+ this.ctx.write(`unknown command: /${c.name}. type /help for the list.
8940
+ `);
8941
+ return { action: "handled" };
8942
+ }
8943
+ const cmd = this.byKey.get(c.name);
8944
+ if (!cmd)
8945
+ return { action: "handled" };
8946
+ if (cmd.name === "quit")
8947
+ return { action: "exit" };
8948
+ try {
8949
+ await cmd.run(this.ctx, c.args);
8950
+ } catch (e) {
8951
+ this.ctx.write(`/${c.name} failed: ${e.message}
8952
+ `);
8953
+ }
8954
+ return { action: "handled" };
8955
+ }
8956
+ };
8957
+
8599
8958
  // ../adapter-ai-ezio/dist/mid-composition-shape.js
8600
8959
  var DRAFTING_PREFIXES = [
8601
8960
  "let's draft",
@@ -8632,6 +8991,11 @@ function isMidCompositionShape(message) {
8632
8991
  }
8633
8992
 
8634
8993
  // ../adapter-ai-ezio/dist/create-ai-ezio-live-session.js
8994
+ import { spawn as spawn7 } from "node:child_process";
8995
+ import { randomUUID as randomUUID3 } from "node:crypto";
8996
+ import { readFileSync as readFileSync5 } from "node:fs";
8997
+ import { tmpdir } from "node:os";
8998
+ import { join as join14 } from "node:path";
8635
8999
  var defaultBuildAutoCompact = ({ session, host, write }) => {
8636
9000
  const { compaction } = loadConfig();
8637
9001
  return createAutoCompactDriver({
@@ -8653,6 +9017,9 @@ function createAiEzioLiveSession(input) {
8653
9017
  let driver = null;
8654
9018
  let sawTurn = false;
8655
9019
  let pendingContent = null;
9020
+ let lastContent = "";
9021
+ let lastUsage;
9022
+ let slash = null;
8656
9023
  const outputHandlers = [];
8657
9024
  const turnFinishedHandlers = [];
8658
9025
  const fidelityDecisionHandlers = [];
@@ -8669,6 +9036,8 @@ function createAiEzioLiveSession(input) {
8669
9036
  break;
8670
9037
  case "assistant_turn_finished":
8671
9038
  sawTurn = true;
9039
+ lastContent = event.content;
9040
+ lastUsage = event.usage;
8672
9041
  if (pendingContent !== null) {
8673
9042
  const superseded = pendingContent;
8674
9043
  for (const h of fidelityDecisionHandlers)
@@ -8722,8 +9091,47 @@ function createAiEzioLiveSession(input) {
8722
9091
  for (const h of exitHandlers)
8723
9092
  h();
8724
9093
  });
8725
- await session.start();
9094
+ const transcriptPath = join14(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
9095
+ await session.start({ transcriptPath });
8726
9096
  await host.start(session);
9097
+ const skillEnv = {
9098
+ cwd: process.cwd(),
9099
+ home: process.env.HOME ?? "",
9100
+ xdgConfigHome: process.env.XDG_CONFIG_HOME
9101
+ };
9102
+ const skillFs = nodeSkillFs();
9103
+ const sessionFacet = session;
9104
+ const slashCtx = {
9105
+ write: (s) => input.stdout.write(s),
9106
+ session: sessionFacet,
9107
+ ...driver ? { compactor: { compactNow: () => driver.compactNow() } } : {},
9108
+ lastContent: () => lastContent,
9109
+ lastUsage: () => lastUsage,
9110
+ skills: () => discoverSkills(skillEnv, skillFs).map((s) => ({
9111
+ name: s.name,
9112
+ source: s.source,
9113
+ description: s.description
9114
+ })),
9115
+ clipboard: input.clipboard ?? makeClipboard(process.platform, spawn7),
9116
+ showTranscript: () => showTranscript({
9117
+ path: sessionFacet.transcriptPath ?? "",
9118
+ readText: (p) => {
9119
+ try {
9120
+ return readFileSync5(p, "utf8");
9121
+ } catch {
9122
+ return void 0;
9123
+ }
9124
+ },
9125
+ interactive: false,
9126
+ spawnPager: () => Promise.resolve(),
9127
+ suspendRaw: () => {
9128
+ },
9129
+ restoreRaw: () => {
9130
+ },
9131
+ write: (s) => input.stdout.write(s)
9132
+ })
9133
+ };
9134
+ slash = new SlashController(slashCtx, { excludeCommands: ["quit"] });
8727
9135
  },
8728
9136
  async stop() {
8729
9137
  await host.stop();
@@ -8736,6 +9144,12 @@ function createAiEzioLiveSession(input) {
8736
9144
  interrupt() {
8737
9145
  session?.interrupt();
8738
9146
  },
9147
+ async tryConsumeLocalCommand(line) {
9148
+ if (!slash)
9149
+ return false;
9150
+ const outcome = await slash.handle(line);
9151
+ return outcome.action !== "submit";
9152
+ },
8739
9153
  echoUserInput(text, cols) {
8740
9154
  renderer.echoUserInput(text, cols);
8741
9155
  },
@@ -8047,11 +8047,25 @@ function codexNotifyArgs(input) {
8047
8047
  ]);
8048
8048
  return ["-c", `notify=${notify}`];
8049
8049
  }
8050
+ var RECOGNIZED_TURN_EVENTS_TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "off", "none"]);
8051
+ function parseTurnEventsTokens(raw) {
8052
+ return raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
8053
+ }
8050
8054
  function resolveTurnEvents(flag) {
8051
- const source = flag ?? process.env["AI_WHISPER_TURN_EVENTS"] ?? "";
8052
- const set = new Set(source.split(",").map((s) => s.trim()).filter(Boolean));
8055
+ const raw = flag ?? process.env["AI_WHISPER_TURN_EVENTS"];
8056
+ if (raw === void 0) return { claude: true, codex: true };
8057
+ const set = new Set(parseTurnEventsTokens(raw));
8058
+ if (set.has("off") || set.has("none")) return { claude: false, codex: false };
8053
8059
  return { claude: set.has("claude"), codex: set.has("codex") };
8054
8060
  }
8061
+ function unrecognizedTurnEventsTokens(flag) {
8062
+ const raw = flag ?? process.env["AI_WHISPER_TURN_EVENTS"];
8063
+ if (raw === void 0) return [];
8064
+ const unknown = parseTurnEventsTokens(raw).filter(
8065
+ (t) => !RECOGNIZED_TURN_EVENTS_TOKENS.has(t)
8066
+ );
8067
+ return [...new Set(unknown)];
8068
+ }
8055
8069
  function formatTurnEventsStartupLine(e) {
8056
8070
  const on = (b) => b ? "ON" : "off";
8057
8071
  return `[ai-whisper] turn-events: claude=${on(e.claude)} codex=${on(e.codex)} (codex notify-chaining: off)`;
@@ -8605,7 +8619,7 @@ function createLiveSessionRuntime(input) {
8605
8619
  input.interactiveSession.sendLocalMessage(CLEAR_LINE2);
8606
8620
  }
8607
8621
  let inputLineBuffer = "";
8608
- function feedLineBufferedInput(data) {
8622
+ async function feedLineBufferedInput(data) {
8609
8623
  for (const char of data) {
8610
8624
  if (char === "") {
8611
8625
  if (inputLineBuffer.length > 0) {
@@ -8624,7 +8638,7 @@ function createLiveSessionRuntime(input) {
8624
8638
  const completed = inputLineBuffer;
8625
8639
  inputLineBuffer = "";
8626
8640
  const session = input.interactiveSession;
8627
- if (completed.length > 0 && session.echoUserInput) {
8641
+ if (completed.length > 0) {
8628
8642
  const cols = ttyCols(input.stdout);
8629
8643
  const len = Array.from(completed).length;
8630
8644
  const plainRows = Math.max(1, Math.ceil((STRIPE_COLS + len) / cols));
@@ -8632,13 +8646,18 @@ function createLiveSessionRuntime(input) {
8632
8646
  if (plainRows > 1) erase += `\x1B[${plainRows - 1}A`;
8633
8647
  erase += "\x1B[J";
8634
8648
  input.stdout.write(erase);
8635
- session.echoUserInput(completed, cols);
8649
+ if (await session.tryConsumeLocalCommand?.(completed)) {
8650
+ continue;
8651
+ }
8652
+ if (session.echoUserInput) {
8653
+ session.echoUserInput(completed, cols);
8654
+ } else {
8655
+ input.stdout.write("\n");
8656
+ }
8657
+ input.interactiveSession.writeUserInput(completed);
8636
8658
  } else {
8637
8659
  input.stdout.write("\n");
8638
8660
  }
8639
- if (completed.length > 0) {
8640
- input.interactiveSession.writeUserInput(completed);
8641
- }
8642
8661
  continue;
8643
8662
  }
8644
8663
  if (char === "\b" || char === "\x7F") {
@@ -8708,7 +8727,7 @@ function createLiveSessionRuntime(input) {
8708
8727
  clearRelayPreview();
8709
8728
  if (decision.kind === "passthrough") {
8710
8729
  if (input.lineBufferedInput) {
8711
- feedLineBufferedInput(decision.data);
8730
+ await feedLineBufferedInput(decision.data);
8712
8731
  } else {
8713
8732
  input.interactiveSession.writeUserInput(decision.data);
8714
8733
  }
@@ -8803,6 +8822,11 @@ function createLiveSessionRuntime(input) {
8803
8822
  };
8804
8823
  }
8805
8824
 
8825
+ // src/runtime/injected-input.ts
8826
+ function injectedWrite(session, value) {
8827
+ session.writeUserInput(value);
8828
+ }
8829
+
8806
8830
  // ../companion-core/dist/create-companion-runtime.js
8807
8831
  function createCompanionRuntime(input) {
8808
8832
  let sessionSecret = null;
@@ -11264,6 +11288,360 @@ ${RED}\u258C ${event.message}${RESET2}`);
11264
11288
  };
11265
11289
  }
11266
11290
 
11291
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/skills.js
11292
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
11293
+ import { join as join15 } from "node:path";
11294
+ function configBase(env) {
11295
+ return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join15(env.home, ".config");
11296
+ }
11297
+ function skillDirs(env) {
11298
+ const base = configBase(env);
11299
+ return [
11300
+ { source: "project", path: join15(env.cwd, ".agents", "skills"), engineVisible: true },
11301
+ {
11302
+ source: "ai-ezio-global",
11303
+ path: join15(base, "ai-ezio", "skills"),
11304
+ // Engine-visible as of M4: ai-ezio sets HAX_EXTRA_SKILLS_DIR to this dir
11305
+ // on launch, so hax injects these skills into the model prompt.
11306
+ engineVisible: true
11307
+ },
11308
+ { source: "hax-global", path: join15(base, "hax", "skills"), engineVisible: true }
11309
+ ];
11310
+ }
11311
+ function parseSkillDescription(md) {
11312
+ const text = md.replace(/^/, "");
11313
+ if (!/^---\r?\n/.test(text))
11314
+ return null;
11315
+ const end = text.indexOf("\n---", 4);
11316
+ if (end === -1)
11317
+ return null;
11318
+ const front = text.slice(text.indexOf("\n") + 1, end);
11319
+ for (const raw of front.split(/\r?\n/)) {
11320
+ const m = raw.match(/^description:\s*(.*)$/);
11321
+ if (m) {
11322
+ let value = (m[1] ?? "").trim();
11323
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
11324
+ value = value.slice(1, -1);
11325
+ }
11326
+ return value.length > 0 ? value : null;
11327
+ }
11328
+ }
11329
+ return null;
11330
+ }
11331
+ function discoverSkills(env, fs2) {
11332
+ const byName = /* @__PURE__ */ new Map();
11333
+ for (const dir of skillDirs(env)) {
11334
+ if (!fs2.isDirectory(dir.path))
11335
+ continue;
11336
+ for (const name of fs2.listDirs(dir.path)) {
11337
+ if (byName.has(name))
11338
+ continue;
11339
+ const skillMdPath = join15(dir.path, name, "SKILL.md");
11340
+ const md = fs2.readFile(skillMdPath);
11341
+ if (md === null)
11342
+ continue;
11343
+ byName.set(name, {
11344
+ name,
11345
+ description: parseSkillDescription(md),
11346
+ skillMdPath,
11347
+ source: dir.source,
11348
+ engineVisible: dir.engineVisible
11349
+ });
11350
+ }
11351
+ }
11352
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
11353
+ }
11354
+ function nodeSkillFs() {
11355
+ return {
11356
+ isDirectory: (path4) => {
11357
+ try {
11358
+ return statSync3(path4).isDirectory();
11359
+ } catch {
11360
+ return false;
11361
+ }
11362
+ },
11363
+ listDirs: (path4) => {
11364
+ try {
11365
+ return readdirSync3(path4, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
11366
+ } catch {
11367
+ return [];
11368
+ }
11369
+ },
11370
+ readFile: (path4) => {
11371
+ try {
11372
+ return readFileSync5(path4, "utf8");
11373
+ } catch {
11374
+ return null;
11375
+ }
11376
+ }
11377
+ };
11378
+ }
11379
+
11380
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/clipboard.js
11381
+ import { spawn as spawn7 } from "node:child_process";
11382
+ function tryCopy(argv2, text, spawnFn) {
11383
+ return new Promise((resolve5, reject) => {
11384
+ const child = spawnFn(argv2[0], argv2.slice(1), { stdio: ["pipe", "ignore", "ignore"] });
11385
+ child.on("error", reject);
11386
+ child.on("close", (code) => code === 0 ? resolve5() : reject(new Error(`${argv2[0]} exited ${code}`)));
11387
+ child.stdin?.end(text);
11388
+ });
11389
+ }
11390
+ function makeClipboard(platform, spawnFn = spawn7) {
11391
+ const candidates = platform === "darwin" ? [["pbcopy"]] : [["wl-copy"], ["xclip", "-selection", "clipboard"]];
11392
+ return async (text) => {
11393
+ let lastErr = new Error("no clipboard tool available");
11394
+ for (const argv2 of candidates) {
11395
+ try {
11396
+ await tryCopy(argv2, text, spawnFn);
11397
+ return;
11398
+ } catch (e) {
11399
+ lastErr = e;
11400
+ }
11401
+ }
11402
+ throw lastErr;
11403
+ };
11404
+ }
11405
+
11406
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/transcript-view.js
11407
+ import { join as join16 } from "node:path";
11408
+ async function showTranscript(deps) {
11409
+ const text = deps.path ? deps.readText(deps.path) : void 0;
11410
+ if (text === void 0 || text === "") {
11411
+ deps.write("\x1B[2m\u2500 no transcript yet \u2500\x1B[0m\n");
11412
+ return;
11413
+ }
11414
+ if (!deps.interactive) {
11415
+ deps.write(text.endsWith("\n") ? text : `${text}
11416
+ `);
11417
+ return;
11418
+ }
11419
+ deps.suspendRaw();
11420
+ try {
11421
+ await deps.spawnPager(deps.path);
11422
+ } catch {
11423
+ deps.write(text.endsWith("\n") ? text : `${text}
11424
+ `);
11425
+ } finally {
11426
+ deps.restoreRaw();
11427
+ }
11428
+ }
11429
+
11430
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/slash.js
11431
+ var NAME_RE = /^[a-zA-Z][\w-]*$/;
11432
+ function classifyLine(line, known) {
11433
+ if (!line.startsWith("/"))
11434
+ return { kind: "submit" };
11435
+ if (line.includes("\n"))
11436
+ return { kind: "submit" };
11437
+ const body = line.slice(1);
11438
+ const ws = body.search(/\s/);
11439
+ const rawName = ws === -1 ? body : body.slice(0, ws);
11440
+ const args = ws === -1 ? "" : body.slice(ws).trim();
11441
+ if (rawName === "")
11442
+ return { kind: "submit" };
11443
+ if (!NAME_RE.test(rawName))
11444
+ return { kind: "submit" };
11445
+ const name = rawName.toLowerCase();
11446
+ return known.has(name) ? { kind: "command", name, args } : { kind: "unknown", name };
11447
+ }
11448
+ function renderHelp(ctx, cmds) {
11449
+ for (const c of cmds)
11450
+ ctx.write(` /${c.name} ${c.summary}
11451
+ `);
11452
+ ctx.write("\nshortcuts: Enter submit \xB7 Alt+Enter newline \xB7 paste multiline \xB7 Ctrl-C interrupt \xB7 Ctrl-D exit\n");
11453
+ }
11454
+ function formatUsage(u) {
11455
+ if (!u)
11456
+ return null;
11457
+ const parts = [];
11458
+ if (u.contextTokens !== void 0)
11459
+ parts.push(`context ${u.contextTokens}`);
11460
+ if (u.outputTokens !== void 0)
11461
+ parts.push(`output ${u.outputTokens}`);
11462
+ if (u.cachedTokens !== void 0)
11463
+ parts.push(`cached ${u.cachedTokens}`);
11464
+ if (u.contextLimit !== void 0)
11465
+ parts.push(`limit ${u.contextLimit}`);
11466
+ if (u.contextTokens !== void 0 && u.contextLimit !== void 0 && u.contextLimit > 0) {
11467
+ parts.push(`${Math.round(u.contextTokens / u.contextLimit * 100)}%`);
11468
+ }
11469
+ return parts.length ? parts.join(" \xB7 ") : null;
11470
+ }
11471
+ function builtinCommands(listCommands) {
11472
+ return [
11473
+ {
11474
+ name: "help",
11475
+ summary: "list commands and keyboard shortcuts",
11476
+ run: (ctx) => renderHelp(ctx, listCommands())
11477
+ },
11478
+ {
11479
+ name: "new",
11480
+ aliases: ["clear"],
11481
+ summary: "start a new conversation",
11482
+ run: async (ctx) => {
11483
+ ctx.recorder?.noteNewConversation();
11484
+ await ctx.session.newConversation();
11485
+ ctx.write("\u2014 new conversation \u2014\n");
11486
+ }
11487
+ },
11488
+ {
11489
+ name: "status",
11490
+ summary: "show provider, model, and effort",
11491
+ run: async (ctx) => {
11492
+ const s = await ctx.session.status();
11493
+ const effort = s.effort ? ` \xB7 ${s.effort}` : "";
11494
+ ctx.write(`${s.provider} \xB7 ${s.model}${effort}
11495
+ `);
11496
+ }
11497
+ },
11498
+ {
11499
+ name: "skills",
11500
+ summary: "list discovered skills",
11501
+ run: (ctx) => {
11502
+ const skills = ctx.skills();
11503
+ if (skills.length === 0) {
11504
+ ctx.write("(no skills found)\n");
11505
+ return;
11506
+ }
11507
+ for (const s of skills)
11508
+ ctx.write(` ${s.name} \xB7 ${s.source}
11509
+ `);
11510
+ }
11511
+ },
11512
+ {
11513
+ name: "copy",
11514
+ summary: "copy the last response to the clipboard",
11515
+ run: async (ctx) => {
11516
+ const text = ctx.lastContent();
11517
+ if (text === "") {
11518
+ ctx.write("no response to copy\n");
11519
+ return;
11520
+ }
11521
+ try {
11522
+ await ctx.clipboard(text);
11523
+ ctx.write(`copied ${Buffer.byteLength(text, "utf8")} bytes
11524
+ `);
11525
+ } catch (e) {
11526
+ ctx.write(`clipboard unavailable: ${e.message}
11527
+ `);
11528
+ }
11529
+ }
11530
+ },
11531
+ {
11532
+ name: "usage",
11533
+ summary: "show the last turn's token usage",
11534
+ run: (ctx) => {
11535
+ const formatted = formatUsage(ctx.lastUsage());
11536
+ ctx.write(formatted ? `${formatted}
11537
+ ` : "no usage yet\n");
11538
+ }
11539
+ },
11540
+ {
11541
+ name: "transcript",
11542
+ summary: "view the model-perspective transcript (same as Ctrl+T)",
11543
+ run: async (ctx) => {
11544
+ if (!ctx.showTranscript) {
11545
+ ctx.write("transcript unavailable\n");
11546
+ return;
11547
+ }
11548
+ await ctx.showTranscript();
11549
+ }
11550
+ },
11551
+ {
11552
+ name: "compact",
11553
+ summary: "summarize old history and free context",
11554
+ run: async (ctx) => {
11555
+ if (!ctx.compactor) {
11556
+ ctx.write("compaction unavailable\n");
11557
+ return;
11558
+ }
11559
+ const out = await ctx.compactor.compactNow();
11560
+ if (out.kind === "skipped" && out.reason === "in-progress") {
11561
+ ctx.write("compaction already in progress\n");
11562
+ }
11563
+ }
11564
+ },
11565
+ {
11566
+ name: "quit",
11567
+ aliases: ["exit"],
11568
+ summary: "exit ezio",
11569
+ run: () => {
11570
+ }
11571
+ // the controller maps /quit to the exit outcome before run()
11572
+ }
11573
+ ];
11574
+ }
11575
+ var SlashController = class {
11576
+ ctx;
11577
+ /** name OR alias → command (so classifyLine's `known` set is keys()). */
11578
+ byKey = /* @__PURE__ */ new Map();
11579
+ constructor(ctx, opts) {
11580
+ this.ctx = ctx;
11581
+ const exclude = new Set(opts?.excludeCommands ?? []);
11582
+ for (const cmd of builtinCommands(() => this.summaries())) {
11583
+ if (exclude.has(cmd.name))
11584
+ continue;
11585
+ this.register(cmd);
11586
+ }
11587
+ }
11588
+ /** Register (or override) a command and its aliases. Last registration wins
11589
+ * per key: any command that already owns this command's NAME is fully evicted
11590
+ * (all of its keys removed) so an override replaces it cleanly; an alias key
11591
+ * collision is resolved key-by-key (the alias now points at the new command,
11592
+ * but the prior owner keeps its other keys). */
11593
+ register(cmd) {
11594
+ const displaced = this.byKey.get(cmd.name);
11595
+ if (displaced) {
11596
+ for (const [k, v] of this.byKey)
11597
+ if (v === displaced)
11598
+ this.byKey.delete(k);
11599
+ }
11600
+ this.byKey.set(cmd.name, cmd);
11601
+ for (const a of cmd.aliases ?? [])
11602
+ this.byKey.set(a, cmd);
11603
+ }
11604
+ /** Deduped canonical command list for /help. A command is listed only if it
11605
+ * still OWNS its own name key — this filters out any command reachable only
11606
+ * through a stolen alias key (e.g. another command claimed its name), so
11607
+ * /help never shows a stale entry. */
11608
+ summaries() {
11609
+ const seen = /* @__PURE__ */ new Set();
11610
+ const out = [];
11611
+ for (const cmd of this.byKey.values()) {
11612
+ if (seen.has(cmd))
11613
+ continue;
11614
+ seen.add(cmd);
11615
+ if (this.byKey.get(cmd.name) !== cmd)
11616
+ continue;
11617
+ out.push({ name: cmd.name, summary: cmd.summary });
11618
+ }
11619
+ return out;
11620
+ }
11621
+ async handle(line) {
11622
+ const c = classifyLine(line, new Set(this.byKey.keys()));
11623
+ if (c.kind === "submit")
11624
+ return { action: "submit", text: line };
11625
+ if (c.kind === "unknown") {
11626
+ this.ctx.write(`unknown command: /${c.name}. type /help for the list.
11627
+ `);
11628
+ return { action: "handled" };
11629
+ }
11630
+ const cmd = this.byKey.get(c.name);
11631
+ if (!cmd)
11632
+ return { action: "handled" };
11633
+ if (cmd.name === "quit")
11634
+ return { action: "exit" };
11635
+ try {
11636
+ await cmd.run(this.ctx, c.args);
11637
+ } catch (e) {
11638
+ this.ctx.write(`/${c.name} failed: ${e.message}
11639
+ `);
11640
+ }
11641
+ return { action: "handled" };
11642
+ }
11643
+ };
11644
+
11267
11645
  // ../adapter-ai-ezio/dist/mid-composition-shape.js
11268
11646
  var DRAFTING_PREFIXES = [
11269
11647
  "let's draft",
@@ -11300,6 +11678,11 @@ function isMidCompositionShape(message) {
11300
11678
  }
11301
11679
 
11302
11680
  // ../adapter-ai-ezio/dist/create-ai-ezio-live-session.js
11681
+ import { spawn as spawn8 } from "node:child_process";
11682
+ import { randomUUID as randomUUID3 } from "node:crypto";
11683
+ import { readFileSync as readFileSync6 } from "node:fs";
11684
+ import { tmpdir } from "node:os";
11685
+ import { join as join17 } from "node:path";
11303
11686
  var defaultBuildAutoCompact = ({ session, host, write }) => {
11304
11687
  const { compaction } = loadConfig();
11305
11688
  return createAutoCompactDriver({
@@ -11321,6 +11704,9 @@ function createAiEzioLiveSession(input) {
11321
11704
  let driver = null;
11322
11705
  let sawTurn = false;
11323
11706
  let pendingContent = null;
11707
+ let lastContent = "";
11708
+ let lastUsage;
11709
+ let slash = null;
11324
11710
  const outputHandlers = [];
11325
11711
  const turnFinishedHandlers = [];
11326
11712
  const fidelityDecisionHandlers = [];
@@ -11337,6 +11723,8 @@ function createAiEzioLiveSession(input) {
11337
11723
  break;
11338
11724
  case "assistant_turn_finished":
11339
11725
  sawTurn = true;
11726
+ lastContent = event.content;
11727
+ lastUsage = event.usage;
11340
11728
  if (pendingContent !== null) {
11341
11729
  const superseded = pendingContent;
11342
11730
  for (const h of fidelityDecisionHandlers)
@@ -11390,8 +11778,47 @@ function createAiEzioLiveSession(input) {
11390
11778
  for (const h of exitHandlers)
11391
11779
  h();
11392
11780
  });
11393
- await session.start();
11781
+ const transcriptPath = join17(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
11782
+ await session.start({ transcriptPath });
11394
11783
  await host.start(session);
11784
+ const skillEnv = {
11785
+ cwd: process.cwd(),
11786
+ home: process.env.HOME ?? "",
11787
+ xdgConfigHome: process.env.XDG_CONFIG_HOME
11788
+ };
11789
+ const skillFs = nodeSkillFs();
11790
+ const sessionFacet = session;
11791
+ const slashCtx = {
11792
+ write: (s) => input.stdout.write(s),
11793
+ session: sessionFacet,
11794
+ ...driver ? { compactor: { compactNow: () => driver.compactNow() } } : {},
11795
+ lastContent: () => lastContent,
11796
+ lastUsage: () => lastUsage,
11797
+ skills: () => discoverSkills(skillEnv, skillFs).map((s) => ({
11798
+ name: s.name,
11799
+ source: s.source,
11800
+ description: s.description
11801
+ })),
11802
+ clipboard: input.clipboard ?? makeClipboard(process.platform, spawn8),
11803
+ showTranscript: () => showTranscript({
11804
+ path: sessionFacet.transcriptPath ?? "",
11805
+ readText: (p) => {
11806
+ try {
11807
+ return readFileSync6(p, "utf8");
11808
+ } catch {
11809
+ return void 0;
11810
+ }
11811
+ },
11812
+ interactive: false,
11813
+ spawnPager: () => Promise.resolve(),
11814
+ suspendRaw: () => {
11815
+ },
11816
+ restoreRaw: () => {
11817
+ },
11818
+ write: (s) => input.stdout.write(s)
11819
+ })
11820
+ };
11821
+ slash = new SlashController(slashCtx, { excludeCommands: ["quit"] });
11395
11822
  },
11396
11823
  async stop() {
11397
11824
  await host.stop();
@@ -11404,6 +11831,12 @@ function createAiEzioLiveSession(input) {
11404
11831
  interrupt() {
11405
11832
  session?.interrupt();
11406
11833
  },
11834
+ async tryConsumeLocalCommand(line) {
11835
+ if (!slash)
11836
+ return false;
11837
+ const outcome = await slash.handle(line);
11838
+ return outcome.action !== "submit";
11839
+ },
11407
11840
  echoUserInput(text, cols) {
11408
11841
  renderer.echoUserInput(text, cols);
11409
11842
  },
@@ -11586,9 +12019,9 @@ function createCliWorkItemId(now) {
11586
12019
 
11587
12020
  // src/ezio-staleness-check.ts
11588
12021
  import { createRequire as createRequire4 } from "node:module";
11589
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
12022
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
11590
12023
  import { homedir as homedir2 } from "node:os";
11591
- import { join as join15 } from "node:path";
12024
+ import { join as join18 } from "node:path";
11592
12025
 
11593
12026
  // src/semver-compare.ts
11594
12027
  function parse(version) {
@@ -11634,10 +12067,10 @@ function compareSemver(a, b) {
11634
12067
 
11635
12068
  // src/generated/ezio-provenance.ts
11636
12069
  var EZIO_PROVENANCE = {
11637
- ezioCliVersion: "0.2.0-beta.3",
11638
- ezioGitSha: "4a1bdbf",
11639
- builtAt: "2026-06-13T13:56:01.916Z",
11640
- whisperVersion: "0.5.8"
12070
+ ezioCliVersion: "0.2.0-beta.4",
12071
+ ezioGitSha: "64f64ed",
12072
+ builtAt: "2026-06-14T04:05:27.620Z",
12073
+ whisperVersion: "0.5.9"
11641
12074
  };
11642
12075
 
11643
12076
  // src/ezio-provenance-types.ts
@@ -11661,18 +12094,18 @@ function parseCache(raw) {
11661
12094
  }
11662
12095
  }
11663
12096
  function cacheFile() {
11664
- return join15(homedir2(), ".ai-whisper", "update-check.json");
12097
+ return join18(homedir2(), ".ai-whisper", "update-check.json");
11665
12098
  }
11666
12099
  function defaultReadCache() {
11667
12100
  try {
11668
- return parseCache(readFileSync5(cacheFile(), "utf8"));
12101
+ return parseCache(readFileSync7(cacheFile(), "utf8"));
11669
12102
  } catch {
11670
12103
  return null;
11671
12104
  }
11672
12105
  }
11673
12106
  function defaultWriteCache(cache) {
11674
12107
  try {
11675
- mkdirSync6(join15(homedir2(), ".ai-whisper"), { recursive: true });
12108
+ mkdirSync6(join18(homedir2(), ".ai-whisper"), { recursive: true });
11676
12109
  writeFileSync4(cacheFile(), JSON.stringify(cache), "utf8");
11677
12110
  } catch {
11678
12111
  }
@@ -11697,9 +12130,9 @@ function defaultResolveInstalledEzioVersion() {
11697
12130
  const require4 = createRequire4(import.meta.url);
11698
12131
  const paths = require4.resolve.paths("@ai-creed/ai-ezio") ?? [];
11699
12132
  for (const base of paths) {
11700
- const pkgPath = join15(base, "@ai-creed", "ai-ezio", "package.json");
12133
+ const pkgPath = join18(base, "@ai-creed", "ai-ezio", "package.json");
11701
12134
  if (existsSync10(pkgPath)) {
11702
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
12135
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
11703
12136
  return pkg.version ?? null;
11704
12137
  }
11705
12138
  }
@@ -13032,7 +13465,7 @@ async function captureHandbackText(input) {
13032
13465
  import { execFile as execFile3 } from "node:child_process";
13033
13466
  import { existsSync as existsSync11 } from "node:fs";
13034
13467
  import { fileURLToPath as fileURLToPath3 } from "node:url";
13035
- import { dirname as dirname5, join as join16 } from "node:path";
13468
+ import { dirname as dirname5, join as join19 } from "node:path";
13036
13469
  var HELPER_BIN_NAME = "clipboard-change-count";
13037
13470
  function execFileText2(command, args = []) {
13038
13471
  return new Promise((resolve5, reject) => {
@@ -13049,7 +13482,7 @@ function makeChangeCountReader(deps) {
13049
13482
  const platform = deps?.platform ?? process.platform;
13050
13483
  const runHelper = deps?.runHelper ?? (async () => {
13051
13484
  const here = dirname5(fileURLToPath3(import.meta.url));
13052
- const bin = join16(here, "..", "native", HELPER_BIN_NAME);
13485
+ const bin = join19(here, "..", "native", HELPER_BIN_NAME);
13053
13486
  if (!existsSync11(bin)) throw new Error("changeCount helper not built");
13054
13487
  return execFileText2(bin);
13055
13488
  });
@@ -13313,7 +13746,7 @@ function createMountSessionRuntime(input) {
13313
13746
  channel,
13314
13747
  data: value
13315
13748
  });
13316
- interactiveSession.writeUserInput(value);
13749
+ injectedWrite(interactiveSession, value);
13317
13750
  };
13318
13751
  const submitInjectedInput2 = async (text) => {
13319
13752
  debugLog({
@@ -14094,6 +14527,12 @@ async function runCollabMount(input) {
14094
14527
  mkdirSync8(getStateSocketsDir(), { recursive: true });
14095
14528
  mkdirSync8(getStateLogsDir(), { recursive: true });
14096
14529
  console.error(formatTurnEventsStartupLine(enablement));
14530
+ const unknownTurnEventsTokens = unrecognizedTurnEventsTokens(input.turnEventsFlag);
14531
+ if (unknownTurnEventsTokens.length > 0) {
14532
+ console.error(
14533
+ `[ai-whisper] turn-events: ignoring unrecognized token(s) ${unknownTurnEventsTokens.map((t) => `"${t}"`).join(", ")} \u2014 expected claude, codex, off, or none`
14534
+ );
14535
+ }
14097
14536
  const tryResolve = () => {
14098
14537
  const db = openDatabase(getSharedSqlitePath());
14099
14538
  try {
@@ -15091,8 +15530,8 @@ import Database2 from "better-sqlite3";
15091
15530
  import { existsSync as existsSync12 } from "node:fs";
15092
15531
 
15093
15532
  // src/runtime/evaluator-config.ts
15094
- import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
15095
- import { join as join17 } from "node:path";
15533
+ import { readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
15534
+ import { join as join20 } from "node:path";
15096
15535
  function isEvaluatorReady(status) {
15097
15536
  return status === "ready" || status === "unknown";
15098
15537
  }
@@ -15778,7 +16217,7 @@ async function runSkillInstall(input) {
15778
16217
  init_dist2();
15779
16218
 
15780
16219
  // src/runtime/cli-package-info.ts
15781
- import { readFileSync as readFileSync7 } from "node:fs";
16220
+ import { readFileSync as readFileSync9 } from "node:fs";
15782
16221
  import { dirname as dirname7 } from "node:path";
15783
16222
  import { fileURLToPath as fileURLToPath7 } from "node:url";
15784
16223
  var PKG_CANDIDATES = ["../../package.json", "../package.json"];
@@ -15786,7 +16225,7 @@ function readWhisperPackage() {
15786
16225
  for (const rel of PKG_CANDIDATES) {
15787
16226
  try {
15788
16227
  const url = new URL(rel, import.meta.url);
15789
- const pkg = JSON.parse(readFileSync7(url, "utf8"));
16228
+ const pkg = JSON.parse(readFileSync9(url, "utf8"));
15790
16229
  if (pkg.name === "ai-whisper" && typeof pkg.version === "string") {
15791
16230
  return { url, version: pkg.version };
15792
16231
  }
@@ -16007,7 +16446,7 @@ Not attached (stdin/stdout is not a TTY). To view the tmux session, run:
16007
16446
  "Args forwarded after `--` to the agent binary spawn (e.g. `mount codex -- --full-auto`)"
16008
16447
  ).option("--workspace <path>", "Workspace root", process.cwd()).option("--collab <id>", "Target a specific collab id (defaults to the active collab for cwd)").option(
16009
16448
  "--turn-events <providers>",
16010
- "Enable push turn-completion events for the given providers (comma-separated: claude,codex). Overrides AI_WHISPER_TURN_EVENTS. Default: off."
16449
+ "Scope the push turn-completion event path to the given providers (comma-separated: claude,codex), or disable it with `off`/`none` to revert to pure clipboard capture. Overrides AI_WHISPER_TURN_EVENTS. Default: on (claude,codex)."
16011
16450
  ).action(
16012
16451
  async (target, passthroughArgs, opts) => {
16013
16452
  await runCollabMount({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-whisper",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Terminal-first relay for paired AI coding agents (Claude + Codex), driven by structured workflows.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,12 +51,12 @@
51
51
  "@types/better-sqlite3": "^7.6.12",
52
52
  "@types/react": "^19.2.14",
53
53
  "ink-testing-library": "^4.0.0",
54
- "@ai-whisper/adapter-codex": "0.0.0",
55
- "@ai-whisper/adapter-ai-ezio": "0.0.0",
56
- "@ai-whisper/shared": "0.0.0",
54
+ "@ai-whisper/adapter-claude": "0.0.0",
57
55
  "@ai-whisper/broker": "0.0.0",
56
+ "@ai-whisper/adapter-ai-ezio": "0.0.0",
58
57
  "@ai-whisper/companion-core": "0.0.0",
59
- "@ai-whisper/adapter-claude": "0.0.0"
58
+ "@ai-whisper/adapter-codex": "0.0.0",
59
+ "@ai-whisper/shared": "0.0.0"
60
60
  },
61
61
  "files": [
62
62
  "dist",