aiterm-mcp 0.29.3 → 0.29.6
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/dist/core.js +49 -38
- package/dist/psmux-send-worker.js +99 -0
- package/dist/runtime-error-store.js +13 -3
- package/dist/tmux-runtime.js +23 -0
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -13,7 +13,7 @@ import * as path from "node:path";
|
|
|
13
13
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
14
|
import * as rtk from "./rtk.js";
|
|
15
15
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
16
|
-
import { isWin, SOCKDIR, tmuxCommand, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
16
|
+
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
17
17
|
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
18
18
|
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
19
19
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexLaunchBlockingDialog, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./harnesses/codex.js";
|
|
@@ -75,7 +75,7 @@ const MAX_SEND_BYTES = 64 * 1024;
|
|
|
75
75
|
const PTY_PASTE_CHUNK_BYTES = 256;
|
|
76
76
|
const PTY_PASTE_CHUNK_PAUSE_MS = 10;
|
|
77
77
|
const PTY_PASTE_PAUSE_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
78
|
-
const SESSION_SEND_LOCK_WAIT_MS = 10_000;
|
|
78
|
+
const SESSION_SEND_LOCK_WAIT_MS = isWin ? 130_000 : 10_000;
|
|
79
79
|
const SESSION_SEND_LOCK_POLL_MS = 25;
|
|
80
80
|
// 安全: send 前に弾く破壊的コマンド(外部システム境界の防御)
|
|
81
81
|
const DESTRUCTIVE = [
|
|
@@ -121,6 +121,15 @@ function paneCurrentCommand(name) {
|
|
|
121
121
|
return "";
|
|
122
122
|
return normalizePaneCommand(r.stdout.trim());
|
|
123
123
|
}
|
|
124
|
+
function paneCurrentCommandForMark(name) {
|
|
125
|
+
const foreground = paneCurrentCommand(name);
|
|
126
|
+
if (!isWin || foreground === "powershell" || foreground === "pwsh")
|
|
127
|
+
return foreground;
|
|
128
|
+
// psmuxはnew-session直後だけpane_current_commandへ起動元shellを返すことがある。
|
|
129
|
+
// 現在の末尾画面にPowerShell promptが描画済みなら、mark構文はその実効shellへ合わせる。
|
|
130
|
+
const screen = captureScreen(name, 4).replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "");
|
|
131
|
+
return /(?:^|\n)PS [^\r\n]*>\s*$/m.test(screen) ? "pwsh" : foreground;
|
|
132
|
+
}
|
|
124
133
|
function pasteBufferSupportsNoSanitizeFlag() {
|
|
125
134
|
const listed = tmux("list-commands");
|
|
126
135
|
if (listed.code !== 0) {
|
|
@@ -658,7 +667,7 @@ export function send(name, text, o = {}) {
|
|
|
658
667
|
if (o.mark) {
|
|
659
668
|
// mark の sentinel は POSIX シェル構文。前面が fish/csh/tcsh 等の非 POSIX 対話シェルだと "$?" が
|
|
660
669
|
// 壊れて sentinel が成立しない。黙って壊れた完了検出を作らず、明示エラーで until を促す(B8)。
|
|
661
|
-
fg =
|
|
670
|
+
fg = paneCurrentCommandForMark(name);
|
|
662
671
|
if (NON_POSIX_MARK_SHELLS.has(fg)) {
|
|
663
672
|
throw new AitermError(`mark は POSIX シェル(bash/sh/zsh/dash)前提です。前面が ${fg} のため sentinel の "$?" が` +
|
|
664
673
|
`正しく展開されません。until で完了パターンを指定してください。`, 2);
|
|
@@ -702,42 +711,44 @@ export function send(name, text, o = {}) {
|
|
|
702
711
|
/* noop */
|
|
703
712
|
}
|
|
704
713
|
}
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
for (let i = 0; i < chunks.length; i += 1) {
|
|
712
|
-
const bufferName = `${bufferBase}-${i}`;
|
|
713
|
-
const partial = i > 0
|
|
714
|
-
? " 先行chunkはPTYに入力済みでEnterは未送信です。再送前に入力を確認・消去してください。"
|
|
715
|
-
: "";
|
|
716
|
-
const loaded = loadPtyBufferChunk(true, bufferName, chunks[i]);
|
|
717
|
-
if (loaded.code !== 0) {
|
|
718
|
-
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
719
|
-
throw new AitermError(`tmux bufferへの送信準備に失敗しました` +
|
|
720
|
-
`(chunk ${i + 1}/${chunks.length}): ${loaded.stderr.trim() || `code=${loaded.code}`}.${partial}`, 2);
|
|
721
|
-
}
|
|
722
|
-
// -Sは対応新版だけでvis(3)制御文字変換を無効化する。
|
|
723
|
-
// send 自身の raw/sanitize 契約だけを真実とし、tmux 側で黙って再変換させない。
|
|
724
|
-
const pasteArgs = pasteBufferBaseArgs();
|
|
725
|
-
if (o.bracketedPaste)
|
|
726
|
-
pasteArgs.push("-p");
|
|
727
|
-
if (pasteSupportsNoSanitize)
|
|
728
|
-
pasteArgs.push("-S");
|
|
729
|
-
pasteArgs.push("-b", bufferName, "-t", name);
|
|
730
|
-
const pasted = tmux(...pasteArgs);
|
|
731
|
-
if (pasted.code !== 0) {
|
|
732
|
-
// paste 失敗時は -d で消えないbufferを明示的に掃除する。元エラーを優先する。
|
|
733
|
-
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
734
|
-
throw new AitermError(`tmux bufferのPTY送信に失敗しました` +
|
|
735
|
-
`(chunk ${i + 1}/${chunks.length}): ${pasted.stderr.trim() || `code=${pasted.code}`}.${partial}`, 2);
|
|
714
|
+
// POSIX tmuxはUTF-8安全な256byte pasteへ分割する。Windows psmuxは認証済み
|
|
715
|
+
// server commandでpayload全体を単一SendBytesへ変換し、別processとの交差を構造的に防ぐ。
|
|
716
|
+
if (isWin) {
|
|
717
|
+
const sent = sendPsmuxPayload(true, name, text, !!o.bracketedPaste);
|
|
718
|
+
if (sent.code !== 0) {
|
|
719
|
+
throw new AitermError(`psmuxのPTY送信に失敗しました: ${sent.stderr.trim() || `code=${sent.code}`}`, 2);
|
|
736
720
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const chunks = splitPtyText(text);
|
|
724
|
+
const pasteSupportsNoSanitize = pasteBufferSupportsNoSanitizeFlag();
|
|
725
|
+
const bufferBase = `aiterm-${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
726
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
727
|
+
const bufferName = `${bufferBase}-${i}`;
|
|
728
|
+
const partial = i > 0
|
|
729
|
+
? " 先行chunkはPTYに入力済みでEnterは未送信です。再送前に入力を確認・消去してください。"
|
|
730
|
+
: "";
|
|
731
|
+
const loaded = loadPtyBufferChunk(true, bufferName, chunks[i]);
|
|
732
|
+
if (loaded.code !== 0) {
|
|
733
|
+
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
734
|
+
throw new AitermError(`tmux bufferへの送信準備に失敗しました` +
|
|
735
|
+
`(chunk ${i + 1}/${chunks.length}): ${loaded.stderr.trim() || `code=${loaded.code}`}.${partial}`, 2);
|
|
736
|
+
}
|
|
737
|
+
const pasteArgs = pasteBufferBaseArgs();
|
|
738
|
+
if (o.bracketedPaste)
|
|
739
|
+
pasteArgs.push("-p");
|
|
740
|
+
if (pasteSupportsNoSanitize)
|
|
741
|
+
pasteArgs.push("-S");
|
|
742
|
+
pasteArgs.push("-b", bufferName, "-t", name);
|
|
743
|
+
const pasted = tmux(...pasteArgs);
|
|
744
|
+
if (pasted.code !== 0) {
|
|
745
|
+
tmuxCleanup("delete-buffer", "-b", bufferName);
|
|
746
|
+
throw new AitermError(`tmux bufferのPTY送信に失敗しました` +
|
|
747
|
+
`(chunk ${i + 1}/${chunks.length}): ${pasted.stderr.trim() || `code=${pasted.code}`}.${partial}`, 2);
|
|
748
|
+
}
|
|
749
|
+
if (i + 1 < chunks.length) {
|
|
750
|
+
Atomics.wait(PTY_PASTE_PAUSE_BUFFER, 0, 0, PTY_PASTE_CHUNK_PAUSE_MS);
|
|
751
|
+
}
|
|
741
752
|
}
|
|
742
753
|
}
|
|
743
754
|
if (enter) {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
const MAX_PAYLOAD_BYTES = 64 * 1024 + 12;
|
|
6
|
+
const PTY_CHUNK_BYTES = 256;
|
|
7
|
+
const MAX_FINAL_DRAIN_MS = 3_000;
|
|
8
|
+
const SESSION_BASE_RE = /^[A-Za-z0-9_-]{1,160}$/;
|
|
9
|
+
const KEY_RE = /^[0-9a-f]{16}$/i;
|
|
10
|
+
async function readPayload() {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let total = 0;
|
|
13
|
+
for await (const chunk of process.stdin) {
|
|
14
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
15
|
+
total += bytes.length;
|
|
16
|
+
if (total > MAX_PAYLOAD_BYTES)
|
|
17
|
+
throw new Error(`payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);
|
|
18
|
+
chunks.push(bytes);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks);
|
|
21
|
+
}
|
|
22
|
+
function psmuxDataDir() {
|
|
23
|
+
const override = process.env.PSMUX_DATA_DIR;
|
|
24
|
+
if (override) {
|
|
25
|
+
if (!path.isAbsolute(override))
|
|
26
|
+
throw new Error("PSMUX_DATA_DIR must be absolute");
|
|
27
|
+
return override;
|
|
28
|
+
}
|
|
29
|
+
return path.join(process.env.USERPROFILE || os.homedir(), ".psmux");
|
|
30
|
+
}
|
|
31
|
+
async function sendCommand(port, key, command, expectedResponse) {
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const socket = net.createConnection({ host: "127.0.0.1", port });
|
|
34
|
+
let response = "";
|
|
35
|
+
let settled = false;
|
|
36
|
+
const finish = (error) => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
settled = true;
|
|
40
|
+
socket.destroy();
|
|
41
|
+
if (error)
|
|
42
|
+
reject(error);
|
|
43
|
+
else
|
|
44
|
+
resolve();
|
|
45
|
+
};
|
|
46
|
+
socket.setTimeout(2_000, () => finish(new Error("psmux command timed out")));
|
|
47
|
+
socket.on("connect", () => socket.end(`AUTH ${key}\n${command}\n`));
|
|
48
|
+
socket.on("data", (chunk) => {
|
|
49
|
+
response += chunk.toString("utf8");
|
|
50
|
+
});
|
|
51
|
+
socket.on("end", () => {
|
|
52
|
+
if (response.split(/\r?\n/).includes("OK") &&
|
|
53
|
+
(expectedResponse == null || response.includes(expectedResponse)))
|
|
54
|
+
finish();
|
|
55
|
+
else
|
|
56
|
+
finish(new Error(`psmux authentication failed: ${response.trim() || "empty response"}`));
|
|
57
|
+
});
|
|
58
|
+
socket.on("error", (error) => finish(error));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function delay(ms) {
|
|
62
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
}
|
|
64
|
+
async function send() {
|
|
65
|
+
const sessionBase = process.argv[2] ?? "";
|
|
66
|
+
if (!SESSION_BASE_RE.test(sessionBase))
|
|
67
|
+
throw new Error("invalid psmux session base");
|
|
68
|
+
const payload = await readPayload();
|
|
69
|
+
const dir = psmuxDataDir();
|
|
70
|
+
const portText = fs.readFileSync(path.join(dir, `${sessionBase}.port`), "utf8").trim();
|
|
71
|
+
const key = fs.readFileSync(path.join(dir, `${sessionBase}.key`), "utf8").trim();
|
|
72
|
+
const port = Number(portText);
|
|
73
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
74
|
+
throw new Error("invalid psmux port");
|
|
75
|
+
if (!KEY_RE.test(key))
|
|
76
|
+
throw new Error("invalid psmux auth key");
|
|
77
|
+
const subcommands = [];
|
|
78
|
+
for (let offset = 0; offset < payload.length; offset += PTY_CHUNK_BYTES) {
|
|
79
|
+
const chunk = payload.subarray(offset, offset + PTY_CHUNK_BYTES);
|
|
80
|
+
const hex = Array.from(chunk, (byte) => byte.toString(16).padStart(2, "0")).join(" ");
|
|
81
|
+
subcommands.push(`send -H ${hex}`);
|
|
82
|
+
}
|
|
83
|
+
const chunkDrainMs = payload.length > 1024 ? 300 : 100;
|
|
84
|
+
for (const command of subcommands) {
|
|
85
|
+
await sendCommand(port, key, command);
|
|
86
|
+
await delay(chunkDrainMs);
|
|
87
|
+
}
|
|
88
|
+
const completionMarker = `AITERM_PSMUX_SEND_DONE_${process.pid}`;
|
|
89
|
+
await sendCommand(port, key, `display-message -p ${completionMarker}`, completionMarker);
|
|
90
|
+
const finalDrainMs = Math.min(MAX_FINAL_DRAIN_MS, Math.max(200, Math.ceil(payload.length / 1024) * 500));
|
|
91
|
+
await delay(finalDrainMs);
|
|
92
|
+
if (process.env.AITERM_PSMUX_DEBUG === "1") {
|
|
93
|
+
process.stderr.write(`psmux-send-worker debug: chunks=${subcommands.length} sent=${subcommands.length}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
send().catch((error) => {
|
|
97
|
+
process.stderr.write(`psmux-send-worker: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
});
|
|
@@ -11,7 +11,12 @@ const STORE_SCHEMA = "aiterm-mcp.runtime-errors.v1";
|
|
|
11
11
|
const STATE_SCHEMA = "1.0";
|
|
12
12
|
const MAX_CONFIG_BYTES = 16 * 1024;
|
|
13
13
|
const MAX_STORE_BYTES = 1024 * 1024;
|
|
14
|
-
const
|
|
14
|
+
const POSIX_WORKER_TIMEOUT_MS = 2_000;
|
|
15
|
+
// Windows はprivate DACLの適用とreadbackをPowerShell 7境界で行う。診断は最大2回、
|
|
16
|
+
// 記録はlock queueとstate更新で複数回この境界を通るため、process起動だけを想定した
|
|
17
|
+
// POSIXの2秒上限では正常処理まで強制終了する。各境界自身の5秒上限は維持し、worker全体だけ分ける。
|
|
18
|
+
const WINDOWS_DIAGNOSTIC_WORKER_TIMEOUT_MS = 12_000;
|
|
19
|
+
const WINDOWS_RECORD_WORKER_TIMEOUT_MS = 30_000;
|
|
15
20
|
export const RUNTIME_ERROR_DEFINITIONS = Object.freeze({
|
|
16
21
|
"AITERM.PTY_DEPENDENCY_UNAVAILABLE": Object.freeze({
|
|
17
22
|
component: "pty-dependency", message_template: "PTY dependency is unavailable", severity: "high",
|
|
@@ -635,6 +640,11 @@ export class RuntimeErrorStore {
|
|
|
635
640
|
}
|
|
636
641
|
const WORKER = fileURLToPath(new URL("./runtime-error-worker.js", import.meta.url));
|
|
637
642
|
function fixedStoreFailure(stderr) { stderr("aiterm: runtime error store unavailable\n"); }
|
|
643
|
+
function defaultWorkerTimeoutMs(action) {
|
|
644
|
+
if (process.platform !== "win32")
|
|
645
|
+
return POSIX_WORKER_TIMEOUT_MS;
|
|
646
|
+
return action === "record" ? WINDOWS_RECORD_WORKER_TIMEOUT_MS : WINDOWS_DIAGNOSTIC_WORKER_TIMEOUT_MS;
|
|
647
|
+
}
|
|
638
648
|
export function recordRuntimeError(code, options = {}) {
|
|
639
649
|
validateRuntimeObservation({ code });
|
|
640
650
|
const stderr = options.stderr ?? ((line) => process.stderr.write(line));
|
|
@@ -647,7 +657,7 @@ export function recordRuntimeError(code, options = {}) {
|
|
|
647
657
|
const child = spawn(process.execPath, [options.workerPath ?? WORKER, "record", code], {
|
|
648
658
|
stdio: "ignore", windowsHide: true, env: process.env,
|
|
649
659
|
});
|
|
650
|
-
const timer = setTimeout(() => { report(); forceKill(child); }, options.timeoutMs ??
|
|
660
|
+
const timer = setTimeout(() => { report(); forceKill(child); }, options.timeoutMs ?? defaultWorkerTimeoutMs("record"));
|
|
651
661
|
timer.unref();
|
|
652
662
|
child.once("error", report);
|
|
653
663
|
child.once("exit", (exitCode, signal) => { clearTimeout(timer); if (exitCode !== 0 || signal)
|
|
@@ -686,7 +696,7 @@ export async function runtimeErrorStoreDiagnostic(options = {}) {
|
|
|
686
696
|
finish(fallback);
|
|
687
697
|
return;
|
|
688
698
|
}
|
|
689
|
-
const timer = setTimeout(() => { forceKill(child); finish(fallback); }, options.timeoutMs ??
|
|
699
|
+
const timer = setTimeout(() => { forceKill(child); finish(fallback); }, options.timeoutMs ?? defaultWorkerTimeoutMs("diagnostic"));
|
|
690
700
|
child.stdout.setEncoding("utf8");
|
|
691
701
|
child.stdout.on("data", (chunk) => { if (stdout.length <= 4096)
|
|
692
702
|
stdout += chunk; });
|
package/dist/tmux-runtime.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { AitermError, ptyDependencyError } from "./errors.js";
|
|
10
11
|
// Windows ネイティブには tmux が無いため、tmux CLI 互換の native psmux を叩く
|
|
11
12
|
// (POSIX = Linux/WSL2/macOS は従来どおり tmux を直接叩く。WSL 橋は e3f5fc8 で全廃)。
|
|
@@ -129,6 +130,28 @@ export function tmuxCommandWithInput(observe, input, ...args) {
|
|
|
129
130
|
export function tmuxCommand(observe, ...args) {
|
|
130
131
|
return tmuxCommandWithInput(observe, undefined, ...args);
|
|
131
132
|
}
|
|
133
|
+
export function sendPsmuxPayload(observe, sessionName, text, bracketedPaste) {
|
|
134
|
+
if (!isWin)
|
|
135
|
+
return { code: 1, stdout: "", stderr: "psmux direct send is Windows-only" };
|
|
136
|
+
ensureWinPsmux(observe);
|
|
137
|
+
const worker = fileURLToPath(new URL("./psmux-send-worker.js", import.meta.url));
|
|
138
|
+
const body = bracketedPaste ? `\x1b[200~${text}\x1b[201~` : text;
|
|
139
|
+
const result = spawnSync(process.execPath, [worker, `${WIN_NS}__${sessionName}`], {
|
|
140
|
+
encoding: "utf8",
|
|
141
|
+
maxBuffer: 1024 * 1024,
|
|
142
|
+
input: Buffer.from(body, "utf8"),
|
|
143
|
+
env: tmuxSpawnEnv(),
|
|
144
|
+
timeout: 120_000,
|
|
145
|
+
});
|
|
146
|
+
if (result.error) {
|
|
147
|
+
return {
|
|
148
|
+
code: 1,
|
|
149
|
+
stdout: result.stdout ?? "",
|
|
150
|
+
stderr: `${result.error.code ?? "worker error"}: ${result.error.message}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { code: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
154
|
+
}
|
|
132
155
|
// psmux の load-buffer は stdin (`-`) 非対応で path 位置引数だけを取る。
|
|
133
156
|
// Windows は owner-only の SOCKDIR 内へ一時ファイル経由で渡し、POSIX は stdin で渡す。
|
|
134
157
|
export function loadPtyBufferChunk(observe, bufferName, chunk) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.6",
|
|
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": [
|