approval-box 0.1.13 → 0.1.14

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.
@@ -28,7 +28,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
28
  SOFTWARE.
29
29
  ```
30
30
 
31
- ## aiterm-steer-delivery@0.1.9 (MIT)
31
+ ## aiterm-steer-delivery@0.2.0 (MIT)
32
32
 
33
33
  ```
34
34
  MIT License
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runClaudeHookMain
5
- } from "./chunks/chunk-YBOJCW6Y.mjs";
5
+ } from "./chunks/chunk-GH2JG56Y.mjs";
6
6
 
7
7
  // src/hooks/claude.ts
8
8
  await runClaudeHookMain(PROFILE);
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runCodexHookMain
5
- } from "./chunks/chunk-YBOJCW6Y.mjs";
5
+ } from "./chunks/chunk-GH2JG56Y.mjs";
6
6
 
7
7
  // src/hooks/codex.ts
8
8
  await runCodexHookMain(PROFILE, process.argv[2]);
@@ -5,7 +5,7 @@ import {
5
5
  handleCursorChannelHook,
6
6
  handleCursorHook,
7
7
  withoutBom
8
- } from "./chunks/chunk-YBOJCW6Y.mjs";
8
+ } from "./chunks/chunk-GH2JG56Y.mjs";
9
9
 
10
10
  // src/cursor-binding.ts
11
11
  async function bindCursorResult(profile, raw) {
@@ -2,7 +2,7 @@ import { createRequire as __kbCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  PROFILE,
4
4
  runChannelReceiveMain
5
- } from "./chunks/chunk-YBOJCW6Y.mjs";
5
+ } from "./chunks/chunk-GH2JG56Y.mjs";
6
6
 
7
7
  // src/hooks/receive.ts
8
8
  await runChannelReceiveMain(PROFILE);
@@ -19822,7 +19822,7 @@ function writeHookJson(file2, value) {
19822
19822
  fs.unlinkSync(temporary);
19823
19823
  }
19824
19824
  }
19825
- function waitForFileState(dir, inspect, timeoutMs) {
19825
+ function waitForFileState(dir, inspect, timeoutMs, signal) {
19826
19826
  return new Promise((resolve2, reject) => {
19827
19827
  let finished = false;
19828
19828
  let timer;
@@ -19837,11 +19837,13 @@ function waitForFileState(dir, inspect, timeoutMs) {
19837
19837
  clearInterval(timer);
19838
19838
  if (timeout)
19839
19839
  clearTimeout(timeout);
19840
+ signal?.removeEventListener("abort", abort);
19840
19841
  if (error62)
19841
19842
  reject(error62);
19842
19843
  else
19843
19844
  resolve2(value);
19844
19845
  };
19846
+ const abort = () => finish(new Error("WAIT_FOR_FILE_ABORTED"));
19845
19847
  const check2 = () => {
19846
19848
  if (finished)
19847
19849
  return;
@@ -19857,12 +19859,17 @@ function waitForFileState(dir, inspect, timeoutMs) {
19857
19859
  timer = setInterval(check2, 5e3);
19858
19860
  if (timeoutMs !== void 0)
19859
19861
  timeout = setTimeout(() => finish(new Error("WAIT_FOR_FILE_TIMEOUT")), timeoutMs);
19862
+ if (signal?.aborted)
19863
+ abort();
19864
+ else
19865
+ signal?.addEventListener("abort", abort, { once: true });
19860
19866
  check2();
19861
19867
  });
19862
19868
  }
19863
19869
 
19864
19870
  // ../../node_modules/aiterm-steer-delivery/dist/windows.js
19865
19871
  import { execFileSync, spawnSync } from "node:child_process";
19872
+ import { realpathSync } from "node:fs";
19866
19873
  import * as path3 from "node:path";
19867
19874
  var WINDOWS_POWERSHELL_7_COMMAND = "pwsh.exe";
19868
19875
  var WINDOWS_POWERSHELL_7_INSTALL = "winget install --id Microsoft.PowerShell --source winget";
@@ -19899,9 +19906,15 @@ function resolveWindowsPowerShell7(probe = defaultProbe) {
19899
19906
  }
19900
19907
  if (version2.status !== 0 || identity?.edition !== "Core" || !Number.isInteger(identity.major) || Number(identity.major) < 7)
19901
19908
  fail();
19909
+ let canonical;
19910
+ try {
19911
+ canonical = realpathSync.native(executable);
19912
+ } catch {
19913
+ return fail();
19914
+ }
19902
19915
  if (probe === defaultProbe)
19903
- resolvedDefault = executable;
19904
- return executable;
19916
+ resolvedDefault = canonical;
19917
+ return canonical;
19905
19918
  }
19906
19919
  var quotePowerShell = (value) => `'${value.replace(/'/g, "''")}'`;
19907
19920
  function windowsPowerShellSync(script, onFailure, timeout = 15e3) {
@@ -20170,6 +20183,18 @@ function parsePosixProcessTable(text) {
20170
20183
  };
20171
20184
  });
20172
20185
  }
