aiterm-mcp 0.28.2 → 0.28.4
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/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/agent-resolver.js +8 -2
- package/dist/agent-shared.js +2 -25
- package/dist/claude-stop-hook.js +1 -21
- package/dist/grok-stop-hook.js +1 -24
- package/dist/index.js +2 -1
- package/dist/runtime-error-os.js +8 -5
- package/dist/state-root.js +30 -0
- package/dist/windows-powershell.js +42 -0
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -459,7 +459,7 @@ handoff contextを前置きできる。この任意経路は`throughline >= 0.9.
|
|
|
459
459
|
- **Node.js >= 18**
|
|
460
460
|
- **tmux または psmux**(実行時の前提)
|
|
461
461
|
- **macOS / Linux / WSL2** は tmux を直接使う。macOS は同梱されないので `brew install tmux` で導入する。MCP クライアントがターミナルでなく **GUI から起動**された場合、Homebrew の bin(Apple Silicon: `/opt/homebrew/bin`、Intel: `/usr/local/bin`)が `PATH` に入らないことがある。その場合 aiterm が自動で探索するか、**`AITERM_TMUX=/path/to/tmux`** で明示指定する。
|
|
462
|
-
- **Windows ネイティブ**は WSL を使わず、tmux CLI互換の [psmux](https://github.com/psmux/psmux) **3.3.8
|
|
462
|
+
- **Windows ネイティブ**は WSL を使わず、tmux CLI互換の [psmux](https://github.com/psmux/psmux) **3.3.8以上**をAitermのterminal/session multiplexer backendとして使う(`winget install marlocarlo.psmux`)。**psmuxはshellではない。** `pty_open`の既定shellはPowerShell 7(`pwsh.exe`)で、Windows PowerShell 5.1・PowerShell 6・`cmd.exe`へfallbackしない。5.1しかなければMicrosoft公式installer/package managerで7を導入してから使う。Git for Windowsはharness launcherが内部で明示するBash shell用に引き続き必要。解決先は **`AITERM_PSMUX`**/**`AITERM_BASH`** で上書きできる。他製品はpsmuxへ直接依存せず、永続端末をAiterm公開APIから利用する。
|
|
463
463
|
- **agent harness**を使う場合: 対応CLIを製品所有者の公式経路で導入・認証する。CursorはmacOS/Linux/WSLで`curl https://cursor.com/install -fsS | bash`、Windows nativeで`irm 'https://cursor.com/install?win32=true' | iex`を使い、`agent login`で認証、`agent update`で更新する。Aitermは`cursor-agent`を起動する。portable forkだけは追加で`throughline >= 0.9.0`が必要。
|
|
464
464
|
- 任意: [`rtk`](https://github.com/rtk-ai/rtk) バイナリ(`pty_send` の `rtk: true` 委譲で使う。無くても動く)
|
|
465
465
|
|
package/README.md
CHANGED
|
@@ -501,7 +501,7 @@ Sessions live on a shared tmux socket on POSIX or a shared psmux namespace on na
|
|
|
501
501
|
- **Node.js >= 18**
|
|
502
502
|
- **tmux or psmux** (platform runtime prerequisite)
|
|
503
503
|
- **macOS / Linux / WSL2** run tmux directly. On macOS install it with `brew install tmux` (stock macOS ships none). If your MCP client is launched from the **GUI** rather than a terminal, Homebrew's bin (`/opt/homebrew/bin` on Apple Silicon, `/usr/local/bin` on Intel) may be off its `PATH`; aiterm auto-searches those locations, or set **`AITERM_TMUX=/path/to/tmux`** to point at it explicitly.
|
|
504
|
-
- **Native Windows** has no tmux, so aiterm drives [psmux](https://github.com/psmux/psmux) — a tmux-CLI-compatible native multiplexer — with a per-install `-L` namespace. **
|
|
504
|
+
- **Native Windows** has no tmux, so aiterm drives [psmux](https://github.com/psmux/psmux) — a tmux-CLI-compatible native terminal/session multiplexer — with a per-install `-L` namespace. **psmux is not a shell.** `pty_open` defaults to PowerShell 7 (`pwsh.exe`) and never falls back to Windows PowerShell 5.1, PowerShell 6, or `cmd.exe`; if only 5.1 is installed, use Microsoft's official installer or package manager first. Install psmux **3.3.8 or newer** (`winget install marlocarlo.psmux`; 3.3.8 is the first release whose `pipe-pane` file sink, byte-exact `paste-buffer` wire, and foreground `#{pane_current_command}` behave the way aiterm's capture/dispatch paths rely on). [Git for Windows](https://gitforwindows.org/) remains required for the explicit Bash shell used internally by harness launchers; System32's `bash.exe` is the WSL launcher and is deliberately not used. Override multiplexer/Bash resolution with **`AITERM_PSMUX`** / **`AITERM_BASH`**. Other products consume persistent terminals through Aiterm's public API instead of depending on psmux directly.
|
|
505
505
|
- For **agent harnesses**: the selected CLI, installed and authenticated through its product owner's official path — `claude`, `codex`, `grok`, or Cursor's `cursor-agent`. Portable fork additionally needs `throughline >= 0.9.0`; ordinary clean launch does not. (Not needed if you only use the PTY tools.)
|
|
506
506
|
- Optional: the [`rtk`](https://github.com/rtk-ai/rtk) binary (used by `pty_send`'s `rtk: true` delegation; works fine without it)
|
|
507
507
|
|
package/dist/agent-resolver.js
CHANGED
|
@@ -5,6 +5,7 @@ import * as fs from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import { AitermError } from "./errors.js";
|
|
8
|
+
import { resolveWindowsPowerShell7 } from "./windows-powershell.js";
|
|
8
9
|
import { isWin } from "./tmux-runtime.js";
|
|
9
10
|
export function isUsableExecutableFile(candidate) {
|
|
10
11
|
try {
|
|
@@ -84,7 +85,12 @@ export function spawnAgentControlCommand(bin, args, _cwd, options) {
|
|
|
84
85
|
return spawnSync(bin, args, options);
|
|
85
86
|
}
|
|
86
87
|
export function resolveWinPaneShell(shell) {
|
|
87
|
-
if (!isWin
|
|
88
|
+
if (!isWin)
|
|
89
|
+
return shell;
|
|
90
|
+
if (["powershell", "powershell.exe", "pwsh", "pwsh.exe"].includes(path.basename(shell).toLowerCase())) {
|
|
91
|
+
return resolveWindowsPowerShell7();
|
|
92
|
+
}
|
|
93
|
+
if (shell !== "bash")
|
|
88
94
|
return shell;
|
|
89
95
|
const fromEnv = process.env.AITERM_BASH;
|
|
90
96
|
if (fromEnv) {
|
|
@@ -166,7 +172,7 @@ export function runThroughlineHandoffContext(bin, sessionId) {
|
|
|
166
172
|
if (isWin && /\.(?:cmd|bat)$/i.test(bin)) {
|
|
167
173
|
const ps1 = path.join(path.dirname(bin), `${path.basename(bin, path.extname(bin))}.ps1`);
|
|
168
174
|
if (fs.existsSync(ps1)) {
|
|
169
|
-
return spawnSync(
|
|
175
|
+
return spawnSync(resolveWindowsPowerShell7(), [
|
|
170
176
|
"-NoLogo",
|
|
171
177
|
"-NoProfile",
|
|
172
178
|
"-NonInteractive",
|
package/dist/agent-shared.js
CHANGED
|
@@ -1,35 +1,12 @@
|
|
|
1
1
|
// harness中立の共有プリミティブ。core と harnesses/ の両方が依存する最下層で、
|
|
2
2
|
// tmux-runtime / errors 以外の内部moduleへ依存しない(依存方向: core → harnesses → agent-shared)。
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
|
-
import * as os from "node:os";
|
|
5
4
|
import * as path from "node:path";
|
|
6
5
|
import { randomBytes } from "node:crypto";
|
|
7
6
|
import { AitermError } from "./errors.js";
|
|
8
7
|
export const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// agent metadata の存在確認経由で素の pty_send まで巻き込んで全 send を殺していた。
|
|
12
|
-
// Windows の fs.Stats.uid は常に 0 なので、ここも 0 を返せば owner 比較
|
|
13
|
-
// (st.uid !== currentUid()) は自然に通過する。Windows は POSIX owner 検証を
|
|
14
|
-
// 持たない(NTFS ACL は別体系)という既知の制約の明示的受容であり、POSIX 側は
|
|
15
|
-
// getuid をそのまま返すため挙動不変。
|
|
16
|
-
if (typeof process.getuid !== "function")
|
|
17
|
-
return 0;
|
|
18
|
-
return process.getuid();
|
|
19
|
-
}
|
|
20
|
-
export function runtimeStateBase() {
|
|
21
|
-
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
22
|
-
if (xdg) {
|
|
23
|
-
try {
|
|
24
|
-
if (fs.statSync(xdg).isDirectory())
|
|
25
|
-
return xdg;
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
/* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return os.tmpdir();
|
|
32
|
-
}
|
|
8
|
+
import { currentUid, runtimeStateBase } from "./state-root.js";
|
|
9
|
+
export { currentUid, runtimeStateBase };
|
|
33
10
|
export function safeStatSize(p) {
|
|
34
11
|
try {
|
|
35
12
|
return fs.statSync(p).size;
|
package/dist/claude-stop-hook.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
4
|
+
import { currentUid as uid, runtimeStateBase } from "./state-root.js";
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
const LAUNCH_ID_RE = /^[0-9a-f]{32}$/;
|
|
7
7
|
const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
@@ -21,26 +21,6 @@ function hasAitermEnv() {
|
|
|
21
21
|
process.env.AITERM_AGENT_SESSION_ID ||
|
|
22
22
|
process.env.AITERM_AGENT_LAUNCH_ID);
|
|
23
23
|
}
|
|
24
|
-
function uid() {
|
|
25
|
-
// state root のパス構成要素 `aiterm-mcp-<uid>` にだけ使う。Windows(native) は
|
|
26
|
-
// process.getuid を持たないため core の currentUid() と同じ受容で 0 を返す。
|
|
27
|
-
if (typeof process.getuid !== "function")
|
|
28
|
-
return 0;
|
|
29
|
-
return process.getuid();
|
|
30
|
-
}
|
|
31
|
-
function runtimeStateBase() {
|
|
32
|
-
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
33
|
-
if (xdg) {
|
|
34
|
-
try {
|
|
35
|
-
if (fs.statSync(xdg).isDirectory())
|
|
36
|
-
return xdg;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
/* 壊れたXDG_RUNTIME_DIRはTMPDIRへ戻す */
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return os.tmpdir();
|
|
43
|
-
}
|
|
44
24
|
function agentsDir() {
|
|
45
25
|
// state root は OS が与える per-user runtime dir(XDG_RUNTIME_DIR / os.tmpdir())の下にある。
|
|
46
26
|
// 以前はここで symlink・owner・mode を検査していたが、共有 /tmp に敵対的な同居主体がいる
|
package/dist/grok-stop-hook.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
4
|
+
import { currentUid as uid, runtimeStateBase } from "./state-root.js";
|
|
5
5
|
const LAUNCH_ID_RE = /^[0-9a-f]{32}$/;
|
|
6
6
|
const SESSION_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
7
7
|
const MAX_STDIN_BYTES = 1024 * 1024;
|
|
@@ -18,29 +18,6 @@ function hasAitermEnv() {
|
|
|
18
18
|
process.env.AITERM_AGENT_SESSION_ID ||
|
|
19
19
|
process.env.AITERM_AGENT_LAUNCH_ID);
|
|
20
20
|
}
|
|
21
|
-
function uid() {
|
|
22
|
-
// Windows(native) は process.getuid を持たない。core の currentUid()・claude-stop-hook と
|
|
23
|
-
// 同じ受容として 0 を返す(Windows の fs.Stats.uid は常に 0 のため owner 比較は自然に
|
|
24
|
-
// 通過する。NTFS ACL は別体系)。POSIX は getuid のまま=挙動不変。
|
|
25
|
-
// 以前はここで fail していたため、Windows では Grok/Composer の完了 event が
|
|
26
|
-
// 一度も書かれず aiterm-wait が timeout まで返らなかった。
|
|
27
|
-
if (typeof process.getuid !== "function")
|
|
28
|
-
return 0;
|
|
29
|
-
return process.getuid();
|
|
30
|
-
}
|
|
31
|
-
function runtimeStateBase() {
|
|
32
|
-
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
33
|
-
if (xdg) {
|
|
34
|
-
try {
|
|
35
|
-
if (fs.statSync(xdg).isDirectory())
|
|
36
|
-
return xdg;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
/* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return os.tmpdir();
|
|
43
|
-
}
|
|
44
21
|
function agentsDir() {
|
|
45
22
|
const root = path.join(runtimeStateBase(), `aiterm-mcp-${uid()}`);
|
|
46
23
|
const agents = path.join(root, "agents");
|
package/dist/index.js
CHANGED
|
@@ -84,13 +84,14 @@ server.registerTool("diagnostics", {
|
|
|
84
84
|
description: "Factory 向け read-only 診断。安全な状態語彙だけを機械可読 JSON で返す(PTY 内容・認証情報・path・環境値は返さない)。",
|
|
85
85
|
inputSchema: {},
|
|
86
86
|
}, async () => ok(await factoryDiagnostics()));
|
|
87
|
+
const DEFAULT_PTY_SHELL = process.platform === "win32" ? "pwsh" : "bash";
|
|
87
88
|
server.registerTool("pty_open", {
|
|
88
89
|
description: "ローカル永続端末(tmux セッション)を1個開き、session_id を返す。tmux サーバ常駐ゆえ本サーバや " +
|
|
89
90
|
"クライアントが再起動してもセッションは生存する。リモート操作は専用ツールにせず、開いた端末の中で " +
|
|
90
91
|
'pty_send(session_id, "ssh host") と打って入る。',
|
|
91
92
|
inputSchema: {
|
|
92
93
|
name: z.string().nullish().describe("セッション名(省略時は t1, t2... を自動採番)"),
|
|
93
|
-
shell: z.string().default(
|
|
94
|
+
shell: z.string().default(DEFAULT_PTY_SHELL).describe(`起動シェル(既定 ${DEFAULT_PTY_SHELL})`),
|
|
94
95
|
},
|
|
95
96
|
}, async ({ name, shell }) => {
|
|
96
97
|
try {
|
package/dist/runtime-error-os.js
CHANGED
|
@@ -5,6 +5,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
+
import { resolveWindowsPowerShell7, windowsPowerShell7Command } from "./windows-powershell.js";
|
|
8
9
|
export function defaultRuntimeErrorPaths(options = {}) {
|
|
9
10
|
const platform = options.platform ?? process.platform;
|
|
10
11
|
const home = options.home ?? os.homedir();
|
|
@@ -26,7 +27,9 @@ const WINDOWS_DACL_VERIFY_SCRIPT = String.raw `
|
|
|
26
27
|
$ErrorActionPreference='Stop'
|
|
27
28
|
$target=$env:AITERMMCP_ACL_PATH; $kind=$env:AITERMMCP_ACL_KIND
|
|
28
29
|
$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
29
|
-
$
|
|
30
|
+
$item=Get-Item -LiteralPath $target
|
|
31
|
+
$sections=[Security.AccessControl.AccessControlSections]::Access -bor [Security.AccessControl.AccessControlSections]::Owner
|
|
32
|
+
$check=[IO.FileSystemAclExtensions]::GetAccessControl($item,$sections)
|
|
30
33
|
$ownerSid=$check.GetOwner([Security.Principal.SecurityIdentifier]).Value
|
|
31
34
|
$rules=@($check.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]))
|
|
32
35
|
if($ownerSid -ne $sid.Value -or $rules.Count -ne 1 -or $rules[0].IdentityReference.Value -ne $sid.Value -or $rules[0].AccessControlType -ne 'Allow' -or $rules[0].IsInherited -or (($rules[0].FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -ne [Security.AccessControl.FileSystemRights]::FullControl)){exit 9}
|
|
@@ -38,18 +41,18 @@ $sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
|
38
41
|
if($kind -eq 'directory'){$acl=New-Object Security.AccessControl.DirectorySecurity;$inherit=[Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'}else{$acl=New-Object Security.AccessControl.FileSecurity;$inherit=[Security.AccessControl.InheritanceFlags]::None}
|
|
39
42
|
$acl.SetOwner($sid); $acl.SetAccessRuleProtection($true,$false)
|
|
40
43
|
$rule=New-Object Security.AccessControl.FileSystemAccessRule($sid,[Security.AccessControl.FileSystemRights]::FullControl,$inherit,[Security.AccessControl.PropagationFlags]::None,[Security.AccessControl.AccessControlType]::Allow)
|
|
41
|
-
$acl.AddAccessRule($rule);
|
|
44
|
+
$acl.AddAccessRule($rule); $item=Get-Item -LiteralPath $target; [IO.FileSystemAclExtensions]::SetAccessControl($item,$acl)
|
|
42
45
|
` + WINDOWS_DACL_VERIFY_SCRIPT;
|
|
43
46
|
export function windowsPrivateDaclCommand(target, kind = "directory") {
|
|
44
47
|
return {
|
|
45
|
-
command:
|
|
48
|
+
command: windowsPowerShell7Command(),
|
|
46
49
|
args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", WINDOWS_DACL_SCRIPT],
|
|
47
50
|
env: { ...process.env, AITERMMCP_ACL_PATH: target, AITERMMCP_ACL_KIND: kind },
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
export function windowsPrivateDaclVerifyCommand(target, kind = "file") {
|
|
51
54
|
return {
|
|
52
|
-
command:
|
|
55
|
+
command: windowsPowerShell7Command(),
|
|
53
56
|
args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", WINDOWS_DACL_VERIFY_SCRIPT],
|
|
54
57
|
env: { ...process.env, AITERMMCP_ACL_PATH: target, AITERMMCP_ACL_KIND: kind },
|
|
55
58
|
};
|
|
@@ -128,7 +131,7 @@ export function processStartIdentity(pid, platform, timeoutMs = 1000) {
|
|
|
128
131
|
}
|
|
129
132
|
if (platform === "win32") {
|
|
130
133
|
const script = "$p=Get-Process -Id $env:AITERMMCP_PROCESS_ID -ErrorAction Stop; $p.StartTime.ToUniversalTime().Ticks";
|
|
131
|
-
const result = spawnSync(
|
|
134
|
+
const result = spawnSync(resolveWindowsPowerShell7(), ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], {
|
|
132
135
|
encoding: "utf8", timeout: timeoutMs, maxBuffer: 4096, windowsHide: true,
|
|
133
136
|
env: { ...process.env, AITERMMCP_PROCESS_ID: String(pid) },
|
|
134
137
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// state root(`aiterm-mcp-<uid>`)の位置決めの唯一の置き場。node builtin 以外へ依存しない最下層で、
|
|
2
|
+
// 単体実行される stop hook 2本と agent-shared がここだけを共有する(campaign 32 の maintenance queue
|
|
3
|
+
// に積んだ uid()/runtimeStateBase() の三重実装を解消。stop hook の「内部 module へ依存しない」設計は、
|
|
4
|
+
// 本 module が builtin 依存だけであることで保たれる)。
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
export function currentUid() {
|
|
8
|
+
// Windows(native)は process.getuid を持たない。以前はここで throw していたが、
|
|
9
|
+
// agent metadata の存在確認経由で素の pty_send まで巻き込んで全 send を殺していた。
|
|
10
|
+
// Windows の fs.Stats.uid は常に 0 なので、ここも 0 を返せば owner 比較
|
|
11
|
+
// (st.uid !== currentUid()) は自然に通過する。Windows は POSIX owner 検証を
|
|
12
|
+
// 持たない(NTFS ACL は別体系)という既知の制約の明示的受容であり、POSIX 側は
|
|
13
|
+
// getuid をそのまま返すため挙動不変。
|
|
14
|
+
if (typeof process.getuid !== "function")
|
|
15
|
+
return 0;
|
|
16
|
+
return process.getuid();
|
|
17
|
+
}
|
|
18
|
+
export function runtimeStateBase() {
|
|
19
|
+
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
20
|
+
if (xdg) {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.statSync(xdg).isDirectory())
|
|
23
|
+
return xdg;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* XDG_RUNTIME_DIR が壊れている CI/非 login 環境では os.tmpdir() に戻す */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return os.tmpdir();
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Windows PowerShell実体の唯一の解決境界。5.1/cmd fallbackを持たず、検出したpwsh自身の
|
|
2
|
+
// edition/majorを実行時に検証してから絶対pathを返す。
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { AitermError } from "./errors.js";
|
|
6
|
+
export const WINDOWS_POWERSHELL_7_COMMAND = "pwsh.exe";
|
|
7
|
+
export const WINDOWS_POWERSHELL_7_INSTALL = "winget install --id Microsoft.PowerShell --source winget";
|
|
8
|
+
export const WINDOWS_POWERSHELL_7_FIXTURE = "C:\\Program Files\\PowerShell\\7\\pwsh.exe";
|
|
9
|
+
const defaultProbe = (command, args) => spawnSync(command, [...args], {
|
|
10
|
+
encoding: "utf8", timeout: 5000, maxBuffer: 16 * 1024, windowsHide: true,
|
|
11
|
+
});
|
|
12
|
+
let resolvedDefault = null;
|
|
13
|
+
export function resolveWindowsPowerShell7(probe = defaultProbe) {
|
|
14
|
+
if (probe === defaultProbe && resolvedDefault !== null)
|
|
15
|
+
return resolvedDefault;
|
|
16
|
+
const located = probe("where.exe", [WINDOWS_POWERSHELL_7_COMMAND]);
|
|
17
|
+
const resolved = located.stdout?.split(/\r?\n/u)
|
|
18
|
+
.find(candidate => path.win32.isAbsolute(candidate)
|
|
19
|
+
&& path.win32.basename(candidate).toLowerCase() === WINDOWS_POWERSHELL_7_COMMAND);
|
|
20
|
+
const fail = () => {
|
|
21
|
+
throw new AitermError(`PowerShell 7が必要です。Microsoft公式経路で導入してください: ${WINDOWS_POWERSHELL_7_INSTALL}`, 2);
|
|
22
|
+
};
|
|
23
|
+
if (located.status !== 0 || typeof resolved !== "string")
|
|
24
|
+
fail();
|
|
25
|
+
const executable = resolved;
|
|
26
|
+
const version = probe(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
|
|
27
|
+
'[ordered]@{ edition = $PSVersionTable.PSEdition; major = $PSVersionTable.PSVersion.Major } | ConvertTo-Json -Compress']);
|
|
28
|
+
let identity = null;
|
|
29
|
+
try {
|
|
30
|
+
identity = JSON.parse(version.stdout?.trim() ?? "");
|
|
31
|
+
}
|
|
32
|
+
catch { /* typed failure below */ }
|
|
33
|
+
if (version.status !== 0 || identity?.edition !== "Core"
|
|
34
|
+
|| !Number.isInteger(identity.major) || Number(identity.major) < 7)
|
|
35
|
+
fail();
|
|
36
|
+
if (probe === defaultProbe)
|
|
37
|
+
resolvedDefault = executable;
|
|
38
|
+
return executable;
|
|
39
|
+
}
|
|
40
|
+
export function windowsPowerShell7Command(platform = process.platform) {
|
|
41
|
+
return platform === "win32" ? resolveWindowsPowerShell7() : WINDOWS_POWERSHELL_7_FIXTURE;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.4",
|
|
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": [
|