aiterm-mcp 0.61.18 → 0.61.19
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.
- package/CHANGELOG.md +8 -1
- package/README.ja.md +5 -1
- package/README.md +5 -1
- package/dist/errors.js +10 -0
- package/dist/harnesses/claude.js +5 -5
- package/dist/index.js +7 -0
- package/dist/tmux-runtime.js +25 -2
- package/docs/DESIGN.md +3 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.61.19] - 2026-10-26
|
|
11
|
+
|
|
12
|
+
- Fix native Windows shell selection for current Git Bash prompts wrapped across pane columns, preserving unknown and historical prompt boundaries. Both ordinary and marked literal multiline sends retain the correct shell syntax.
|
|
13
|
+
|
|
14
|
+
- Preserve typed Claude authentication preflight failures in ordinary MCP launch errors, distinguishing not authenticated, unconfirmed status and timeout without exposing the native status body or creating a session.
|
|
15
|
+
|
|
10
16
|
## [0.61.18] - 2026-10-25
|
|
11
17
|
|
|
12
18
|
- Fix CURRENT-MENU-1: parse the current Claude auth phase, choices, cursor and occupied code field together; navigation consumes that same parse and rejects stale menus before keys.
|
|
@@ -2868,7 +2874,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
2868
2874
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
2869
2875
|
provenance.
|
|
2870
2876
|
|
|
2871
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.
|
|
2877
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.19...HEAD
|
|
2878
|
+
[0.61.19]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.18...v0.61.19
|
|
2872
2879
|
[0.61.18]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.17...v0.61.18
|
|
2873
2880
|
[0.61.17]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.16...v0.61.17
|
|
2874
2881
|
[0.61.16]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.15...v0.61.16
|
package/README.ja.md
CHANGED
|
@@ -209,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
209
209
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
210
210
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
211
211
|
|
|
212
|
-
**状態:** 開発継続中 · 現行公開版 **v0.61.
|
|
212
|
+
**状態:** 開発継続中 · 現行公開版 **v0.61.19** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
213
213
|
|
|
214
214
|
### 更新と巻き戻し
|
|
215
215
|
|
|
@@ -694,6 +694,8 @@ handoff contextを前置きできる。この任意経路は`throughline >= 0.9.
|
|
|
694
694
|
|
|
695
695
|
SSH先がPowerShellの場合、`mark:true`は現在の標準`PS ...>`プロンプトから方言を判定する。Aiterm自身がmacOS・Linux上でもPowerShell構文を送り、過去の出力に残ったプロンプトは判定に使わない。
|
|
696
696
|
|
|
697
|
+
Windows nativeで現在のGit Bashプロンプトが折り返されている時は、実pane列数と一致する表示幅の連続行だけを接続して方言を補正する。過去の入力済みcommand・別prompt・未知の幅を根拠にBashへ切り替えず、通常送信とmark送信が同じruntimeの判定を使う。
|
|
698
|
+
|
|
697
699
|
`pty_read({ wait: true })`は通常PTYを、process終了/`mark:true` sentinel/`until`一致/shell復帰を伴う出力静止/timeoutの5層で判定する。agent sessionは第6の正確な層を使い、Codexは通常rollout、Grokは通常session event、Claudeはlaunch相関Stop event、Cursorは通常agent transcriptの`turn_ended`を`aiterm-wait --cursor`が観測する。親はブロックもポーリングもしない。
|
|
698
700
|
|
|
699
701
|
### Codex/Claude Code/Cursor親への回答自動配送
|
|
@@ -715,6 +717,8 @@ Cursor親(`clientInfo.name`が`cursor-vscode`)も同じ完了観測と本文
|
|
|
715
717
|
Claudeをリンク経由の`cwd`から起動した場合も、実体パスに対応する会話記録を参照する。
|
|
716
718
|
|
|
717
719
|
|
|
720
|
+
Claudeの認証事前確認による起動拒否は従来のエラー文を保ち、`structuredContent`へ`schema:"aiterm.agent-launch-error.v1"`、`harness:"claude-code"`、`phase:"authentication_preflight"`、`effect:"not_issued"`、`session_created:false`を返す。`code`は`CLAUDE_AUTH_NOT_AUTHENTICATED`(公式statusが未認証)、`CLAUDE_AUTH_STATUS_UNCONFIRMED`(確認不能)、`CLAUDE_AUTH_STATUS_TIMEOUT`(確認が時間切れ)を区別する。資格情報の故障原因やログイン期限切れは断定せず、公式status本文・stderrは返さない。
|
|
721
|
+
|
|
718
722
|
### 別端末のagentを1回で起動する(`remote`)
|
|
719
723
|
|
|
720
724
|
`agent_launch`に`remote`を付けると、SSHで入った別端末のAitermで同じ起動を行う。端末へ入る操作と現地のagentを起動する操作が1回で済み、親から見た完了の届き方はこの端末の子と同じになる。Linux・macOS・Windowsの端末へ同じ依頼を並行して投げる用途を想定している。
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
227
227
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
228
228
|
Release re-registers the Official MCP Registry entry.
|
|
229
229
|
|
|
230
|
-
**Status:** actively maintained · current public release **v0.61.
|
|
230
|
+
**Status:** actively maintained · current public release **v0.61.19** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
|
|
231
231
|
|
|
232
232
|
### Update and rollback
|
|
233
233
|
|
|
@@ -726,6 +726,8 @@ The result (`aiterm.agent-models.v1`) is `{ harness, source, harness_version, de
|
|
|
726
726
|
|
|
727
727
|
The selected harness CLI must be installed and authenticated. Use each product owner's official installer and updater; Aiterm does not distribute alternate CLI tarballs. For Cursor Agent CLI, use `curl https://cursor.com/install -fsS | bash` on macOS/Linux/WSL or `irm 'https://cursor.com/install?win32=true' | iex` on native Windows, authenticate once with `agent login`, and update with `agent update`; Aiterm invokes the unambiguous `cursor-agent` binary. Missing binaries, invalid model/effort values, unavailable Grok catalog models, and nonexistent `cwd` fail before a session exists.
|
|
728
728
|
|
|
729
|
+
Claude authentication preflight refusals retain the existing error text and add `structuredContent` with schema `aiterm.agent-launch-error.v1`, `harness:"claude-code"`, `phase:"authentication_preflight"`, `effect:"not_issued"`, and `session_created:false`. `code` distinguishes `CLAUDE_AUTH_NOT_AUTHENTICATED`, `CLAUDE_AUTH_STATUS_UNCONFIRMED`, and `CLAUDE_AUTH_STATUS_TIMEOUT`. These describe the official status check, not the cause of a credential failure or a confirmed login expiry. Native status bodies and stderr are omitted.
|
|
730
|
+
|
|
729
731
|
Set `throughline_source_session` together with a non-empty mission in `prompt` to prepend
|
|
730
732
|
Throughline's read-only handoff context. This optional route requires `throughline >= 0.9.0`,
|
|
731
733
|
cannot be combined with `launch_operation_id`, and leaves the source session's database ownership
|
|
@@ -739,6 +741,8 @@ When an agent's answer is longer than the on-screen tail (pane height ≈ 24 lin
|
|
|
739
741
|
|
|
740
742
|
For PowerShell over SSH, `mark:true` recognizes the current standard `PS ...>` prompt and emits PowerShell syntax even when Aiterm runs on macOS or Linux. A prompt left in earlier output is not used to select the syntax.
|
|
741
743
|
|
|
744
|
+
On native Windows, a wrapped current Git Bash prompt is reconciled with the measured pane columns before choosing shell syntax. Only full-width continuation rows of the current empty prompt are joined; earlier commands, other prompts and unknown widths do not authorize a correction. Ordinary and marked sends use the same runtime decision.
|
|
745
|
+
|
|
742
746
|
`pty_read({ wait: true })` decides "is the command done?" via five layers: process exit / a `mark:true` sentinel / an `until` match / output quiescence with shell return / timeout. `mark` emits the shell's exit status on POSIX shells and `0` (success) or `1` (failure) on PowerShell; fish/csh/tcsh are rejected before send because they do not share either status syntax. When `mark` or `until` is active, that requested evidence takes precedence and a momentarily quiet shell cannot complete the read as quiescent. Agent sessions add a sixth exact layer: Codex observes normal rollout `task_complete`; Grok observes normal session `turn_ended`; Claude observes its additive launch-correlated Stop event; Cursor observes `turn_ended(status:"success")` in the launch-bound normal agent transcript. `aiterm-wait --cursor` performs that harness-specific observation without the parent blocking or polling. Pre-send readiness failures are MCP errors, and late completion remains recoverable without resending.
|
|
743
747
|
|
|
744
748
|
### Completion push for parent agents (`aiterm-wait`)
|
package/dist/errors.js
CHANGED
|
@@ -7,6 +7,16 @@ export class AitermError extends Error {
|
|
|
7
7
|
this.code = code;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
/** A harness authentication check refused launch before any session creation.
|
|
11
|
+
* Keep the native CLI's status body and credential diagnostics out of the wire. */
|
|
12
|
+
export class AgentAuthenticationPreflightError extends AitermError {
|
|
13
|
+
reasonCode;
|
|
14
|
+
constructor(reasonCode, message) {
|
|
15
|
+
super(message, 2);
|
|
16
|
+
this.reasonCode = reasonCode;
|
|
17
|
+
this.name = "AgentAuthenticationPreflightError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
export class TelemetryOwnedError extends AitermError {
|
|
11
21
|
telemetryCode;
|
|
12
22
|
constructor(message, code, telemetryCode, cause) {
|
package/dist/harnesses/claude.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as os from "node:os";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { AitermError } from "../errors.js";
|
|
10
|
+
import { AitermError, AgentAuthenticationPreflightError } from "../errors.js";
|
|
11
11
|
import { modeBitsWorldAccessible } from "../tmux-runtime.js";
|
|
12
12
|
import { runAgentProtocolCommand, spawnAgentControlCommand } from "../agent-resolver.js";
|
|
13
13
|
import { catalogInvalid, catalogUnavailable, checkedCatalog, findJsonLine, processSummary } from "../model-catalog.js";
|
|
@@ -289,13 +289,13 @@ export function assertClaudeAuthenticationReady(bin) {
|
|
|
289
289
|
typeof status === "object" &&
|
|
290
290
|
!Array.isArray(status) &&
|
|
291
291
|
status.loggedIn === false) {
|
|
292
|
-
throw new
|
|
292
|
+
throw new AgentAuthenticationPreflightError("CLAUDE_AUTH_NOT_AUTHENTICATED", "Claude Codeの認証を利用できません。sessionは作成していません。" +
|
|
293
293
|
"通常端末で `claude doctor` を実行し、Keychain/credential storeを直してから一度だけ `claude auth login` を実行してください。" +
|
|
294
|
-
"aiterm相関付きClaude session内で /login を繰り返さないでください。"
|
|
294
|
+
"aiterm相関付きClaude session内で /login を繰り返さないでください。");
|
|
295
295
|
}
|
|
296
296
|
const timedOut = result.error && result.error.code === "ETIMEDOUT";
|
|
297
|
-
throw new
|
|
298
|
-
"`claude auth status --json` と `claude doctor` が成功することを通常端末で確認してください。"
|
|
297
|
+
throw new AgentAuthenticationPreflightError(timedOut ? "CLAUDE_AUTH_STATUS_TIMEOUT" : "CLAUDE_AUTH_STATUS_UNCONFIRMED", `Claude Codeの認証状態を起動前に確認できません${timedOut ? "(5秒でtimeout)" : ""}。sessionは作成していません。` +
|
|
298
|
+
"`claude auth status --json` と `claude doctor` が成功することを通常端末で確認してください。");
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
301
301
|
* 起動時に席へ渡す、Aitermの届け方の説明(ADR 0116)。Claude Codeは貼り付けを包み、包みの中の指示を利用者の言葉と分けて読む。
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { AuthResponseError, authUiSchema, authBindingSchema, authResponseSchema } from "./agent-auth-ui.js";
|
|
3
|
+
import { AgentAuthenticationPreflightError } from "./errors.js";
|
|
3
4
|
/**
|
|
4
5
|
* aiterm-mcp — AI が握るローカル永続端末を stdio MCP サーバとして公開する(Node/TS 版)。
|
|
5
6
|
*
|
|
@@ -170,6 +171,12 @@ export function createAitermServer(options = {}) {
|
|
|
170
171
|
}
|
|
171
172
|
function fail(e) {
|
|
172
173
|
const msg = e instanceof Error ? e.message : String(e);
|
|
174
|
+
if (e instanceof AgentAuthenticationPreflightError)
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: "aiterm: " + msg }], isError: true,
|
|
177
|
+
structuredContent: { schema: "aiterm.agent-launch-error.v1", harness: "claude-code", code: e.reasonCode,
|
|
178
|
+
phase: "authentication_preflight", effect: "not_issued", session_created: false, message: msg },
|
|
179
|
+
};
|
|
173
180
|
return { content: [{ type: "text", text: "aiterm: " + msg }], isError: true };
|
|
174
181
|
}
|
|
175
182
|
/**
|
package/dist/tmux-runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as path from "node:path";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import stringWidth from 'string-width';
|
|
10
11
|
import { AitermError, ptyDependencyError } from "./errors.js";
|
|
11
12
|
import { guardTmuxExecution, beforeExecutionEffect, hasExecutionCreationScope, prepareExecutionCreation, captureExecutionCreatedNative } from "./execution-effect.js";
|
|
12
13
|
import { ExecutionError } from "./execution-wire.js";
|
|
@@ -605,7 +606,7 @@ export function atomicShellMultiline(text, foreground) {
|
|
|
605
606
|
// printf %bで本文を復元する。末尾LFの除去はpty_sendのsubmit Enterと同値。
|
|
606
607
|
return `eval "$(command printf '%b' '${quoted}')"`;
|
|
607
608
|
}
|
|
608
|
-
export function markShellCommand(foreground, screen, platform = process.platform) {
|
|
609
|
+
export function markShellCommand(foreground, screen, platform = process.platform, columns) {
|
|
609
610
|
if (platform !== "win32" && foreground !== "ssh")
|
|
610
611
|
return foreground;
|
|
611
612
|
// psmuxの前面名はprocess snapshotやOSCの古い値を返しうる。
|
|
@@ -615,6 +616,22 @@ export function markShellCommand(foreground, screen, platform = process.platform
|
|
|
615
616
|
return "pwsh";
|
|
616
617
|
if (platform === "win32" && /(?:^|\n)[^\r\n]*\b(?:MINGW(?:32|64)|UCRT64|MSYS)\b[^\r\n]*\r?\n\$\s*$/.test(current))
|
|
617
618
|
return "bash";
|
|
619
|
+
if (platform === "win32" && columns !== undefined && Number.isSafeInteger(columns) && columns > 0 && columns <= 10000) {
|
|
620
|
+
const lines = current.split(/\r?\n/u);
|
|
621
|
+
if (/^\$\s*$/u.test(lines.at(-1) ?? "")) {
|
|
622
|
+
let at = lines.length - 2, header = lines[at] ?? "";
|
|
623
|
+
// psmux may retain soft wraps even with capture-pane -J. Reconstruct
|
|
624
|
+
// only this trailing prompt's full-width rows, stopping at any prior
|
|
625
|
+
// command/prompt boundary. Never join arbitrary old output lines.
|
|
626
|
+
while (at > 0 && !/\b(?:MINGW(?:32|64)|UCRT64|MSYS)\b/u.test(header)) {
|
|
627
|
+
if ([lines[at], lines[at - 1]].some(line => /^\s*(?:[$>#❯]|PS\s)/u.test(line) || /[\x00-\x1f\x7f]/u.test(line)) || stringWidth(lines[at - 1]) !== columns)
|
|
628
|
+
break;
|
|
629
|
+
header = lines[--at] + header;
|
|
630
|
+
}
|
|
631
|
+
if (/^[^\r\n]*\b(?:MINGW(?:32|64)|UCRT64|MSYS)\b[^\r\n]*$/u.test(header))
|
|
632
|
+
return "bash";
|
|
633
|
+
}
|
|
634
|
+
}
|
|
618
635
|
return foreground;
|
|
619
636
|
}
|
|
620
637
|
/** The runtime owns platform foreground queries and shell-dialect reconciliation. */
|
|
@@ -624,7 +641,13 @@ export function paneShellForSubmission(name) {
|
|
|
624
641
|
if (!isWin && foreground !== "ssh")
|
|
625
642
|
return foreground;
|
|
626
643
|
const screen = tmuxCommand(true, "capture-pane", "-p", "-J", "-t", name, "-S", "-4");
|
|
627
|
-
|
|
644
|
+
const text = screen.code === 0 ? screen.stdout : "";
|
|
645
|
+
const selected = markShellCommand(foreground, text);
|
|
646
|
+
if (!isWin || selected !== foreground || selected === "bash" || !/(?:^|\n)\$\s*$/u.test(text.trimEnd()))
|
|
647
|
+
return selected;
|
|
648
|
+
const width = tmuxCommand(true, "display-message", "-p", "-t", name, "#{pane_width}");
|
|
649
|
+
const columns = width.code === 0 && /^\d+\s*$/u.test(width.stdout) ? Number(width.stdout.trim()) : undefined;
|
|
650
|
+
return markShellCommand(foreground, text, "win32", columns);
|
|
628
651
|
}
|
|
629
652
|
/** Common send code supplies intent; the runtime decides whether/how to encode it. */
|
|
630
653
|
export function adaptShellSubmission(name, text, options = {}) {
|
package/docs/DESIGN.md
CHANGED
|
@@ -63,6 +63,7 @@ argv・lineage・metadata・native admission・receipt・TUI本文は変えず
|
|
|
63
63
|
同じ入口へ渡す。共通sendはraw/TUI/startupという送信意図と符号化前の上限検査を所有し、
|
|
64
64
|
Windowsの前面名だけから方言を決めない。psmuxのprocess snapshot/OSCが古い時は、
|
|
65
65
|
現在のPowerShell又はGit Bash promptで双方向に補正する。過去のpromptは使わない。
|
|
66
|
+
psmuxが`capture-pane -J`でも現在のGit Bash promptを折り返す場合は、実測したpane列数とUnicode表示セル数が一致する連続行だけを現在promptへ接続する。入力済みの`$ command`・別prompt・制御文字を跨がず、列数が不明/不一致なら補正しない。表示幅はNode18対応string-width 7.2.0へ委ね、文字数で日本語/emojiの幅を代用しない。通常の未折返しpromptやPOSIXのshell判定に追加の列数照会は行わない。
|
|
66
67
|
|
|
67
68
|
TUIの準備判定、送信文の入力欄への残存、完了後の画面安定待ちは`agent-screen-observation.ts`が所有する。
|
|
68
69
|
このmoduleはcoreをimportせず、既存harness adapterと渡されたsample・sleep・時計だけで判断する。
|
|
@@ -501,6 +502,8 @@ Claude authentication URL receipts recognize the official claude.com/cai/oauth/a
|
|
|
501
502
|
|
|
502
503
|
### 認証画面と専用応答
|
|
503
504
|
|
|
505
|
+
Claude起動前の公式auth status拒否はharnessが`AgentAuthenticationPreflightError`として所有し、通常MCPのfailが`aiterm.agent-launch-error.v1`へ投影する。既存数値exit codeと文言を維持し、未認証・確認不能・timeoutの機械code、authentication_preflight/not_issued/session_created:falseだけを追加する。CLI status本文・stderr・資格・推定したexpiry原因は持ち出さず、この拒否は認証PTYの入力や起動後turnの終端とは分ける。
|
|
506
|
+
|
|
504
507
|
`agent-auth-ui.ts`が既v1 receiptに追加する`auth_ui`とrespondの型を所有する。Native harnessが公式URL・device code・空code欄・現在のラベル付き選択肢を識別し、Rootはキー列を作らない。不明な入力段階はunavailable。coreが既auth metadataのdev/ino/作成・更新時刻、native session/pane/server/PID開始識別を照合する。legacyのlaunch UUID欠測はnullのまま扱い、無関係なflowをadoptしない。新startは既launch_id欄を必ず設定する。
|
|
505
508
|
|
|
506
509
|
respondは既session send lock内で現在の空欄/段階と世代を再検証し、専用auth paneのpipe loggingを入力前に停止する。新auth paneは公式CLI起動前にloggingを停止する。通常send・lastcmd・mark・turn・journalを通さず、POSIX tmux load-bufferへstdinで送り、immutable pane IDへpaste/Enterを一回送る。secretはargv/fileへ渡さずbufferを消す。CLIの独自資格保存はCLI所有で、Aitermの非保存保証とは区別する。入力前の拒否はnot_issued、入力開始後の所有/結果欠測はunknown。元画面へ勝手に戻す・再送する回復はしない。Windowsは既psmux送信だけで前面所有を証明できないため専用respondを未対応として拒否する。macOSはPOSIX経路のsource実装であり実OS受入は別gate。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.61.
|
|
3
|
+
"version": "0.61.19",
|
|
4
4
|
"mcpName": "io.github.kitepon/aiterm-mcp",
|
|
5
5
|
"description": "Persistent terminal MCP with one harness-based launcher for Claude Code, Codex CLI, Grok CLI, and Cursor Agent CLI, plus durable PTYs for SSH, containers, and REPLs.",
|
|
6
6
|
"keywords": [
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
76
76
|
"aiterm-steer-delivery": "^0.5.0",
|
|
77
|
+
"string-width": "7.2.0",
|
|
77
78
|
"ws": "8.21.3",
|
|
78
79
|
"zod": "^4.4.3"
|
|
79
80
|
},
|