20186
+ var HOOK_SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "zsh", "ksh", "ash", "busybox", "pwsh", "powershell", "cmd"]);
20187
+ function executableName(row) {
20188
+ const source = row.executable || (row.command.startsWith('"') ? row.command.slice(1).split('"')[0] : row.command.split(/\s+/u)[0]);
20189
+ return (source.split(/[\\/]/u).pop() ?? "").replace(/^-/u, "").replace(/\.exe$/iu, "").toLowerCase();
20190
+ }
20191
+ function hookOwnerProcess(rows, pid) {
20192
+ const byPid = new Map(rows.map((row) => [row.pid, row]));
20193
+ let current = byPid.get(pid);
20194
+ for (let depth = 0; current && depth < 4 && HOOK_SHELLS.has(executableName(current)); depth += 1)
20195
+ current = byPid.get(current.parent_pid);
20196
+ return current;
20197
+ }
20173
20198
  function readRuntimeProcesses() {
20174
20199
  if (!isWin2) {
20175
20200
  const result2 = spawnSync3("/bin/ps", ["-axww", "-o", "pid=,ppid=,pgid=,stat=,lstart=,time=,command="], {
@@ -20595,8 +20620,8 @@ function assertOpen(parent) {
20595
20620
  }
20596
20621
  function prepareClaudeHookRequest(input2, root) {
20597
20622
  const event = external_exports.object({ session_id: external_exports.uuid(), tool_use_id: requestId, agent_id: external_exports.string().optional() }).parse(input2);
20598
- const identity = processIdentity(process.ppid);
20599
- if (!identity)
20623
+ const owner = hookOwnerProcess(readRuntimeProcesses(), process.ppid);
20624
+ if (!owner)
20600
20625
  throw new ClaudeDeliveryError("CLAUDE_PARENT_PROCESS_UNAVAILABLE", "hook\u3092\u8D77\u52D5\u3057\u305F\u89AAprocess\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093");
20601
20626
  const dir = path8.join(root, event.tool_use_id);
20602
20627
  fs5.mkdirSync(dir, { recursive: true, mode: 448 });
@@ -20604,8 +20629,8 @@ function prepareClaudeHookRequest(input2, root) {
20604
20629
  request_id: event.tool_use_id,
20605
20630
  session_id: event.session_id,
20606
20631
  agent_id: event.agent_id ?? null,
20607
- parent_pid: process.ppid,
20608
- parent_started_identity: identity
20632
+ parent_pid: owner.pid,
20633
+ parent_started_identity: owner.started_identity
20609
20634
  });
20610
20635
  }
20611
20636
  function claudeParentFromRequest(profile, clientName, metadata, root) {
@@ -20695,6 +20720,250 @@ async function runClaudeResultHook(profile, input2, emit, root) {
20695
20720
  import * as fs6 from "node:fs";
20696
20721
  import { homedir as homedir3 } from "node:os";
20697
20722
  import * as path9 from "node:path";
20723
+
20724
+ // ../../node_modules/aiterm-steer-delivery/dist/hook-setup.js
20725
+ import { copyFileSync, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync4, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
20726
+ import { dirname as dirname3 } from "node:path";
20727
+ import { randomUUID as randomUUID2 } from "node:crypto";
20728
+ import { isDeepStrictEqual } from "node:util";
20729
+ var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20730
+ function endsWithFile(value, file2) {
20731
+ return value === file2 || value.endsWith(`/${file2}`) || value.endsWith(`\\${file2}`);
20732
+ }
20733
+ function shellQuote(value, platform = process.platform) {
20734
+ if (platform === "win32")
20735
+ return `'${value.replace(/'/g, "''")}'`;
20736
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
20737
+ }
20738
+ function claudeParentHookCommand(hook, platform = process.platform) {
20739
+ const words = `${shellQuote(hook.command, platform)} ${shellQuote(hook.script, platform)}`;
20740
+ if (platform === "win32")
20741
+ return { command: `& ${words}; exit (Get-Variable LASTEXITCODE -ValueOnly)`, shell: "powershell" };
20742
+ return { command: `exec ${words}` };
20743
+ }
20744
+ function claudeParentHookEntries(profile, hook, platform = process.platform) {
20745
+ const command2 = { type: "command", ...claudeParentHookCommand(hook, platform) };
20746
+ const matcher = `^mcp__${profile.mcp_server}__(${profile.dispatch_tools.join("|")})$`;
20747
+ return {
20748
+ PreToolUse: [{ matcher, hooks: [{ ...command2, timeout: 15 }] }],
20749
+ PostToolUse: [{ matcher, hooks: [{ ...command2, asyncRewake: true, timeout: 86400 }] }],
20750
+ // 長く続く受信(channel)では、turnが終わるたびに待機を張り直す。
20751
+ ...profile.channels ? { Stop: [{ hooks: [{ ...command2, asyncRewake: true, timeout: 86400 }] }] } : {},
20752
+ SessionEnd: [{ hooks: [{ ...command2, timeout: 15 }] }]
20753
+ };
20754
+ }
20755
+ function mergeClaudeParentHooks(profile, file2, hook) {
20756
+ const target = existsSync6(file2) ? realpathSync4(file2) : file2;
20757
+ let current = {};
20758
+ if (existsSync6(target)) {
20759
+ try {
20760
+ current = JSON.parse(readFileSync5(target, "utf8"));
20761
+ } catch {
20762
+ throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9AJSON\u3092\u8AAD\u3081\u307E\u305B\u3093");
20763
+ }
20764
+ }
20765
+ if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
20766
+ throw new SetupError("config_invalid", "Claude\u306Ehooks\u8A2D\u5B9A\u306Fobject\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
20767
+ }
20768
+ if (current.disableAllHooks === true)
20769
+ throw new SetupError("claude_parent_hooks_disabled", "Claude\u306Ehook\u304C\u7121\u52B9\u3067\u3059\u3002disableAllHooks\u3092false\u306B\u5909\u66F4\u3057\u3066\u304B\u3089setup\u3057\u3066\u304F\u3060\u3055\u3044");
20770
+ const hooks = { ...current.hooks };
20771
+ for (const [event, additions] of Object.entries(claudeParentHookEntries(profile, hook))) {
20772
+ const previous = hooks[event] ?? [];
20773
+ if (!Array.isArray(previous) || previous.some((group) => !record2(group) || !Array.isArray(group.hooks))) {
20774
+ throw new SetupError("config_invalid", `Claude\u306E${event} hook\u5F62\u5F0F\u3092\u8AAD\u3081\u307E\u305B\u3093`);
20775
+ }
20776
+ const owned = previous.flatMap((group) => group.hooks.filter((entry) => isClaudeParentHook(profile, entry)));
20777
+ if (owned.length === additions.flatMap((group) => group.hooks).length && additions.every((addition) => previous.some((group) => isDeepStrictEqual(group, addition))))
20778
+ continue;
20779
+ const retained = previous.map((group) => ({
20780
+ ...group,
20781
+ hooks: group.hooks.filter((entry) => !isClaudeParentHook(profile, entry))
20782
+ })).filter((group) => group.hooks.length > 0);
20783
+ hooks[event] = [...retained, ...additions];
20784
+ }
20785
+ const next = { ...current, hooks };
20786
+ if (isDeepStrictEqual(current, next))
20787
+ return "unchanged";
20788
+ mkdirSync5(dirname3(target), { recursive: true });
20789
+ const temporary = `${target}.${profile.id}-${randomUUID2()}`;
20790
+ try {
20791
+ writeFileSync3(temporary, `${JSON.stringify(next, null, 2)}
20792
+ `, { mode: 384, flag: "wx" });
20793
+ if (existsSync6(target))
20794
+ copyFileSync(target, `${target}${profile.backup_suffix}`);
20795
+ renameSync2(temporary, target);
20796
+ } finally {
20797
+ if (existsSync6(temporary))
20798
+ unlinkSync3(temporary);
20799
+ }
20800
+ if (!isDeepStrictEqual(JSON.parse(readFileSync5(target, "utf8")), next)) {
20801
+ throw new SetupError("config_readback_failed", "Claude\u306Ehook\u767B\u9332\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
20802
+ }
20803
+ return "configured";
20804
+ }
20805
+ function isClaudeParentHook(profile, hook) {
20806
+ if (!record2(hook) || hook.type !== "command")
20807
+ return false;
20808
+ if (Array.isArray(hook.args))
20809
+ return typeof hook.args[0] === "string" && endsWithFile(hook.args[0], profile.hooks.claude);
20810
+ return typeof hook.command === "string" && commandNamesFile(hook.command, profile.hooks.claude);
20811
+ }
20812
+ function ownedClaudeParentHooks(profile, document, event) {
20813
+ const hooks = record2(document) && record2(document.hooks) ? document.hooks[event] : void 0;
20814
+ if (!Array.isArray(hooks))
20815
+ return [];
20816
+ return hooks.flatMap((group) => record2(group) && Array.isArray(group.hooks) ? group.hooks : []).filter((entry) => isClaudeParentHook(profile, entry));
20817
+ }
20818
+ function claudeParentHooksRegistered(profile, document) {
20819
+ return Object.keys(claudeParentHookEntries(profile, { command: "", script: profile.hooks.claude })).every((event) => ownedClaudeParentHooks(profile, document, event).length > 0);
20820
+ }
20821
+ function removeClaudeParentHooks(profile, file2) {
20822
+ if (!existsSync6(file2))
20823
+ return "unchanged";
20824
+ const target = realpathSync4(file2);
20825
+ const current = JSON.parse(readFileSync5(target, "utf8"));
20826
+ if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
20827
+ throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
20828
+ }
20829
+ if (current.hooks === void 0)
20830
+ return "unchanged";
20831
+ const hooks = { ...current.hooks };
20832
+ for (const event of ["PreToolUse", "PostToolUse", "Stop", "SessionEnd"]) {
20833
+ if (hooks[event] === void 0)
20834
+ continue;
20835
+ if (!Array.isArray(hooks[event]))
20836
+ throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
20837
+ hooks[event] = hooks[event].map((group) => {
20838
+ if (!record2(group) || !Array.isArray(group.hooks))
20839
+ throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
20840
+ return { ...group, hooks: group.hooks.filter((entry) => !isClaudeParentHook(profile, entry)) };
20841
+ }).filter((group) => group.hooks.length > 0);
20842
+ }
20843
+ const next = { ...current, hooks };
20844
+ if (isDeepStrictEqual(current, next))
20845
+ return "unchanged";
20846
+ const temporary = `${target}.${profile.id}-${randomUUID2()}`;
20847
+ try {
20848
+ writeFileSync3(temporary, `${JSON.stringify(next, null, 2)}
20849
+ `, { mode: 384, flag: "wx" });
20850
+ copyFileSync(target, `${target}${profile.backup_suffix}`);
20851
+ renameSync2(temporary, target);
20852
+ } finally {
20853
+ if (existsSync6(temporary))
20854
+ unlinkSync3(temporary);
20855
+ }
20856
+ if (!isDeepStrictEqual(JSON.parse(readFileSync5(target, "utf8")), next))
20857
+ throw new SetupError("config_readback_failed", "Claude hook\u89E3\u9664\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
20858
+ return "removed";
20859
+ }
20860
+ function cursorParentHookCommand(hook) {
20861
+ const command2 = `${shellQuote(hook.command)} ${shellQuote(hook.script)}`;
20862
+ return process.platform === "win32" ? `& ${command2}` : command2;
20863
+ }
20864
+ function commandNamesFile(command2, file2) {
20865
+ const escaped = file2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20866
+ return new RegExp(`(?:^|[\\\\/'"\\s])${escaped}(?:['"\\s]|$)`, "u").test(command2);
20867
+ }
20868
+ function ownsCursorParentHook(profile, hook) {
20869
+ return record2(hook) && typeof hook.command === "string" && commandNamesFile(hook.command, profile.hooks.cursor);
20870
+ }
20871
+ function mergeCursorParentHooks(profile, file2, hook) {
20872
+ const target = existsSync6(file2) ? realpathSync4(file2) : file2;
20873
+ let current = { version: 1, hooks: {} };
20874
+ if (existsSync6(target)) {
20875
+ try {
20876
+ current = JSON.parse(readFileSync5(target, "utf8"));
20877
+ } catch {
20878
+ throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9AJSON\u3092\u8AAD\u3081\u307E\u305B\u3093");
20879
+ }
20880
+ } else {
20881
+ try {
20882
+ if (lstatSync(file2).isSymbolicLink())
20883
+ throw new SetupError("config_invalid", "Cursor\u306Ehooks\u8A2D\u5B9Asymlink\u306E\u53C2\u7167\u5148\u304C\u3042\u308A\u307E\u305B\u3093");
20884
+ } catch (error62) {
20885
+ if (error62.code !== "ENOENT")
20886
+ throw error62;
20887
+ }
20888
+ }
20889
+ if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
20890
+ throw new SetupError("config_invalid", "Cursor\u306Ehooks\u8A2D\u5B9A\u306Fobject\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
20891
+ }
20892
+ const hooks = { ...current.hooks };
20893
+ const entry = { command: cursorParentHookCommand(hook), timeout: 15 };
20894
+ for (const event of ["afterMCPExecution", "postToolUse"]) {
20895
+ const previous = hooks[event] ?? [];
20896
+ if (!Array.isArray(previous))
20897
+ throw new SetupError("config_invalid", `Cursor\u306E${event} hook\u5F62\u5F0F\u3092\u8AAD\u3081\u307E\u305B\u3093`);
20898
+ const retained = previous.filter((hook2) => !ownsCursorParentHook(profile, hook2));
20899
+ const owned = previous.filter((hook2) => ownsCursorParentHook(profile, hook2));
20900
+ if (owned.length === 1 && isDeepStrictEqual(owned[0], entry) && retained.length + 1 === previous.length)
20901
+ continue;
20902
+ const index = previous.findIndex((hook2) => ownsCursorParentHook(profile, hook2));
20903
+ hooks[event] = index < 0 ? [...retained, entry] : previous.map((hook2, i) => i === index ? entry : hook2).filter((hook2, i) => i === index || !ownsCursorParentHook(profile, hook2));
20904
+ }
20905
+ const next = { ...current, version: current.version ?? 1, hooks };
20906
+ if (isDeepStrictEqual(current, next))
20907
+ return "unchanged";
20908
+ mkdirSync5(dirname3(target), { recursive: true });
20909
+ const temporary = `${target}.${profile.id}-${randomUUID2()}`;
20910
+ try {
20911
+ writeFileSync3(temporary, `${JSON.stringify(next, null, 2)}
20912
+ `, { mode: 384, flag: "wx" });
20913
+ if (existsSync6(target))
20914
+ copyFileSync(target, `${target}${profile.backup_suffix}`);
20915
+ renameSync2(temporary, target);
20916
+ } finally {
20917
+ if (existsSync6(temporary))
20918
+ unlinkSync3(temporary);
20919
+ }
20920
+ if (!isDeepStrictEqual(JSON.parse(readFileSync5(target, "utf8")), next)) {
20921
+ throw new SetupError("config_readback_failed", "Cursor\u306Ehook\u767B\u9332\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
20922
+ }
20923
+ return "configured";
20924
+ }
20925
+ function removeCursorParentHooks(profile, file2) {
20926
+ if (!existsSync6(file2))
20927
+ return "unchanged";
20928
+ const target = realpathSync4(file2);
20929
+ const current = JSON.parse(readFileSync5(target, "utf8"));
20930
+ if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
20931
+ throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
20932
+ }
20933
+ if (current.hooks === void 0)
20934
+ return "unchanged";
20935
+ const hooks = { ...current.hooks };
20936
+ for (const event of ["afterMCPExecution", "postToolUse"]) {
20937
+ if (hooks[event] === void 0)
20938
+ continue;
20939
+ if (!Array.isArray(hooks[event]))
20940
+ throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
20941
+ const retained = hooks[event].filter((hook) => !ownsCursorParentHook(profile, hook));
20942
+ if (retained.length)
20943
+ hooks[event] = retained;
20944
+ else
20945
+ delete hooks[event];
20946
+ }
20947
+ const next = { ...current, hooks };
20948
+ if (isDeepStrictEqual(current, next))
20949
+ return "unchanged";
20950
+ const temporary = `${target}.${profile.id}-${randomUUID2()}`;
20951
+ try {
20952
+ writeFileSync3(temporary, `${JSON.stringify(next, null, 2)}
20953
+ `, { mode: 384, flag: "wx" });
20954
+ copyFileSync(target, `${target}${profile.backup_suffix}`);
20955
+ renameSync2(temporary, target);
20956
+ } finally {
20957
+ if (existsSync6(temporary))
20958
+ unlinkSync3(temporary);
20959
+ }
20960
+ if (!isDeepStrictEqual(JSON.parse(readFileSync5(target, "utf8")), next)) {
20961
+ throw new SetupError("config_readback_failed", "Cursor hook\u89E3\u9664\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
20962
+ }
20963
+ return "removed";
20964
+ }
20965
+
20966
+ // ../../node_modules/aiterm-steer-delivery/dist/cursor-receiver.js
20698
20967
  var cursorParentSchema = external_exports.object({
20699
20968
  kind: external_exports.literal("cursor"),
20700
20969
  hook_root: external_exports.string()
@@ -20720,7 +20989,7 @@ function cursorParentHooksRegistered(profile, document) {
20720
20989
  if (entry === null || typeof entry !== "object")
20721
20990
  return false;
20722
20991
  const command2 = entry.command;
20723
- return typeof command2 === "string" && command2.includes(profile.hooks.cursor);
20992
+ return typeof command2 === "string" && commandNamesFile(command2, profile.hooks.cursor);
20724
20993
  });
20725
20994
  };
20726
20995
  return owns("postToolUse") && owns("afterMCPExecution");
@@ -20888,7 +21157,7 @@ function waitProcessCommandLine(wait, platform = process.platform) {
20888
21157
  // ../../node_modules/aiterm-steer-delivery/dist/channel.js
20889
21158
  import * as fs7 from "node:fs";
20890
21159
  import * as path10 from "node:path";
20891
- import { randomUUID as randomUUID2 } from "node:crypto";
21160
+ import { randomUUID as randomUUID3 } from "node:crypto";
20892
21161
  import { setTimeout as delay } from "node:timers/promises";
20893
21162
  var uuid3 = external_exports.uuid();
20894
21163
  var channelSchema = external_exports.object({
@@ -20914,7 +21183,7 @@ function mkdirs(...dirs) {
20914
21183
  fs7.mkdirSync(dir, { recursive: true, mode: 448 });
20915
21184
  }
20916
21185
  function openChannel(profile, parent) {
20917
- const channel_id = randomUUID2();
21186
+ const channel_id = randomUUID3();
20918
21187
  const dir = channelDir(profile, channel_id);
20919
21188
  const created_at = (/* @__PURE__ */ new Date()).toISOString();
20920
21189
  let channel;
@@ -20980,7 +21249,7 @@ async function sendToChannel(profile, channelId, deliveryId, text, runtime) {
20980
21249
  }
20981
21250
  const name = `${Date.now().toString().padStart(15, "0")}-${(sendSequence++).toString().padStart(9, "0")}-${deliveryId}.json`;
20982
21251
  const item = { delivery_id: deliveryId, text, at: (/* @__PURE__ */ new Date()).toISOString() };
20983
- const temporary = path10.join(dir, `.${deliveryId}.${randomUUID2()}.tmp`);
21252
+ const temporary = path10.join(dir, `.${deliveryId}.${randomUUID3()}.tmp`);
20984
21253
  fs7.writeFileSync(temporary, JSON.stringify(item) + "\n", { flag: "wx", mode: 384 });
20985
21254
  try {
20986
21255
  fs7.linkSync(temporary, path10.join(dir, "inbox", name));
@@ -21130,7 +21399,7 @@ function acquireWaiter(index) {
21130
21399
  if (typeof owner.pid === "number" && typeof owner.started_identity === "string" && processIdentity2(owner.pid) === owner.started_identity)
21131
21400
  return null;
21132
21401
  try {
21133
- fs7.renameSync(file2, path10.join(index, `waiter.${randomUUID2()}.stale`));
21402
+ fs7.renameSync(file2, path10.join(index, `waiter.${randomUUID3()}.stale`));
21134
21403
  } catch {
21135
21404
  }
21136
21405
  for (const name of fs7.readdirSync(index).filter((entry) => entry.endsWith(".stale")))
@@ -21332,6 +21601,8 @@ async function runCodexHookMain(profile, directory2 = process.argv[2]) {
21332
21601
  async function runClaudeHookMain(profile, root = claudeHookRoot(profile)) {
21333
21602
  try {
21334
21603
  const event = JSON.parse(await readStdin());
21604
+ if (event !== null && typeof event === "object" && typeof event.hookEventName === "string")
21605
+ return;
21335
21606
  const write = (text) => new Promise((resolve2, reject) => {
21336
21607
  process.stderr.write(text, (error62) => error62 ? reject(error62) : resolve2());
21337
21608
  });
@@ -21402,7 +21673,7 @@ function setupNodeExecutable(executable = process.execPath) {
21402
21673
  // ../../node_modules/aiterm-steer-delivery/dist/codex-setup.js
21403
21674
  import * as fs8 from "node:fs";
21404
21675
  import * as path11 from "node:path";
21405
- import { isDeepStrictEqual } from "node:util";
21676
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
21406
21677
  function codexHookCommand(node2, hook, directory2, platform = process.platform, powershell = () => resolveWindowsPowerShell7()) {
21407
21678
  if (platform === "win32") {
21408
21679
  const script = `& ${[node2, hook, directory2].map(quotePowerShell).join(" ")}; exit $LASTEXITCODE`;
@@ -21472,7 +21743,7 @@ function planCodexParentHooks(file2, command2, previousCommand) {
21472
21743
  delete hooks[event];
21473
21744
  }
21474
21745
  const next = { ...current, hooks };
21475
- return { target, current, next, changed: !isDeepStrictEqual(current, next), moves, owned: ownedAt };
21746
+ return { target, current, next, changed: !isDeepStrictEqual2(current, next), moves, owned: ownedAt };
21476
21747
  }
21477
21748
  function writeCodexHookPlan(profile, plan) {
21478
21749
  if (!plan.changed)
@@ -21480,7 +21751,7 @@ function writeCodexHookPlan(profile, plan) {
21480
21751
  if (fs8.existsSync(plan.target))
21481
21752
  fs8.copyFileSync(plan.target, `${plan.target}${profile.backup_suffix}`);
21482
21753
  writeHookJson(plan.target, plan.next);
21483
- if (!isDeepStrictEqual(JSON.parse(fs8.readFileSync(plan.target, "utf8")), plan.next))
21754
+ if (!isDeepStrictEqual2(JSON.parse(fs8.readFileSync(plan.target, "utf8")), plan.next))
21484
21755
  throw new SetupError("codex_hook_readback_failed", "Codex hook\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
21485
21756
  return true;
21486
21757
  }
@@ -21676,222 +21947,6 @@ async function configureCodexSteer(profile, action = "status", overrides) {
21676
21947
  return needsRestart(config2) ? { status: "restart_required", reason_code: "codex_restart_required" } : { status: "ready" };
21677
21948
  }
21678
21949
 
21679
- // ../../node_modules/aiterm-steer-delivery/dist/hook-setup.js
21680
- import { copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, realpathSync as realpathSync4, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
21681
- import { dirname as dirname4 } from "node:path";
21682
- import { randomUUID as randomUUID3 } from "node:crypto";
21683
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
21684
- var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21685
- function endsWithFile(value, file2) {
21686
- return value === file2 || value.endsWith(`/${file2}`) || value.endsWith(`\\${file2}`);
21687
- }
21688
- function claudeParentHookEntries(profile, hook) {
21689
- const command2 = { type: "command", command: hook.command, args: [hook.script] };
21690
- const matcher = `^mcp__${profile.mcp_server}__(${profile.dispatch_tools.join("|")})$`;
21691
- return {
21692
- PreToolUse: [{ matcher, hooks: [{ ...command2, timeout: 15 }] }],
21693
- PostToolUse: [{ matcher, hooks: [{ ...command2, asyncRewake: true, timeout: 86400 }] }],
21694
- // 長く続く受信(channel)では、turnが終わるたびに待機を張り直す。
21695
- ...profile.channels ? { Stop: [{ hooks: [{ ...command2, asyncRewake: true, timeout: 86400 }] }] } : {},
21696
- SessionEnd: [{ hooks: [{ ...command2, timeout: 15 }] }]
21697
- };
21698
- }
21699
- function mergeClaudeParentHooks(profile, file2, hook) {
21700
- const target = existsSync9(file2) ? realpathSync4(file2) : file2;
21701
- let current = {};
21702
- if (existsSync9(target)) {
21703
- try {
21704
- current = JSON.parse(readFileSync8(target, "utf8"));
21705
- } catch {
21706
- throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9AJSON\u3092\u8AAD\u3081\u307E\u305B\u3093");
21707
- }
21708
- }
21709
- if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
21710
- throw new SetupError("config_invalid", "Claude\u306Ehooks\u8A2D\u5B9A\u306Fobject\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
21711
- }
21712
- if (current.disableAllHooks === true)
21713
- throw new SetupError("claude_parent_hooks_disabled", "Claude\u306Ehook\u304C\u7121\u52B9\u3067\u3059\u3002disableAllHooks\u3092false\u306B\u5909\u66F4\u3057\u3066\u304B\u3089setup\u3057\u3066\u304F\u3060\u3055\u3044");
21714
- const hooks = { ...current.hooks };
21715
- for (const [event, additions] of Object.entries(claudeParentHookEntries(profile, hook))) {
21716
- const previous = hooks[event] ?? [];
21717
- if (!Array.isArray(previous) || previous.some((group) => !record2(group) || !Array.isArray(group.hooks))) {
21718
- throw new SetupError("config_invalid", `Claude\u306E${event} hook\u5F62\u5F0F\u3092\u8AAD\u3081\u307E\u305B\u3093`);
21719
- }
21720
- const retained = previous.map((group) => ({
21721
- ...group,
21722
- hooks: group.hooks.filter((entry) => !isClaudeParentHook(profile, entry))
21723
- })).filter((group) => group.hooks.length > 0);
21724
- hooks[event] = [...retained, ...additions];
21725
- }
21726
- const next = { ...current, hooks };
21727
- if (isDeepStrictEqual2(current, next))
21728
- return "unchanged";
21729
- mkdirSync7(dirname4(target), { recursive: true });
21730
- const temporary = `${target}.${profile.id}-${randomUUID3()}`;
21731
- try {
21732
- writeFileSync5(temporary, `${JSON.stringify(next, null, 2)}
21733
- `, { mode: 384, flag: "wx" });
21734
- if (existsSync9(target))
21735
- copyFileSync2(target, `${target}${profile.backup_suffix}`);
21736
- renameSync3(temporary, target);
21737
- } finally {
21738
- if (existsSync9(temporary))
21739
- unlinkSync4(temporary);
21740
- }
21741
- if (!isDeepStrictEqual2(JSON.parse(readFileSync8(target, "utf8")), next)) {
21742
- throw new SetupError("config_readback_failed", "Claude\u306Ehook\u767B\u9332\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
21743
- }
21744
- return "configured";
21745
- }
21746
- function isClaudeParentHook(profile, hook) {
21747
- return record2(hook) && hook.type === "command" && Array.isArray(hook.args) && typeof hook.args[0] === "string" && endsWithFile(hook.args[0], profile.hooks.claude);
21748
- }
21749
- function removeClaudeParentHooks(profile, file2) {
21750
- if (!existsSync9(file2))
21751
- return "unchanged";
21752
- const target = realpathSync4(file2);
21753
- const current = JSON.parse(readFileSync8(target, "utf8"));
21754
- if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
21755
- throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
21756
- }
21757
- if (current.hooks === void 0)
21758
- return "unchanged";
21759
- const hooks = { ...current.hooks };
21760
- for (const event of ["PreToolUse", "PostToolUse", "Stop", "SessionEnd"]) {
21761
- if (hooks[event] === void 0)
21762
- continue;
21763
- if (!Array.isArray(hooks[event]))
21764
- throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
21765
- hooks[event] = hooks[event].map((group) => {
21766
- if (!record2(group) || !Array.isArray(group.hooks))
21767
- throw new SetupError("config_invalid", "Claude\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
21768
- return { ...group, hooks: group.hooks.filter((entry) => !isClaudeParentHook(profile, entry)) };
21769
- }).filter((group) => group.hooks.length > 0);
21770
- }
21771
- const next = { ...current, hooks };
21772
- if (isDeepStrictEqual2(current, next))
21773
- return "unchanged";
21774
- const temporary = `${target}.${profile.id}-${randomUUID3()}`;
21775
- try {
21776
- writeFileSync5(temporary, `${JSON.stringify(next, null, 2)}
21777
- `, { mode: 384, flag: "wx" });
21778
- copyFileSync2(target, `${target}${profile.backup_suffix}`);
21779
- renameSync3(temporary, target);
21780
- } finally {
21781
- if (existsSync9(temporary))
21782
- unlinkSync4(temporary);
21783
- }
21784
- if (!isDeepStrictEqual2(JSON.parse(readFileSync8(target, "utf8")), next))
21785
- throw new SetupError("config_readback_failed", "Claude hook\u89E3\u9664\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
21786
- return "removed";
21787
- }
21788
- function shellQuote(value) {
21789
- if (process.platform === "win32")
21790
- return `'${value.replace(/'/g, "''")}'`;
21791
- return `'${value.replace(/'/g, `'"'"'`)}'`;
21792
- }
21793
- function cursorParentHookCommand(hook) {
21794
- const command2 = `${shellQuote(hook.command)} ${shellQuote(hook.script)}`;
21795
- return process.platform === "win32" ? `& ${command2}` : command2;
21796
- }
21797
- function ownsCursorParentHook(profile, hook) {
21798
- return record2(hook) && typeof hook.command === "string" && hook.command.includes(profile.hooks.cursor);
21799
- }
21800
- function mergeCursorParentHooks(profile, file2, hook) {
21801
- const target = existsSync9(file2) ? realpathSync4(file2) : file2;
21802
- let current = { version: 1, hooks: {} };
21803
- if (existsSync9(target)) {
21804
- try {
21805
- current = JSON.parse(readFileSync8(target, "utf8"));
21806
- } catch {
21807
- throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9AJSON\u3092\u8AAD\u3081\u307E\u305B\u3093");
21808
- }
21809
- } else {
21810
- try {
21811
- if (lstatSync2(file2).isSymbolicLink())
21812
- throw new SetupError("config_invalid", "Cursor\u306Ehooks\u8A2D\u5B9Asymlink\u306E\u53C2\u7167\u5148\u304C\u3042\u308A\u307E\u305B\u3093");
21813
- } catch (error62) {
21814
- if (error62.code !== "ENOENT")
21815
- throw error62;
21816
- }
21817
- }
21818
- if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
21819
- throw new SetupError("config_invalid", "Cursor\u306Ehooks\u8A2D\u5B9A\u306Fobject\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
21820
- }
21821
- const hooks = { ...current.hooks };
21822
- const entry = { command: cursorParentHookCommand(hook), timeout: 15 };
21823
- for (const event of ["afterMCPExecution", "postToolUse"]) {
21824
- const previous = hooks[event] ?? [];
21825
- if (!Array.isArray(previous))
21826
- throw new SetupError("config_invalid", `Cursor\u306E${event} hook\u5F62\u5F0F\u3092\u8AAD\u3081\u307E\u305B\u3093`);
21827
- const retained = previous.filter((hook2) => !ownsCursorParentHook(profile, hook2));
21828
- const owned = previous.filter((hook2) => ownsCursorParentHook(profile, hook2));
21829
- if (owned.length === 1 && isDeepStrictEqual2(owned[0], entry) && retained.length + 1 === previous.length)
21830
- continue;
21831
- const index = previous.findIndex((hook2) => ownsCursorParentHook(profile, hook2));
21832
- hooks[event] = index < 0 ? [...retained, entry] : previous.map((hook2, i) => i === index ? entry : hook2).filter((hook2, i) => i === index || !ownsCursorParentHook(profile, hook2));
21833
- }
21834
- const next = { ...current, version: current.version ?? 1, hooks };
21835
- if (isDeepStrictEqual2(current, next))
21836
- return "unchanged";
21837
- mkdirSync7(dirname4(target), { recursive: true });
21838
- const temporary = `${target}.${profile.id}-${randomUUID3()}`;
21839
- try {
21840
- writeFileSync5(temporary, `${JSON.stringify(next, null, 2)}
21841
- `, { mode: 384, flag: "wx" });
21842
- if (existsSync9(target))
21843
- copyFileSync2(target, `${target}${profile.backup_suffix}`);
21844
- renameSync3(temporary, target);
21845
- } finally {
21846
- if (existsSync9(temporary))
21847
- unlinkSync4(temporary);
21848
- }
21849
- if (!isDeepStrictEqual2(JSON.parse(readFileSync8(target, "utf8")), next)) {
21850
- throw new SetupError("config_readback_failed", "Cursor\u306Ehook\u767B\u9332\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
21851
- }
21852
- return "configured";
21853
- }
21854
- function removeCursorParentHooks(profile, file2) {
21855
- if (!existsSync9(file2))
21856
- return "unchanged";
21857
- const target = realpathSync4(file2);
21858
- const current = JSON.parse(readFileSync8(target, "utf8"));
21859
- if (!record2(current) || current.hooks !== void 0 && !record2(current.hooks)) {
21860
- throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
21861
- }
21862
- if (current.hooks === void 0)
21863
- return "unchanged";
21864
- const hooks = { ...current.hooks };
21865
- for (const event of ["afterMCPExecution", "postToolUse"]) {
21866
- if (hooks[event] === void 0)
21867
- continue;
21868
- if (!Array.isArray(hooks[event]))
21869
- throw new SetupError("config_invalid", "Cursor\u306Ehook\u8A2D\u5B9A\u3092\u8AAD\u3081\u307E\u305B\u3093");
21870
- const retained = hooks[event].filter((hook) => !ownsCursorParentHook(profile, hook));
21871
- if (retained.length)
21872
- hooks[event] = retained;
21873
- else
21874
- delete hooks[event];
21875
- }
21876
- const next = { ...current, hooks };
21877
- if (isDeepStrictEqual2(current, next))
21878
- return "unchanged";
21879
- const temporary = `${target}.${profile.id}-${randomUUID3()}`;
21880
- try {
21881
- writeFileSync5(temporary, `${JSON.stringify(next, null, 2)}
21882
- `, { mode: 384, flag: "wx" });
21883
- copyFileSync2(target, `${target}${profile.backup_suffix}`);
21884
- renameSync3(temporary, target);
21885
- } finally {
21886
- if (existsSync9(temporary))
21887
- unlinkSync4(temporary);
21888
- }
21889
- if (!isDeepStrictEqual2(JSON.parse(readFileSync8(target, "utf8")), next)) {
21890
- throw new SetupError("config_readback_failed", "Cursor hook\u89E3\u9664\u306E\u8AAD\u623B\u3057\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093");
21891
- }
21892
- return "removed";
21893
- }
21894
-
21895
21950
  export {
21896
21951
  __require,
21897
21952
  __commonJS,
@@ -21929,16 +21984,17 @@ export {
21929
21984
  verifyCodexParent,
21930
21985
  configureCodexSteer,
21931
21986
  claudeParentFromRequest,
21932
- isCursorMcpClient,
21933
- cursorParentHooksRegistered,
21934
- verifyCursorParent,
21935
- handleCursorHook,
21936
- waitProcessCommandLine,
21937
21987
  mergeClaudeParentHooks,
21988
+ claudeParentHooksRegistered,
21938
21989
  removeClaudeParentHooks,
21939
21990
  cursorParentHookCommand,
21940
21991
  mergeCursorParentHooks,
21941
21992
  removeCursorParentHooks,
21993
+ isCursorMcpClient,
21994
+ cursorParentHooksRegistered,
21995
+ verifyCursorParent,
21996
+ handleCursorHook,
21997
+ waitProcessCommandLine,
21942
21998
  openChannel,
21943
21999
  readChannel,
21944
22000
  channelClosed,
package/dist/cli.mjs CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  channelReceiveProcess,
18
18
  claudeHookRoot,
19
19
  claudeParentFromRequest,
20
+ claudeParentHooksRegistered,
20
21
  codexParentFromRequest,
21
22
  configureCodexSteer,
22
23
  cursorHookRoot,
@@ -53,7 +54,7 @@ import {
53
54
  waitProcessCommandLine,
54
55
  withCodexReceiver,
55
56
  withdrawFromChannel
56
- } from "./chunks/chunk-YBOJCW6Y.mjs";
57
+ } from "./chunks/chunk-GH2JG56Y.mjs";
57
58
 
58
59
  // ../../node_modules/qrcode/lib/can-promise.js
59
60
  var require_can_promise = __commonJS({
@@ -12407,9 +12408,9 @@ function registeredTools(target) {
12407
12408
  const entryOf = (value) => (value?.args ?? [])[0];
12408
12409
  if (target === "claude") {
12409
12410
  const entry = readJson(claudeJson()).mcpServers?.[MCP_SERVER];
12410
- const settings = JSON.stringify(readJson(join6(claudeDir(), "settings.json")));
12411
+ const settings = readJson(join6(claudeDir(), "settings.json"));
12411
12412
  const e = entryOf(entry);
12412
- return { mcp: !!entry, hooks: settings.includes(PROFILE.hooks.claude), ...e ? { entry: e } : {} };
12413
+ return { mcp: !!entry, hooks: claudeParentHooksRegistered(PROFILE, settings), ...e ? { entry: e } : {} };
12413
12414
  }
12414
12415
  if (target === "cursor") {
12415
12416
  const entry = readJson(join6(cursorDir(), "mcp.json")).mcpServers?.[MCP_SERVER];
@@ -15788,7 +15789,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:f
15788
15789
  import { basename as basename2, join as join7 } from "node:path";
15789
15790
 
15790
15791
  // src/version.ts
15791
- var VERSION = true ? "0.1.13" : "0.0.0-dev";
15792
+ var VERSION = true ? "0.1.14" : "0.0.0-dev";
15792
15793
  function osName() {
15793
15794
  return process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform;
15794
15795
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "approval-box",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Approval Box connector: lets Claude Code, Codex, Cursor and Grok ask you for a decision, and steers your answer back into the same session.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://approval-box.kitepon.dev",
@@ -28,14 +28,13 @@
28
28
  "typecheck": "tsc -p tsconfig.json --noEmit",
29
29
  "test": "node --test 'test/**/*.test.ts'"
30
30
  },
31
- "dependencies": {},
32
31
  "devDependencies": {
33
32
  "@modelcontextprotocol/sdk": "^1.31.0",
34
33
  "@types/node": "^25.9.1",
35
- "aiterm-steer-delivery": "^0.1.9",
34
+ "@types/qrcode": "^1.5.5",
35
+ "aiterm-steer-delivery": "^0.2.0",
36
36
  "esbuild": "^0.28.2",
37
37
  "qrcode": "^1.5.4",
38
- "@types/qrcode": "^1.5.5",
39
38
  "typescript": "^6.0.3",
40
39
  "zod": "^4.4.3"
41
40
  },