plugin-kit-ai 1.0.4__tar.gz
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.
- plugin_kit_ai-1.0.4/PKG-INFO +33 -0
- plugin_kit_ai-1.0.4/README.md +13 -0
- plugin_kit_ai-1.0.4/pyproject.toml +39 -0
- plugin_kit_ai-1.0.4/setup.cfg +4 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai/__init__.py +3 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai/cli.py +19 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai/install.py +215 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai/platform.py +41 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai.egg-info/PKG-INFO +33 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai.egg-info/SOURCES.txt +11 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai.egg-info/dependency_links.txt +1 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai.egg-info/entry_points.txt +2 -0
- plugin_kit_ai-1.0.4/src/plugin_kit_ai.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plugin-kit-ai
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Thin Python launcher for the plugin-kit-ai CLI
|
|
5
|
+
Author: plugin-kit-ai
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/777genius/plugin-kit-ai
|
|
8
|
+
Project-URL: Repository, https://github.com/777genius/plugin-kit-ai
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# plugin-kit-ai (PyPI wrapper)
|
|
22
|
+
|
|
23
|
+
Thin Python launcher for the `plugin-kit-ai` CLI.
|
|
24
|
+
|
|
25
|
+
It downloads the matching published GitHub Releases binary, verifies
|
|
26
|
+
`checksums.txt`, caches the binary locally, and executes it.
|
|
27
|
+
|
|
28
|
+
Primary user path:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install plugin-kit-ai
|
|
32
|
+
plugin-kit-ai version
|
|
33
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# plugin-kit-ai (PyPI wrapper)
|
|
2
|
+
|
|
3
|
+
Thin Python launcher for the `plugin-kit-ai` CLI.
|
|
4
|
+
|
|
5
|
+
It downloads the matching published GitHub Releases binary, verifies
|
|
6
|
+
`checksums.txt`, caches the binary locally, and executes it.
|
|
7
|
+
|
|
8
|
+
Primary user path:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pipx install plugin-kit-ai
|
|
12
|
+
plugin-kit-ai version
|
|
13
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "plugin-kit-ai"
|
|
7
|
+
description = "Thin Python launcher for the plugin-kit-ai CLI"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "plugin-kit-ai" }]
|
|
12
|
+
dynamic = ["version"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Topic :: Software Development :: Build Tools",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
plugin-kit-ai = "plugin_kit_ai.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/777genius/plugin-kit-ai"
|
|
30
|
+
Repository = "https://github.com/777genius/plugin-kit-ai"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
package-dir = { "" = "src" }
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.dynamic]
|
|
39
|
+
version = { attr = "plugin_kit_ai.__version__" }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Console entrypoint for the plugin-kit-ai Python wrapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .install import format_install_error, run_binary
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
try:
|
|
12
|
+
raise SystemExit(run_binary())
|
|
13
|
+
except Exception as err: # pragma: no cover - exercised through integration tests
|
|
14
|
+
sys.stderr.write(format_install_error(err) + "\n")
|
|
15
|
+
raise SystemExit(1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Install and execute the plugin-kit-ai CLI binary from GitHub Releases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import stat
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import tarfile
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, Iterable, Optional
|
|
16
|
+
from urllib.error import HTTPError, URLError
|
|
17
|
+
from urllib.request import Request, urlopen
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
from .platform import asset_name_for_version, detect_platform
|
|
21
|
+
|
|
22
|
+
DEFAULT_REPOSITORY = "777genius/plugin-kit-ai"
|
|
23
|
+
DEFAULT_API_BASE = "https://api.github.com"
|
|
24
|
+
PLACEHOLDER_VERSION = "0.0.0-development"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def normalize_tag(raw: str) -> str:
|
|
28
|
+
value = str(raw or "").strip()
|
|
29
|
+
if not value or value == "latest":
|
|
30
|
+
return ""
|
|
31
|
+
return value if value.startswith("v") else f"v{value}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def derive_release_base(api_base: str, override: str) -> str:
|
|
35
|
+
if override and override.strip():
|
|
36
|
+
return override.strip().rstrip("/")
|
|
37
|
+
trimmed = str(api_base or DEFAULT_API_BASE).strip().rstrip("/")
|
|
38
|
+
if trimmed in {"https://api.github.com", "http://api.github.com"}:
|
|
39
|
+
return "https://github.com"
|
|
40
|
+
if trimmed.endswith("/api/v3"):
|
|
41
|
+
return trimmed[: -len("/api/v3")]
|
|
42
|
+
if trimmed.endswith("/api"):
|
|
43
|
+
return trimmed[: -len("/api")]
|
|
44
|
+
return trimmed
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_requested_tag() -> str:
|
|
48
|
+
env_version = normalize_tag(os.environ.get("PLUGIN_KIT_AI_VERSION", ""))
|
|
49
|
+
if env_version:
|
|
50
|
+
return env_version
|
|
51
|
+
if __version__ and __version__ != PLACEHOLDER_VERSION:
|
|
52
|
+
return normalize_tag(__version__)
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def request_headers(accept_json: bool) -> Dict[str, str]:
|
|
57
|
+
headers: Dict[str, str] = {}
|
|
58
|
+
token = os.environ.get("GITHUB_TOKEN", "").strip()
|
|
59
|
+
if token:
|
|
60
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
61
|
+
if accept_json:
|
|
62
|
+
headers["Accept"] = "application/vnd.github+json"
|
|
63
|
+
return headers
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def fetch_bytes(url: str, accept_json: bool = False) -> bytes:
|
|
67
|
+
req = Request(url, headers=request_headers(accept_json))
|
|
68
|
+
try:
|
|
69
|
+
with urlopen(req) as resp:
|
|
70
|
+
return resp.read()
|
|
71
|
+
except (HTTPError, URLError) as exc:
|
|
72
|
+
raise RuntimeError(f"request failed for {url}: {exc}") from exc
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fetch_text(url: str, accept_json: bool = False) -> str:
|
|
76
|
+
return fetch_bytes(url, accept_json=accept_json).decode("utf-8")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def latest_tag(api_base: str, repository: str) -> str:
|
|
80
|
+
clean_base = str(api_base or DEFAULT_API_BASE).strip().rstrip("/")
|
|
81
|
+
payload = json.loads(fetch_text(f"{clean_base}/repos/{repository}/releases/latest", accept_json=True))
|
|
82
|
+
tag_name = str(payload.get("tag_name", "")).strip()
|
|
83
|
+
if not tag_name:
|
|
84
|
+
raise RuntimeError(f"could not resolve latest release tag from {clean_base}")
|
|
85
|
+
return normalize_tag(tag_name)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_checksums(text: str) -> Dict[str, str]:
|
|
89
|
+
out: Dict[str, str] = {}
|
|
90
|
+
for raw_line in str(text or "").splitlines():
|
|
91
|
+
line = raw_line.strip()
|
|
92
|
+
if not line:
|
|
93
|
+
continue
|
|
94
|
+
fields = line.split()
|
|
95
|
+
if len(fields) < 2:
|
|
96
|
+
raise RuntimeError(f'invalid checksums.txt line "{line}"')
|
|
97
|
+
checksum = fields[0].strip()
|
|
98
|
+
name = fields[-1].lstrip("*").strip()
|
|
99
|
+
out[name] = checksum
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def sha256_bytes(body: bytes) -> str:
|
|
104
|
+
return hashlib.sha256(body).hexdigest()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def default_cache_root() -> Path:
|
|
108
|
+
override = os.environ.get("PLUGIN_KIT_AI_CACHE_DIR", "").strip()
|
|
109
|
+
if override:
|
|
110
|
+
return Path(override)
|
|
111
|
+
if sys.platform == "darwin":
|
|
112
|
+
return Path.home() / "Library" / "Caches" / "plugin-kit-ai"
|
|
113
|
+
if sys.platform == "win32":
|
|
114
|
+
base = os.environ.get("LOCALAPPDATA") or str(Path.home() / "AppData" / "Local")
|
|
115
|
+
return Path(base) / "plugin-kit-ai"
|
|
116
|
+
return Path(os.environ.get("XDG_CACHE_HOME", str(Path.home() / ".cache"))) / "plugin-kit-ai"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def extract_binary(archive_path: Path, wanted_name: str, target_path: Path) -> None:
|
|
120
|
+
with tarfile.open(archive_path, mode="r:gz") as archive:
|
|
121
|
+
for member in archive.getmembers():
|
|
122
|
+
if not member.isfile():
|
|
123
|
+
continue
|
|
124
|
+
if Path(member.name).name != wanted_name:
|
|
125
|
+
continue
|
|
126
|
+
extracted = archive.extractfile(member)
|
|
127
|
+
if extracted is None:
|
|
128
|
+
continue
|
|
129
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
with extracted, target_path.open("wb") as out:
|
|
131
|
+
shutil.copyfileobj(extracted, out)
|
|
132
|
+
return
|
|
133
|
+
raise RuntimeError(f"archive does not contain {wanted_name} at archive root")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def ensure_installed(*, quiet: bool = False) -> Dict[str, str]:
|
|
137
|
+
repository = os.environ.get("PLUGIN_KIT_AI_REPOSITORY", DEFAULT_REPOSITORY)
|
|
138
|
+
api_base = os.environ.get("GITHUB_API_BASE", DEFAULT_API_BASE)
|
|
139
|
+
release_base = derive_release_base(api_base, os.environ.get("PLUGIN_KIT_AI_RELEASE_BASE_URL", ""))
|
|
140
|
+
platform_info = detect_platform()
|
|
141
|
+
|
|
142
|
+
tag = resolve_requested_tag()
|
|
143
|
+
if not tag:
|
|
144
|
+
tag = latest_tag(api_base, repository)
|
|
145
|
+
version = tag[1:]
|
|
146
|
+
asset_name = asset_name_for_version(version, platform_info)
|
|
147
|
+
|
|
148
|
+
cache_root = default_cache_root()
|
|
149
|
+
installed_binary = cache_root / tag / platform_info.binary_name
|
|
150
|
+
if installed_binary.exists():
|
|
151
|
+
return {
|
|
152
|
+
"tag": tag,
|
|
153
|
+
"version": version,
|
|
154
|
+
"asset_name": asset_name,
|
|
155
|
+
"installed_binary": str(installed_binary),
|
|
156
|
+
"repository": repository,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
download_base = f"{release_base}/{repository}/releases/download/{tag}"
|
|
160
|
+
checksums = parse_checksums(fetch_text(f"{download_base}/checksums.txt"))
|
|
161
|
+
if asset_name not in checksums:
|
|
162
|
+
raise RuntimeError(f"checksums.txt missing asset {asset_name}")
|
|
163
|
+
|
|
164
|
+
archive = fetch_bytes(f"{download_base}/{asset_name}")
|
|
165
|
+
expected_sum = checksums[asset_name]
|
|
166
|
+
actual_sum = sha256_bytes(archive)
|
|
167
|
+
if actual_sum != expected_sum:
|
|
168
|
+
raise RuntimeError(f"checksum mismatch for {asset_name}")
|
|
169
|
+
|
|
170
|
+
with tempfile.TemporaryDirectory(prefix="plugin-kit-ai-pypi-") as tmpdir:
|
|
171
|
+
archive_path = Path(tmpdir) / asset_name
|
|
172
|
+
archive_path.write_bytes(archive)
|
|
173
|
+
extract_binary(archive_path, platform_info.binary_name, installed_binary)
|
|
174
|
+
|
|
175
|
+
if platform_info.os_name != "windows":
|
|
176
|
+
current_mode = installed_binary.stat().st_mode
|
|
177
|
+
installed_binary.chmod(current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
178
|
+
|
|
179
|
+
if not quiet:
|
|
180
|
+
lines = [
|
|
181
|
+
"Installed plugin-kit-ai PyPI wrapper binary",
|
|
182
|
+
f"Version: {tag}",
|
|
183
|
+
f"Repository: {repository}",
|
|
184
|
+
f"Asset: {asset_name}",
|
|
185
|
+
f"Installed path: {installed_binary}",
|
|
186
|
+
"Checksum: verified via checksums.txt",
|
|
187
|
+
]
|
|
188
|
+
sys.stdout.write(os.linesep.join(lines) + os.linesep)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"tag": tag,
|
|
192
|
+
"version": version,
|
|
193
|
+
"asset_name": asset_name,
|
|
194
|
+
"installed_binary": str(installed_binary),
|
|
195
|
+
"repository": repository,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def format_install_error(err: Exception) -> str:
|
|
200
|
+
return os.linesep.join(
|
|
201
|
+
[
|
|
202
|
+
f"plugin-kit-ai PyPI bootstrap: {err}",
|
|
203
|
+
"Fallbacks:",
|
|
204
|
+
"- Homebrew: brew install 777genius/homebrew-plugin-kit-ai/plugin-kit-ai",
|
|
205
|
+
"- npm: npm i -g plugin-kit-ai",
|
|
206
|
+
"- Verified script: curl -fsSL https://raw.githubusercontent.com/777genius/plugin-kit-ai/main/scripts/install.sh | sh",
|
|
207
|
+
]
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def run_binary(argv: Optional[Iterable[str]] = None) -> int:
|
|
212
|
+
install = ensure_installed(quiet=True)
|
|
213
|
+
args = [install["installed_binary"], *(list(argv) if argv is not None else sys.argv[1:])]
|
|
214
|
+
completed = subprocess.run(args, check=False)
|
|
215
|
+
return completed.returncode
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Platform helpers for the plugin-kit-ai Python wrapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import platform
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class PlatformInfo:
|
|
11
|
+
os_name: str
|
|
12
|
+
arch_name: str
|
|
13
|
+
binary_name: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def detect_platform() -> PlatformInfo:
|
|
17
|
+
system = platform.system().lower()
|
|
18
|
+
machine = platform.machine().lower()
|
|
19
|
+
|
|
20
|
+
if system == "darwin":
|
|
21
|
+
os_name = "darwin"
|
|
22
|
+
elif system == "linux":
|
|
23
|
+
os_name = "linux"
|
|
24
|
+
elif system == "windows":
|
|
25
|
+
os_name = "windows"
|
|
26
|
+
else:
|
|
27
|
+
raise RuntimeError(f"unsupported OS {platform.system()}")
|
|
28
|
+
|
|
29
|
+
if machine in {"x86_64", "amd64"}:
|
|
30
|
+
arch_name = "amd64"
|
|
31
|
+
elif machine in {"arm64", "aarch64"}:
|
|
32
|
+
arch_name = "arm64"
|
|
33
|
+
else:
|
|
34
|
+
raise RuntimeError(f"unsupported architecture {platform.machine()}")
|
|
35
|
+
|
|
36
|
+
binary_name = "plugin-kit-ai.exe" if os_name == "windows" else "plugin-kit-ai"
|
|
37
|
+
return PlatformInfo(os_name=os_name, arch_name=arch_name, binary_name=binary_name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def asset_name_for_version(version: str, platform_info: PlatformInfo) -> str:
|
|
41
|
+
return f"plugin-kit-ai_{version}_{platform_info.os_name}_{platform_info.arch_name}.tar.gz"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plugin-kit-ai
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Thin Python launcher for the plugin-kit-ai CLI
|
|
5
|
+
Author: plugin-kit-ai
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/777genius/plugin-kit-ai
|
|
8
|
+
Project-URL: Repository, https://github.com/777genius/plugin-kit-ai
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# plugin-kit-ai (PyPI wrapper)
|
|
22
|
+
|
|
23
|
+
Thin Python launcher for the `plugin-kit-ai` CLI.
|
|
24
|
+
|
|
25
|
+
It downloads the matching published GitHub Releases binary, verifies
|
|
26
|
+
`checksums.txt`, caches the binary locally, and executes it.
|
|
27
|
+
|
|
28
|
+
Primary user path:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install plugin-kit-ai
|
|
32
|
+
plugin-kit-ai version
|
|
33
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/plugin_kit_ai/__init__.py
|
|
4
|
+
src/plugin_kit_ai/cli.py
|
|
5
|
+
src/plugin_kit_ai/install.py
|
|
6
|
+
src/plugin_kit_ai/platform.py
|
|
7
|
+
src/plugin_kit_ai.egg-info/PKG-INFO
|
|
8
|
+
src/plugin_kit_ai.egg-info/SOURCES.txt
|
|
9
|
+
src/plugin_kit_ai.egg-info/dependency_links.txt
|
|
10
|
+
src/plugin_kit_ai.egg-info/entry_points.txt
|
|
11
|
+
src/plugin_kit_ai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
plugin_kit_ai
|