quick-mcp-shell 0.3.0__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.
- quick_mcp_shell/__init__.py +3 -0
- quick_mcp_shell/__main__.py +5 -0
- quick_mcp_shell/config.py +56 -0
- quick_mcp_shell/runtime.py +76 -0
- quick_mcp_shell/server.py +122 -0
- quick_mcp_shell-0.3.0.dist-info/METADATA +145 -0
- quick_mcp_shell-0.3.0.dist-info/RECORD +10 -0
- quick_mcp_shell-0.3.0.dist-info/WHEEL +4 -0
- quick_mcp_shell-0.3.0.dist-info/entry_points.txt +2 -0
- quick_mcp_shell-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigurationError(ValueError):
|
|
10
|
+
"""Raised when local shell execution has not been explicitly configured."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _positive_int(value: str, variable: str) -> int:
|
|
14
|
+
try:
|
|
15
|
+
parsed = int(value)
|
|
16
|
+
except ValueError as exc:
|
|
17
|
+
raise ConfigurationError(f"{variable} 必须是正整数。") from exc
|
|
18
|
+
if parsed <= 0:
|
|
19
|
+
raise ConfigurationError(f"{variable} 必须是正整数。")
|
|
20
|
+
return parsed
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class Settings:
|
|
25
|
+
"""Runtime settings derived from environment variables."""
|
|
26
|
+
|
|
27
|
+
allowed_root: Path
|
|
28
|
+
max_timeout_seconds: int
|
|
29
|
+
max_output_chars: int
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_environment(cls, env: Mapping[str, str] | None = None) -> "Settings":
|
|
33
|
+
environment = os.environ if env is None else env
|
|
34
|
+
raw_root = environment.get("MCP_SHELL_ALLOWED_ROOT", "").strip()
|
|
35
|
+
if not raw_root:
|
|
36
|
+
raise ConfigurationError(
|
|
37
|
+
"未设置 MCP_SHELL_ALLOWED_ROOT。请在 MCP 配置中显式指定允许执行命令的本地目录。"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
allowed_root = Path(raw_root).expanduser().resolve()
|
|
41
|
+
if not allowed_root.is_dir():
|
|
42
|
+
raise ConfigurationError(
|
|
43
|
+
f"MCP_SHELL_ALLOWED_ROOT 不是有效目录:{allowed_root}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return cls(
|
|
47
|
+
allowed_root=allowed_root,
|
|
48
|
+
max_timeout_seconds=_positive_int(
|
|
49
|
+
environment.get("MCP_SHELL_MAX_TIMEOUT_SECONDS", "60"),
|
|
50
|
+
"MCP_SHELL_MAX_TIMEOUT_SECONDS",
|
|
51
|
+
),
|
|
52
|
+
max_output_chars=_positive_int(
|
|
53
|
+
environment.get("MCP_SHELL_MAX_OUTPUT_CHARS", "100000"),
|
|
54
|
+
"MCP_SHELL_MAX_OUTPUT_CHARS",
|
|
55
|
+
),
|
|
56
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .config import Settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_cwd(cwd: str | None, settings: Settings) -> Path:
|
|
10
|
+
candidate = settings.allowed_root if cwd is None else Path(cwd).expanduser().resolve()
|
|
11
|
+
try:
|
|
12
|
+
candidate.relative_to(settings.allowed_root)
|
|
13
|
+
except ValueError as exc:
|
|
14
|
+
raise ValueError(
|
|
15
|
+
f"拒绝执行:工作目录 '{candidate}' 不在允许根目录 '{settings.allowed_root}' 下。"
|
|
16
|
+
) from exc
|
|
17
|
+
if not candidate.is_dir():
|
|
18
|
+
raise ValueError(f"工作目录不存在或不是目录:'{candidate}'")
|
|
19
|
+
return candidate
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def effective_timeout(timeout: int, settings: Settings) -> int:
|
|
23
|
+
if timeout <= 0:
|
|
24
|
+
raise ValueError("timeout 必须是正整数。")
|
|
25
|
+
return min(timeout, settings.max_timeout_seconds)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_result(
|
|
29
|
+
stdout: str,
|
|
30
|
+
stderr: str,
|
|
31
|
+
returncode: int,
|
|
32
|
+
max_output_chars: int,
|
|
33
|
+
) -> str:
|
|
34
|
+
parts = [f"returncode: {returncode}"]
|
|
35
|
+
if stdout:
|
|
36
|
+
parts.append(f"stdout:\n{stdout}")
|
|
37
|
+
if stderr:
|
|
38
|
+
parts.append(f"stderr:\n{stderr}")
|
|
39
|
+
result = "\n\n".join(parts)
|
|
40
|
+
if len(result) > max_output_chars:
|
|
41
|
+
omitted = len(result) - max_output_chars
|
|
42
|
+
result = f"{result[:max_output_chars]}\n\n[输出已截断,省略 {omitted} 个字符]"
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_process(
|
|
47
|
+
args: str | list[str],
|
|
48
|
+
*,
|
|
49
|
+
shell: bool,
|
|
50
|
+
work_dir: Path,
|
|
51
|
+
timeout: int,
|
|
52
|
+
settings: Settings,
|
|
53
|
+
) -> str:
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
args,
|
|
57
|
+
shell=shell,
|
|
58
|
+
cwd=work_dir,
|
|
59
|
+
stdin=subprocess.DEVNULL,
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
encoding="utf-8",
|
|
64
|
+
errors="replace",
|
|
65
|
+
close_fds=True,
|
|
66
|
+
)
|
|
67
|
+
return format_result(
|
|
68
|
+
result.stdout,
|
|
69
|
+
result.stderr,
|
|
70
|
+
result.returncode,
|
|
71
|
+
settings.max_output_chars,
|
|
72
|
+
)
|
|
73
|
+
except subprocess.TimeoutExpired:
|
|
74
|
+
return f"错误:命令执行超时({timeout} 秒)。"
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
return f"错误:无法启动进程:{exc}"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .config import ConfigurationError, Settings
|
|
13
|
+
from .runtime import effective_timeout, resolve_cwd, run_process
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
mcp = FastMCP(
|
|
17
|
+
name="quick-mcp-shell",
|
|
18
|
+
instructions=(
|
|
19
|
+
"Execute trusted local shell commands and Python scripts. "
|
|
20
|
+
"This server is intentionally powerful and must only be connected to trusted local MCP clients."
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _settings_or_error() -> tuple[Settings | None, str | None]:
|
|
26
|
+
try:
|
|
27
|
+
return Settings.from_environment(), None
|
|
28
|
+
except ConfigurationError as exc:
|
|
29
|
+
return None, str(exc)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@mcp.tool()
|
|
33
|
+
def run_command(command: str, cwd: str | None = None, timeout: int = 30) -> str:
|
|
34
|
+
"""在允许根目录内执行受信任的本地 shell 命令。"""
|
|
35
|
+
settings, error = _settings_or_error()
|
|
36
|
+
if error:
|
|
37
|
+
return f"配置错误:{error}"
|
|
38
|
+
assert settings is not None
|
|
39
|
+
try:
|
|
40
|
+
work_dir = resolve_cwd(cwd, settings)
|
|
41
|
+
timeout_seconds = effective_timeout(timeout, settings)
|
|
42
|
+
except ValueError as exc:
|
|
43
|
+
return f"错误:{exc}"
|
|
44
|
+
return run_process(
|
|
45
|
+
command,
|
|
46
|
+
shell=True,
|
|
47
|
+
work_dir=work_dir,
|
|
48
|
+
timeout=timeout_seconds,
|
|
49
|
+
settings=settings,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
def run_python_script(script: str, cwd: str | None = None, timeout: int = 30) -> str:
|
|
55
|
+
"""在允许根目录内执行受信任的 Python 脚本。"""
|
|
56
|
+
settings, error = _settings_or_error()
|
|
57
|
+
if error:
|
|
58
|
+
return f"配置错误:{error}"
|
|
59
|
+
assert settings is not None
|
|
60
|
+
try:
|
|
61
|
+
work_dir = resolve_cwd(cwd, settings)
|
|
62
|
+
timeout_seconds = effective_timeout(timeout, settings)
|
|
63
|
+
except ValueError as exc:
|
|
64
|
+
return f"错误:{exc}"
|
|
65
|
+
|
|
66
|
+
script_path: Path | None = None
|
|
67
|
+
try:
|
|
68
|
+
with tempfile.NamedTemporaryFile(
|
|
69
|
+
mode="w",
|
|
70
|
+
suffix=".py",
|
|
71
|
+
prefix=".quick-mcp-shell-",
|
|
72
|
+
dir=work_dir,
|
|
73
|
+
delete=False,
|
|
74
|
+
encoding="utf-8",
|
|
75
|
+
) as handle:
|
|
76
|
+
handle.write(script)
|
|
77
|
+
script_path = Path(handle.name)
|
|
78
|
+
return run_process(
|
|
79
|
+
[sys.executable, str(script_path)],
|
|
80
|
+
shell=False,
|
|
81
|
+
work_dir=work_dir,
|
|
82
|
+
timeout=timeout_seconds,
|
|
83
|
+
settings=settings,
|
|
84
|
+
)
|
|
85
|
+
except OSError as exc:
|
|
86
|
+
return f"错误:无法创建临时 Python 脚本:{exc}"
|
|
87
|
+
finally:
|
|
88
|
+
if script_path is not None:
|
|
89
|
+
try:
|
|
90
|
+
script_path.unlink(missing_ok=True)
|
|
91
|
+
except OSError:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _doctor() -> int:
|
|
96
|
+
settings, error = _settings_or_error()
|
|
97
|
+
if error:
|
|
98
|
+
print(f"配置错误:{error}", file=sys.stderr)
|
|
99
|
+
return 1
|
|
100
|
+
assert settings is not None
|
|
101
|
+
print("quick-mcp-shell 配置正常")
|
|
102
|
+
print(f"允许根目录: {settings.allowed_root}")
|
|
103
|
+
print(f"最大超时: {settings.max_timeout_seconds} 秒")
|
|
104
|
+
print(f"最大输出: {settings.max_output_chars} 字符")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main(argv: list[str] | None = None) -> None:
|
|
109
|
+
parser = argparse.ArgumentParser(description="Trusted local MCP shell server")
|
|
110
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
"--allow-root",
|
|
113
|
+
help="仅用于本次命令的 MCP_SHELL_ALLOWED_ROOT 覆盖值。",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument("command", nargs="?", choices=["doctor"])
|
|
116
|
+
arguments = parser.parse_args(argv)
|
|
117
|
+
|
|
118
|
+
if arguments.allow_root:
|
|
119
|
+
os.environ["MCP_SHELL_ALLOWED_ROOT"] = arguments.allow_root
|
|
120
|
+
if arguments.command == "doctor":
|
|
121
|
+
raise SystemExit(_doctor())
|
|
122
|
+
mcp.run(transport="stdio")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quick-mcp-shell
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A local stdio MCP server for trusted shell and Python execution
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/quick-mcp-shell/
|
|
6
|
+
Author: Quick MCP Shell maintainers
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Topic :: System :: Shells
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Quick MCP Shell
|
|
21
|
+
|
|
22
|
+
`quick-mcp-shell` 是一个本地 stdio MCP Server,供 Amazon Quick 调用本机 shell 命令和 Python 脚本。
|
|
23
|
+
|
|
24
|
+
> 警告:这是一个高权限工具。它执行的命令拥有启动它的用户权限。`MCP_SHELL_ALLOWED_ROOT` 仅限制工作目录(`cwd`),**不是文件系统或网络沙箱**。只应连接到受信任的本地 MCP 客户端,绝不能暴露到公网,也不要向不受信任的提示词或用户开放。
|
|
25
|
+
|
|
26
|
+
## 安装 uv
|
|
27
|
+
|
|
28
|
+
Amazon Quick 通过 `uvx` 自动下载并运行本工具,无需手动安装 Python 包。
|
|
29
|
+
|
|
30
|
+
**Windows(PowerShell)**
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
winget install --id=astral-sh.uv -e
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**macOS(需已安装 Homebrew)**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
brew install uv
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Linux**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
安装后执行 `uvx --version` 确认命令可用。
|
|
49
|
+
|
|
50
|
+
## 配置 Amazon Quick
|
|
51
|
+
|
|
52
|
+
在 Amazon Quick 中打开 **Settings → Capabilities → MCP Servers**,编辑配置文件:
|
|
53
|
+
|
|
54
|
+
- Windows:`C:\Users\你的用户名\.aws\amazonq\mcp.json`
|
|
55
|
+
- macOS / Linux:`~/.aws/amazonq/mcp.json`
|
|
56
|
+
|
|
57
|
+
将下列内容合并到 `mcpServers`。必须把 `MCP_SHELL_ALLOWED_ROOT` 改为你希望允许作为工作目录的实际路径。
|
|
58
|
+
|
|
59
|
+
**Windows 示例**
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"shell": {
|
|
65
|
+
"command": "uvx",
|
|
66
|
+
"args": ["--from", "quick-mcp-shell==0.3.0", "quick-mcp-shell"],
|
|
67
|
+
"description": "在指定工作目录内执行受信任的本地 shell 命令和 Python 脚本。",
|
|
68
|
+
"env": {
|
|
69
|
+
"MCP_SHELL_ALLOWED_ROOT": "D:\\workspace\\my-project",
|
|
70
|
+
"MCP_SHELL_MAX_TIMEOUT_SECONDS": "60",
|
|
71
|
+
"MCP_SHELL_MAX_OUTPUT_CHARS": "100000"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**macOS / Linux 示例**
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"shell": {
|
|
84
|
+
"command": "uvx",
|
|
85
|
+
"args": ["--from", "quick-mcp-shell==0.3.0", "quick-mcp-shell"],
|
|
86
|
+
"description": "在指定工作目录内执行受信任的本地 shell 命令和 Python 脚本。",
|
|
87
|
+
"env": {
|
|
88
|
+
"MCP_SHELL_ALLOWED_ROOT": "/Users/your-name/workspace/my-project",
|
|
89
|
+
"MCP_SHELL_MAX_TIMEOUT_SECONDS": "60",
|
|
90
|
+
"MCP_SHELL_MAX_OUTPUT_CHARS": "100000"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Linux 用户可将示例路径替换为 `/home/your-name/workspace/my-project`。保存配置并重启 Amazon Quick。
|
|
98
|
+
|
|
99
|
+
## 可用工具
|
|
100
|
+
|
|
101
|
+
### `run_command`
|
|
102
|
+
|
|
103
|
+
执行本机 shell 命令。
|
|
104
|
+
|
|
105
|
+
| 参数 | 说明 |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `command` | 要执行的命令,必填 |
|
|
108
|
+
| `cwd` | 工作目录;省略时使用 `MCP_SHELL_ALLOWED_ROOT` |
|
|
109
|
+
| `timeout` | 超时秒数,默认 30;不会超过 `MCP_SHELL_MAX_TIMEOUT_SECONDS` |
|
|
110
|
+
|
|
111
|
+
示例:让 Amazon Quick 在项目目录中执行 `git status`,或运行项目已有的测试命令。
|
|
112
|
+
|
|
113
|
+
### `run_python_script`
|
|
114
|
+
|
|
115
|
+
在所选工作目录中使用本工具自身的 Python 解释器执行一段 Python 脚本。参数与 `run_command` 相同,只是把 `command` 替换为 `script`。
|
|
116
|
+
|
|
117
|
+
## 配置项
|
|
118
|
+
|
|
119
|
+
| 环境变量 | 是否必填 | 默认值 | 说明 |
|
|
120
|
+
|---|---:|---|---|
|
|
121
|
+
| `MCP_SHELL_ALLOWED_ROOT` | 是 | 无 | 允许作为 `cwd` 的根目录;未设置时所有工具调用都会被拒绝。 |
|
|
122
|
+
| `MCP_SHELL_MAX_TIMEOUT_SECONDS` | 否 | `60` | 单次调用允许的最大超时。 |
|
|
123
|
+
| `MCP_SHELL_MAX_OUTPUT_CHARS` | 否 | `100000` | 返回给 MCP 客户端的最大输出字符数;超出部分会被截断。 |
|
|
124
|
+
|
|
125
|
+
## 本地检查
|
|
126
|
+
|
|
127
|
+
在终端中运行以下命令可检查当前配置:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uvx --from quick-mcp-shell==0.3.0 quick-mcp-shell --allow-root /你的工作目录 doctor
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Windows 示例:
|
|
134
|
+
|
|
135
|
+
```powershell
|
|
136
|
+
uvx --from quick-mcp-shell==0.3.0 quick-mcp-shell --allow-root "D:\workspace\my-project" doctor
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 安全边界
|
|
140
|
+
|
|
141
|
+
- 不要将本工具部署为远程 HTTP 服务或暴露到公网。
|
|
142
|
+
- 不要把允许根目录设为系统盘根目录、用户主目录或包含密钥的目录。
|
|
143
|
+
- 不要把本工具交给不受信任的模型、插件、用户输入或自动化流程。
|
|
144
|
+
- 命令和脚本可访问当前用户能访问的资源;允许根目录不是安全沙箱。
|
|
145
|
+
- 固定版本号。升级前先阅读发布说明,并在隔离的项目目录中验证。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
quick_mcp_shell/__init__.py,sha256=Mt_7sRItHgoMiPb31lvJl7LjDouK-Q5BOJySttdf108,54
|
|
2
|
+
quick_mcp_shell/__main__.py,sha256=kXwGR0h4I_1BdqTv1YV6f86NLM1OwukbORECfPc9OPg,65
|
|
3
|
+
quick_mcp_shell/config.py,sha256=rpCnp7yzyC2LonkHrCm90TfxNboIWM3vn5ZWGEsKRYE,1836
|
|
4
|
+
quick_mcp_shell/runtime.py,sha256=smOLQLp_95K1Ni495AIVlidAAQJbNHNwg4EmTS5v6lg,2178
|
|
5
|
+
quick_mcp_shell/server.py,sha256=4ejYmgcJ-QYhaI8TlfrNPuDl7qx7zmlBfzSkU6D3150,3746
|
|
6
|
+
quick_mcp_shell-0.3.0.dist-info/METADATA,sha256=d2y3LrDo5uCQ95yBWgkUFKhZo0az4pf7ScyvGjJ8SmY,4783
|
|
7
|
+
quick_mcp_shell-0.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
quick_mcp_shell-0.3.0.dist-info/entry_points.txt,sha256=nbAo9plh9AiUVMxjnmu2Bh7Fz72PUWXvI4gEauUO9tA,64
|
|
9
|
+
quick_mcp_shell-0.3.0.dist-info/licenses/LICENSE,sha256=hbUa7wlsXAoAwP-OWYtG0wvIMJThjC5PQhZUVrPhsrI,1062
|
|
10
|
+
quick_mcp_shell-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 libao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|