winterm-mcp 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.
winterm_mcp/store.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ 命令存储管理 - winterm-mcp
3
+ """
4
+
5
+ import threading
6
+ import logging
7
+ from typing import Optional
8
+ from .models import CommandInfo
9
+
10
+ logger = logging.getLogger("winterm-mcp")
11
+
12
+
13
+ class CommandStore:
14
+ """
15
+ 命令存储管理类,线程安全
16
+ """
17
+
18
+ def __init__(self) -> None:
19
+ self._commands: dict[str, CommandInfo] = {}
20
+ self._lock = threading.Lock()
21
+
22
+ def add_command(self, token: str, info: CommandInfo) -> None:
23
+ """
24
+ 添加命令信息到存储
25
+
26
+ Args:
27
+ token: 命令令牌
28
+ info: 命令信息对象
29
+ """
30
+ with self._lock:
31
+ self._commands[token] = info
32
+ logger.debug(f"Added command to store: token={token}")
33
+
34
+ def get_command(self, token: str) -> Optional[CommandInfo]:
35
+ """
36
+ 获取命令信息
37
+
38
+ Args:
39
+ token: 命令令牌
40
+
41
+ Returns:
42
+ 命令信息对象,如果不存在则返回 None
43
+ """
44
+ with self._lock:
45
+ return self._commands.get(token)
46
+
47
+ def remove_command(self, token: str) -> bool:
48
+ """
49
+ 移除命令信息
50
+
51
+ Args:
52
+ token: 命令令牌
53
+
54
+ Returns:
55
+ 如果命令存在并被移除返回 True,否则返回 False
56
+ """
57
+ with self._lock:
58
+ if token in self._commands:
59
+ del self._commands[token]
60
+ logger.debug(f"Removed command from store: token={token}")
61
+ return True
62
+ return False
63
+
64
+ def get_all_tokens(self) -> list[str]:
65
+ """
66
+ 获取所有命令令牌
67
+
68
+ Returns:
69
+ 令牌列表
70
+ """
71
+ with self._lock:
72
+ return list(self._commands.keys())
73
+
74
+ def update_command(self, token: str, **kwargs) -> bool:
75
+ """
76
+ 更新命令信息
77
+
78
+ Args:
79
+ token: 命令令牌
80
+ **kwargs: 要更新的字段
81
+
82
+ Returns:
83
+ 如果命令存在并被更新返回 True,否则返回 False
84
+ """
85
+ with self._lock:
86
+ if token in self._commands:
87
+ for key, value in kwargs.items():
88
+ if hasattr(self._commands[token], key):
89
+ setattr(self._commands[token], key, value)
90
+ logger.debug(f"Updated command in store: token={token}, fields={list(kwargs.keys())}")
91
+ return True
92
+ return False
winterm_mcp/utils.py ADDED
@@ -0,0 +1,206 @@
1
+ """
2
+ 工具函数 - winterm-mcp
3
+ """
4
+
5
+ import os
6
+ import re
7
+ import shutil
8
+ import logging
9
+ from typing import Optional
10
+ from .constants import (
11
+ ENV_POWERSHELL_PATH,
12
+ ENV_CMD_PATH,
13
+ POWERSHELL_PATHS,
14
+ PWSH_PATHS,
15
+ CMD_PATHS,
16
+ )
17
+
18
+ logger = logging.getLogger("winterm-mcp")
19
+
20
+
21
+ def find_powershell() -> str:
22
+ """
23
+ 查找 PowerShell 可执行文件路径
24
+
25
+ 优先级:
26
+ 1. WINTERM_POWERSHELL_PATH 环境变量
27
+ 2. 标准 PowerShell 路径
28
+ 3. PowerShell Core 路径
29
+ 4. PATH 环境变量
30
+ 5. 抛出 FileNotFoundError
31
+
32
+ Returns:
33
+ PowerShell 可执行文件的绝对路径
34
+
35
+ Raises:
36
+ FileNotFoundError: 如果找不到 PowerShell
37
+ """
38
+ logger.debug("Starting PowerShell path discovery...")
39
+
40
+ custom_path = os.environ.get(ENV_POWERSHELL_PATH)
41
+ if custom_path:
42
+ logger.debug(f"Found env var {ENV_POWERSHELL_PATH}={custom_path}")
43
+ if os.path.isfile(custom_path):
44
+ logger.info(f"Using custom PowerShell path: {custom_path}")
45
+ return custom_path
46
+ else:
47
+ logger.warning(
48
+ f"Custom PowerShell path not found: {custom_path}, "
49
+ "falling back to standard paths"
50
+ )
51
+
52
+ for path in POWERSHELL_PATHS:
53
+ logger.debug(f"Checking standard path: {path}")
54
+ if os.path.isfile(path):
55
+ logger.info(f"Found Windows PowerShell: {path}")
56
+ return path
57
+
58
+ for path in PWSH_PATHS:
59
+ logger.debug(f"Checking PowerShell Core path: {path}")
60
+ if os.path.isfile(path):
61
+ logger.info(f"Found PowerShell Core: {path}")
62
+ return path
63
+
64
+ logger.debug("Checking PATH environment variable...")
65
+ ps_path = shutil.which("powershell")
66
+ if ps_path:
67
+ logger.info(f"Found PowerShell in PATH: {ps_path}")
68
+ return ps_path
69
+
70
+ pwsh_path = shutil.which("pwsh")
71
+ if pwsh_path:
72
+ logger.info(f"Found pwsh in PATH: {pwsh_path}")
73
+ return pwsh_path
74
+
75
+ checked_paths = POWERSHELL_PATHS + PWSH_PATHS
76
+ error_msg = (
77
+ f"PowerShell not found. "
78
+ f"Set {ENV_POWERSHELL_PATH} environment variable or "
79
+ f"ensure PowerShell is installed. "
80
+ f"Checked paths: {', '.join(checked_paths)}"
81
+ )
82
+ logger.error(error_msg)
83
+ raise FileNotFoundError(error_msg)
84
+
85
+
86
+ def find_cmd() -> str:
87
+ """
88
+ 查找 CMD 可执行文件路径
89
+
90
+ 优先级:
91
+ 1. WINTERM_CMD_PATH 环境变量
92
+ 2. 标准 CMD 路径
93
+ 3. PATH 环境变量
94
+ 4. 抛出 FileNotFoundError
95
+
96
+ Returns:
97
+ CMD 可执行文件的绝对路径
98
+
99
+ Raises:
100
+ FileNotFoundError: 如果找不到 CMD
101
+ """
102
+ logger.debug("Starting CMD path discovery...")
103
+
104
+ custom_path = os.environ.get(ENV_CMD_PATH)
105
+ if custom_path:
106
+ logger.debug(f"Found env var {ENV_CMD_PATH}={custom_path}")
107
+ if os.path.isfile(custom_path):
108
+ logger.info(f"Using custom CMD path: {custom_path}")
109
+ return custom_path
110
+ else:
111
+ logger.warning(
112
+ f"Custom CMD path not found: {custom_path}, "
113
+ "falling back to standard paths"
114
+ )
115
+
116
+ for path in CMD_PATHS:
117
+ logger.debug(f"Checking standard path: {path}")
118
+ if os.path.isfile(path):
119
+ logger.info(f"Found CMD: {path}")
120
+ return path
121
+
122
+ logger.debug("Checking PATH environment variable...")
123
+ cmd_path = shutil.which("cmd")
124
+ if cmd_path:
125
+ logger.info(f"Found CMD in PATH: {cmd_path}")
126
+ return cmd_path
127
+
128
+ error_msg = (
129
+ f"CMD not found. "
130
+ f"Set {ENV_CMD_PATH} environment variable or "
131
+ f"ensure CMD is installed. "
132
+ f"Checked paths: {', '.join(CMD_PATHS)}"
133
+ )
134
+ logger.error(error_msg)
135
+ raise FileNotFoundError(error_msg)
136
+
137
+
138
+ def resolve_executable_path(executable: str) -> str:
139
+ """
140
+ 解析可执行文件路径
141
+
142
+ Args:
143
+ executable: 可执行文件名称或路径
144
+
145
+ Returns:
146
+ 解析后的可执行文件路径
147
+
148
+ 规则:
149
+ - 如果是绝对路径,直接返回
150
+ - 检查当前目录
151
+ - 在 PATH 中搜索(支持 .exe, .bat, .cmd, .com, .ps1)
152
+ - 返回原始名称(让系统处理错误)
153
+ """
154
+ if os.path.isabs(executable):
155
+ logger.debug(f"Executable is absolute path: {executable}")
156
+ return executable
157
+
158
+ if os.path.isfile(executable):
159
+ logger.debug(f"Executable found in current directory: {executable}")
160
+ return os.path.abspath(executable)
161
+
162
+ extensions = [".exe", ".bat", ".cmd", ".com", ".ps1"]
163
+
164
+ for ext in extensions:
165
+ if executable.lower().endswith(ext):
166
+ path = shutil.which(executable)
167
+ if path:
168
+ logger.debug(f"Found executable in PATH: {path}")
169
+ return path
170
+ break
171
+ else:
172
+ for ext in extensions:
173
+ full_path = shutil.which(executable + ext)
174
+ if full_path:
175
+ logger.debug(f"Found executable in PATH: {full_path}")
176
+ return full_path
177
+
178
+ logger.debug(f"Executable not found in PATH, returning original: {executable}")
179
+ return executable
180
+
181
+
182
+ def strip_ansi_codes(text: str) -> str:
183
+ """
184
+ 移除 ANSI 转义序列
185
+
186
+ Args:
187
+ text: 包含 ANSI 转义序列的文本
188
+
189
+ Returns:
190
+ 清理后的文本
191
+
192
+ 支持的转义序列:
193
+ - CSI 序列: \x1b[... 或 \u001b[...
194
+ - OSC 序列: \x1b]...\x07
195
+ - BEL 字符: \x07
196
+ """
197
+ ansi_escape = re.compile(
198
+ r"""
199
+ \x1b\[[0-9;]*[mGKHfABCDsuJK] | # CSI sequences
200
+ \x1b\][0-9;]*\x07 | # OSC sequences
201
+ \x07 | # BEL character
202
+ \x1b. # Other ESC sequences
203
+ """,
204
+ re.VERBOSE,
205
+ )
206
+ return ansi_escape.sub("", text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winterm-mcp
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: A Model Context Protocol (MCP) service for executing Windows terminal commands asynchronously
5
5
  Author-email: winterm-mcp contributors <maintainer@example.com>
6
6
  License: MIT
@@ -19,6 +19,7 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: fastmcp>=0.1.0
21
21
  Requires-Dist: pydantic>=2.0.0
22
+ Requires-Dist: pywinpty>=2.0.0
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: pytest>=7.0.0; extra == "dev"
24
25
  Requires-Dist: black>=23.0.0; extra == "dev"
@@ -0,0 +1,14 @@
1
+ winterm_mcp/__init__.py,sha256=y-6DZGsz4wX5rCZPBH5uQv0S3RYlxt2bB5RyiEe1ts8,859
2
+ winterm_mcp/__main__.py,sha256=jtZWUuc5hQpO5xU_YFak21QXrCM9nKEB2dGScXovJPc,1044
3
+ winterm_mcp/constants.py,sha256=-n1_WlNpAsfn6lp4D3SQW3xFwZsWOFnIp1UIFC3sTGs,647
4
+ winterm_mcp/models.py,sha256=zfzAd7k2a8nTmyM3QWuqPFr2ArELf5SgNyjTeWKcgAQ,1721
5
+ winterm_mcp/server.py,sha256=tz3JT6jpko-tv34rSIRLunLl1z5sZInT9wdmkDM2zNs,7613
6
+ winterm_mcp/service.py,sha256=HO1rPL4UyG2XxdT1rVdrs203tFd6FY-6S2pbhFYEPtg,22370
7
+ winterm_mcp/store.py,sha256=obHzcTP1c7_6r2DzgbUBysx2XpA7Esfl00wLsPSOqS4,2421
8
+ winterm_mcp/utils.py,sha256=uIjNFKP3T3q5ztNtxtquWKFhiWo4gjecOdHl8X3pXxA,5763
9
+ winterm_mcp-0.1.7.dist-info/licenses/LICENSE,sha256=GPZ4VAbf_gxeFMSUHCmNQING30GK0kNduK8EJIO5-gc,1081
10
+ winterm_mcp-0.1.7.dist-info/METADATA,sha256=4apHA0um7BT2WPWcl6mrqLyppveJCpENeQ-ODAxfVcc,5138
11
+ winterm_mcp-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ winterm_mcp-0.1.7.dist-info/entry_points.txt,sha256=0OGioH1DKGxuxvSFD1OCRcbLoutfqdEPmJy_j2GhJTA,113
13
+ winterm_mcp-0.1.7.dist-info/top_level.txt,sha256=S7w96DR3MB-CMIuXLnrMOvs5ApqPF-2PsAcfVCphCHw,12
14
+ winterm_mcp-0.1.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- winterm_mcp/__init__.py,sha256=SKtzW0lc0ixl5FUdEaJA6J7tryL-JYTvsPG8wUi5Dxk,222
2
- winterm_mcp/__main__.py,sha256=u-6txqlMEl-1TP5yQq5LEvGM8ppY4cODmMiTPGlllcQ,1106
3
- winterm_mcp/server.py,sha256=5DFC9bKBdePtf1GK6iCXC3QFBpQIjwpxNe_ov7Dlvhg,4839
4
- winterm_mcp/service.py,sha256=GyE3cBgKheWRviB-y7RtTwZuTj1zA4vX3-nznQA4slc,14281
5
- winterm_mcp-0.1.6.dist-info/licenses/LICENSE,sha256=GPZ4VAbf_gxeFMSUHCmNQING30GK0kNduK8EJIO5-gc,1081
6
- winterm_mcp-0.1.6.dist-info/METADATA,sha256=6Hyewzr6asiHtQkSdtMSfVr21HCw7yCZUjOzHwsam88,5106
7
- winterm_mcp-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- winterm_mcp-0.1.6.dist-info/entry_points.txt,sha256=0OGioH1DKGxuxvSFD1OCRcbLoutfqdEPmJy_j2GhJTA,113
9
- winterm_mcp-0.1.6.dist-info/top_level.txt,sha256=S7w96DR3MB-CMIuXLnrMOvs5ApqPF-2PsAcfVCphCHw,12
10
- winterm_mcp-0.1.6.dist-info/RECORD,,