aistorehub 0.1.0__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.
- aistorehub-0.1.0/PKG-INFO +129 -0
- aistorehub-0.1.0/README.md +88 -0
- aistorehub-0.1.0/aihub/__init__.py +4 -0
- aistorehub-0.1.0/aihub/__main__.py +4 -0
- aistorehub-0.1.0/aistorehub/__init__.py +4 -0
- aistorehub-0.1.0/aistorehub/__main__.py +4 -0
- aistorehub-0.1.0/aistorehub.egg-info/PKG-INFO +129 -0
- aistorehub-0.1.0/aistorehub.egg-info/SOURCES.txt +20 -0
- aistorehub-0.1.0/aistorehub.egg-info/dependency_links.txt +1 -0
- aistorehub-0.1.0/aistorehub.egg-info/entry_points.txt +4 -0
- aistorehub-0.1.0/aistorehub.egg-info/requires.txt +2 -0
- aistorehub-0.1.0/aistorehub.egg-info/top_level.txt +3 -0
- aistorehub-0.1.0/pyproject.toml +3 -0
- aistorehub-0.1.0/setup.cfg +4 -0
- aistorehub-0.1.0/setup.py +51 -0
- aistorehub-0.1.0/sihub/__init__.py +4 -0
- aistorehub-0.1.0/sihub/__main__.py +4 -0
- aistorehub-0.1.0/sihub/client.py +69 -0
- aistorehub-0.1.0/sihub/config.py +54 -0
- aistorehub-0.1.0/sihub/main.py +220 -0
- aistorehub-0.1.0/sihub/mcp.py +135 -0
- aistorehub-0.1.0/sihub/packager.py +109 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aistorehub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Official CLI for AIStoreHub - Discover, pull, publish, and configure AI systems and MCP servers.
|
|
5
|
+
Home-page: https://aistorehub.com
|
|
6
|
+
Author: AIStoreHub Team
|
|
7
|
+
Author-email: hello@aistorehub.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Documentation, https://aistorehub.com/docs
|
|
10
|
+
Project-URL: Source, https://github.com/aistorehub/aistorehub
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/aistorehub/aistorehub/issues
|
|
12
|
+
Keywords: ai,mcp,model-context-protocol,agents,prompts,aistorehub,claude,cursor
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Classifier: Environment :: Console
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: click>=8.0.0
|
|
28
|
+
Requires-Dist: httpx>=0.25.0
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: project-url
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
Dynamic: summary
|
|
41
|
+
|
|
42
|
+
# AIStoreHub CLI (`aistorehub` / `aihub`)
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/aistorehub/)
|
|
45
|
+
[](https://opensource.org/licenses/MIT)
|
|
46
|
+
[](https://www.python.org/)
|
|
47
|
+
|
|
48
|
+
The official command-line interface for **[AIStoreHub](https://aistorehub.com)** — the open registry and marketplace for autonomous AI systems, model instructions, agent workflows, and Model Context Protocol (MCP) servers.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## ⚡ Quick Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install aistorehub
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Tip:** Both `aistorehub` and `aihub` commands are installed. You can use whichever you prefer!
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🚀 Key Features
|
|
63
|
+
|
|
64
|
+
* **1-Click MCP Auto-Configuration**: Automatically injects MCP server configs into **Claude Desktop**, **Cursor IDE**, and **Windsurf** with automatic timestamped `.bak` safety backups.
|
|
65
|
+
* **Instant Artifact Pull**: Download prompts, schemas, instructions, and test datasets straight into your current directory.
|
|
66
|
+
* **Interactive Terminal Search**: Query AI systems, agents, and verified benchmarks without leaving your terminal.
|
|
67
|
+
* **10-Second Manifest Inspector**: Inspect local folders to auto-detect system prompts, tool schemas, and capabilities before publishing.
|
|
68
|
+
* **Team & Organization RBAC**: Switch between personal workspaces and enterprise multi-tenant organizations seamlessly.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 📖 Command Reference
|
|
73
|
+
|
|
74
|
+
### 1. Authenticate with Personal Access Token (PAT)
|
|
75
|
+
```bash
|
|
76
|
+
# Log in with your AIStoreHub API token:
|
|
77
|
+
aihub login
|
|
78
|
+
|
|
79
|
+
# Check authenticated user & organization roles:
|
|
80
|
+
aihub whoami
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Search Systems & Agents
|
|
84
|
+
```bash
|
|
85
|
+
# Search by keyword, problem, or capability:
|
|
86
|
+
aihub search "sql optimizer"
|
|
87
|
+
aihub search "invoice extraction"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Pull System Instructions & Code
|
|
91
|
+
```bash
|
|
92
|
+
# Pull into current directory:
|
|
93
|
+
aihub pull abhinav/sql-optimizer
|
|
94
|
+
|
|
95
|
+
# Pull into a custom destination folder:
|
|
96
|
+
aihub pull abhinav/invoice-agent --dest ./my-tools
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 4. 1-Click MCP Server Installation
|
|
100
|
+
```bash
|
|
101
|
+
# Auto-configure into Claude Desktop and Cursor simultaneously:
|
|
102
|
+
aihub mcp abhinav/sql-optimizer
|
|
103
|
+
|
|
104
|
+
# Or target a specific client:
|
|
105
|
+
aihub mcp abhinav/sql-optimizer --client claude
|
|
106
|
+
aihub mcp abhinav/sql-optimizer --client cursor
|
|
107
|
+
aihub mcp abhinav/sql-optimizer --client windsurf
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 5. Inspect Local Folders for Publishing
|
|
111
|
+
```bash
|
|
112
|
+
# Auto-detect manifests, tools, and prompts in a directory:
|
|
113
|
+
aihub inspect ./my-agent-folder
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ⚙️ Environment Variables
|
|
119
|
+
|
|
120
|
+
You can optionally configure authentication and API endpoints via environment variables:
|
|
121
|
+
|
|
122
|
+
* `AISTOREHUB_TOKEN` or `AIHUB_TOKEN`: Personal Access Token (PAT).
|
|
123
|
+
* `AISTOREHUB_API_URL` or `AIHUB_API_URL`: Custom API endpoint (default: `https://api.aistorehub.com/api/v1`).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 📄 License
|
|
128
|
+
|
|
129
|
+
MIT © [AIStoreHub Team](https://aistorehub.com)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# AIStoreHub CLI (`aistorehub` / `aihub`)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/aistorehub/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
|
|
7
|
+
The official command-line interface for **[AIStoreHub](https://aistorehub.com)** — the open registry and marketplace for autonomous AI systems, model instructions, agent workflows, and Model Context Protocol (MCP) servers.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⚡ Quick Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install aistorehub
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> **Tip:** Both `aistorehub` and `aihub` commands are installed. You can use whichever you prefer!
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🚀 Key Features
|
|
22
|
+
|
|
23
|
+
* **1-Click MCP Auto-Configuration**: Automatically injects MCP server configs into **Claude Desktop**, **Cursor IDE**, and **Windsurf** with automatic timestamped `.bak` safety backups.
|
|
24
|
+
* **Instant Artifact Pull**: Download prompts, schemas, instructions, and test datasets straight into your current directory.
|
|
25
|
+
* **Interactive Terminal Search**: Query AI systems, agents, and verified benchmarks without leaving your terminal.
|
|
26
|
+
* **10-Second Manifest Inspector**: Inspect local folders to auto-detect system prompts, tool schemas, and capabilities before publishing.
|
|
27
|
+
* **Team & Organization RBAC**: Switch between personal workspaces and enterprise multi-tenant organizations seamlessly.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📖 Command Reference
|
|
32
|
+
|
|
33
|
+
### 1. Authenticate with Personal Access Token (PAT)
|
|
34
|
+
```bash
|
|
35
|
+
# Log in with your AIStoreHub API token:
|
|
36
|
+
aihub login
|
|
37
|
+
|
|
38
|
+
# Check authenticated user & organization roles:
|
|
39
|
+
aihub whoami
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Search Systems & Agents
|
|
43
|
+
```bash
|
|
44
|
+
# Search by keyword, problem, or capability:
|
|
45
|
+
aihub search "sql optimizer"
|
|
46
|
+
aihub search "invoice extraction"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Pull System Instructions & Code
|
|
50
|
+
```bash
|
|
51
|
+
# Pull into current directory:
|
|
52
|
+
aihub pull abhinav/sql-optimizer
|
|
53
|
+
|
|
54
|
+
# Pull into a custom destination folder:
|
|
55
|
+
aihub pull abhinav/invoice-agent --dest ./my-tools
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 4. 1-Click MCP Server Installation
|
|
59
|
+
```bash
|
|
60
|
+
# Auto-configure into Claude Desktop and Cursor simultaneously:
|
|
61
|
+
aihub mcp abhinav/sql-optimizer
|
|
62
|
+
|
|
63
|
+
# Or target a specific client:
|
|
64
|
+
aihub mcp abhinav/sql-optimizer --client claude
|
|
65
|
+
aihub mcp abhinav/sql-optimizer --client cursor
|
|
66
|
+
aihub mcp abhinav/sql-optimizer --client windsurf
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 5. Inspect Local Folders for Publishing
|
|
70
|
+
```bash
|
|
71
|
+
# Auto-detect manifests, tools, and prompts in a directory:
|
|
72
|
+
aihub inspect ./my-agent-folder
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## ⚙️ Environment Variables
|
|
78
|
+
|
|
79
|
+
You can optionally configure authentication and API endpoints via environment variables:
|
|
80
|
+
|
|
81
|
+
* `AISTOREHUB_TOKEN` or `AIHUB_TOKEN`: Personal Access Token (PAT).
|
|
82
|
+
* `AISTOREHUB_API_URL` or `AIHUB_API_URL`: Custom API endpoint (default: `https://api.aistorehub.com/api/v1`).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 📄 License
|
|
87
|
+
|
|
88
|
+
MIT © [AIStoreHub Team](https://aistorehub.com)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aistorehub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Official CLI for AIStoreHub - Discover, pull, publish, and configure AI systems and MCP servers.
|
|
5
|
+
Home-page: https://aistorehub.com
|
|
6
|
+
Author: AIStoreHub Team
|
|
7
|
+
Author-email: hello@aistorehub.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Documentation, https://aistorehub.com/docs
|
|
10
|
+
Project-URL: Source, https://github.com/aistorehub/aistorehub
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/aistorehub/aistorehub/issues
|
|
12
|
+
Keywords: ai,mcp,model-context-protocol,agents,prompts,aistorehub,claude,cursor
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Classifier: Environment :: Console
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: click>=8.0.0
|
|
28
|
+
Requires-Dist: httpx>=0.25.0
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: project-url
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
Dynamic: summary
|
|
41
|
+
|
|
42
|
+
# AIStoreHub CLI (`aistorehub` / `aihub`)
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/aistorehub/)
|
|
45
|
+
[](https://opensource.org/licenses/MIT)
|
|
46
|
+
[](https://www.python.org/)
|
|
47
|
+
|
|
48
|
+
The official command-line interface for **[AIStoreHub](https://aistorehub.com)** — the open registry and marketplace for autonomous AI systems, model instructions, agent workflows, and Model Context Protocol (MCP) servers.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## ⚡ Quick Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install aistorehub
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Tip:** Both `aistorehub` and `aihub` commands are installed. You can use whichever you prefer!
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🚀 Key Features
|
|
63
|
+
|
|
64
|
+
* **1-Click MCP Auto-Configuration**: Automatically injects MCP server configs into **Claude Desktop**, **Cursor IDE**, and **Windsurf** with automatic timestamped `.bak` safety backups.
|
|
65
|
+
* **Instant Artifact Pull**: Download prompts, schemas, instructions, and test datasets straight into your current directory.
|
|
66
|
+
* **Interactive Terminal Search**: Query AI systems, agents, and verified benchmarks without leaving your terminal.
|
|
67
|
+
* **10-Second Manifest Inspector**: Inspect local folders to auto-detect system prompts, tool schemas, and capabilities before publishing.
|
|
68
|
+
* **Team & Organization RBAC**: Switch between personal workspaces and enterprise multi-tenant organizations seamlessly.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 📖 Command Reference
|
|
73
|
+
|
|
74
|
+
### 1. Authenticate with Personal Access Token (PAT)
|
|
75
|
+
```bash
|
|
76
|
+
# Log in with your AIStoreHub API token:
|
|
77
|
+
aihub login
|
|
78
|
+
|
|
79
|
+
# Check authenticated user & organization roles:
|
|
80
|
+
aihub whoami
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Search Systems & Agents
|
|
84
|
+
```bash
|
|
85
|
+
# Search by keyword, problem, or capability:
|
|
86
|
+
aihub search "sql optimizer"
|
|
87
|
+
aihub search "invoice extraction"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Pull System Instructions & Code
|
|
91
|
+
```bash
|
|
92
|
+
# Pull into current directory:
|
|
93
|
+
aihub pull abhinav/sql-optimizer
|
|
94
|
+
|
|
95
|
+
# Pull into a custom destination folder:
|
|
96
|
+
aihub pull abhinav/invoice-agent --dest ./my-tools
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 4. 1-Click MCP Server Installation
|
|
100
|
+
```bash
|
|
101
|
+
# Auto-configure into Claude Desktop and Cursor simultaneously:
|
|
102
|
+
aihub mcp abhinav/sql-optimizer
|
|
103
|
+
|
|
104
|
+
# Or target a specific client:
|
|
105
|
+
aihub mcp abhinav/sql-optimizer --client claude
|
|
106
|
+
aihub mcp abhinav/sql-optimizer --client cursor
|
|
107
|
+
aihub mcp abhinav/sql-optimizer --client windsurf
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 5. Inspect Local Folders for Publishing
|
|
111
|
+
```bash
|
|
112
|
+
# Auto-detect manifests, tools, and prompts in a directory:
|
|
113
|
+
aihub inspect ./my-agent-folder
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ⚙️ Environment Variables
|
|
119
|
+
|
|
120
|
+
You can optionally configure authentication and API endpoints via environment variables:
|
|
121
|
+
|
|
122
|
+
* `AISTOREHUB_TOKEN` or `AIHUB_TOKEN`: Personal Access Token (PAT).
|
|
123
|
+
* `AISTOREHUB_API_URL` or `AIHUB_API_URL`: Custom API endpoint (default: `https://api.aistorehub.com/api/v1`).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 📄 License
|
|
128
|
+
|
|
129
|
+
MIT © [AIStoreHub Team](https://aistorehub.com)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
aihub/__init__.py
|
|
5
|
+
aihub/__main__.py
|
|
6
|
+
aistorehub/__init__.py
|
|
7
|
+
aistorehub/__main__.py
|
|
8
|
+
aistorehub.egg-info/PKG-INFO
|
|
9
|
+
aistorehub.egg-info/SOURCES.txt
|
|
10
|
+
aistorehub.egg-info/dependency_links.txt
|
|
11
|
+
aistorehub.egg-info/entry_points.txt
|
|
12
|
+
aistorehub.egg-info/requires.txt
|
|
13
|
+
aistorehub.egg-info/top_level.txt
|
|
14
|
+
sihub/__init__.py
|
|
15
|
+
sihub/__main__.py
|
|
16
|
+
sihub/client.py
|
|
17
|
+
sihub/config.py
|
|
18
|
+
sihub/main.py
|
|
19
|
+
sihub/mcp.py
|
|
20
|
+
sihub/packager.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
this_directory = Path(__file__).parent
|
|
5
|
+
readme_path = this_directory / "README.md"
|
|
6
|
+
long_description = readme_path.read_text(encoding="utf-8") if readme_path.exists() else ""
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="aistorehub",
|
|
10
|
+
version="0.1.0",
|
|
11
|
+
description="The Official CLI for AIStoreHub - Discover, pull, publish, and configure AI systems and MCP servers.",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://aistorehub.com",
|
|
15
|
+
project_urls={
|
|
16
|
+
"Documentation": "https://aistorehub.com/docs",
|
|
17
|
+
"Source": "https://github.com/aistorehub/aistorehub",
|
|
18
|
+
"Issue Tracker": "https://github.com/aistorehub/aistorehub/issues",
|
|
19
|
+
},
|
|
20
|
+
author="AIStoreHub Team",
|
|
21
|
+
author_email="hello@aistorehub.com",
|
|
22
|
+
license="MIT",
|
|
23
|
+
classifiers=[
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Topic :: Software Development :: Build Tools",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
"Environment :: Console",
|
|
36
|
+
],
|
|
37
|
+
keywords="ai, mcp, model-context-protocol, agents, prompts, aistorehub, claude, cursor",
|
|
38
|
+
packages=find_packages(),
|
|
39
|
+
install_requires=[
|
|
40
|
+
"click>=8.0.0",
|
|
41
|
+
"httpx>=0.25.0",
|
|
42
|
+
],
|
|
43
|
+
entry_points={
|
|
44
|
+
"console_scripts": [
|
|
45
|
+
"aihub=sihub.main:cli",
|
|
46
|
+
"aistorehub=sihub.main:cli",
|
|
47
|
+
"sihub=sihub.main:cli",
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
python_requires=">=3.9",
|
|
51
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
import httpx
|
|
3
|
+
from .config import get_config, get_token
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class APIClient:
|
|
7
|
+
def __init__(self, base_url: Optional[str] = None):
|
|
8
|
+
cfg = get_config()
|
|
9
|
+
self.base_url = (base_url or cfg.get("api_url") or "http://localhost:8000/api/v1").rstrip("/")
|
|
10
|
+
self.token = get_token()
|
|
11
|
+
|
|
12
|
+
def _headers(self) -> Dict[str, str]:
|
|
13
|
+
headers = {
|
|
14
|
+
"Accept": "application/json",
|
|
15
|
+
"User-Agent": "sihub-cli/0.1.0",
|
|
16
|
+
}
|
|
17
|
+
if self.token:
|
|
18
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
19
|
+
return headers
|
|
20
|
+
|
|
21
|
+
def whoami(self) -> Dict[str, Any]:
|
|
22
|
+
with httpx.Client(timeout=10.0) as client:
|
|
23
|
+
resp = client.get(f"{self.base_url}/users/me", headers=self._headers())
|
|
24
|
+
if resp.status_code == 401:
|
|
25
|
+
raise Exception("Not authenticated. Please run 'sihub login'.")
|
|
26
|
+
resp.raise_for_status()
|
|
27
|
+
return resp.json()
|
|
28
|
+
|
|
29
|
+
def search(self, query: str = "", limit: int = 15) -> List[Dict[str, Any]]:
|
|
30
|
+
with httpx.Client(timeout=10.0) as client:
|
|
31
|
+
resp = client.get(
|
|
32
|
+
f"{self.base_url}/search",
|
|
33
|
+
params={"q": query, "limit": limit},
|
|
34
|
+
headers=self._headers(),
|
|
35
|
+
)
|
|
36
|
+
resp.raise_for_status()
|
|
37
|
+
return resp.json().get("items", [])
|
|
38
|
+
|
|
39
|
+
def get_system(self, owner: str, system_slug: str, repo: Optional[str] = None) -> Dict[str, Any]:
|
|
40
|
+
# If repo is not provided, try owner/system_slug directly
|
|
41
|
+
repo_name = repo or system_slug
|
|
42
|
+
with httpx.Client(timeout=10.0) as client:
|
|
43
|
+
resp = client.get(
|
|
44
|
+
f"{self.base_url}/systems/{owner}/{repo_name}/{system_slug}",
|
|
45
|
+
headers=self._headers(),
|
|
46
|
+
)
|
|
47
|
+
if resp.status_code == 404:
|
|
48
|
+
# Try finding system by slug
|
|
49
|
+
search_resp = client.get(
|
|
50
|
+
f"{self.base_url}/search",
|
|
51
|
+
params={"q": system_slug, "limit": 5},
|
|
52
|
+
headers=self._headers(),
|
|
53
|
+
)
|
|
54
|
+
if search_resp.status_code == 200:
|
|
55
|
+
items = search_resp.json().get("items", [])
|
|
56
|
+
for it in items:
|
|
57
|
+
if it.get("slug") == system_slug or it.get("name", "").lower() == system_slug.lower():
|
|
58
|
+
return it
|
|
59
|
+
resp.raise_for_status()
|
|
60
|
+
return resp.json()
|
|
61
|
+
|
|
62
|
+
def download_artifact(self, artifact_id: str) -> bytes:
|
|
63
|
+
with httpx.Client(timeout=60.0) as client:
|
|
64
|
+
resp = client.get(
|
|
65
|
+
f"{self.base_url}/marketplace/artifacts/{artifact_id}/download",
|
|
66
|
+
headers=self._headers(),
|
|
67
|
+
)
|
|
68
|
+
resp.raise_for_status()
|
|
69
|
+
return resp.content
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
DEFAULT_API_URL = os.environ.get("AISTOREHUB_API_URL") or os.environ.get("AIHUB_API_URL") or os.environ.get("SIHUB_API_URL", "http://localhost:8000/api/v1")
|
|
7
|
+
CONFIG_DIR = Path.home() / ".aistorehub"
|
|
8
|
+
LEGACY_CONFIG_DIR = Path.home() / ".sihub"
|
|
9
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
10
|
+
LEGACY_CONFIG_FILE = LEGACY_CONFIG_DIR / "config.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_config() -> Dict[str, Any]:
|
|
14
|
+
file_to_read = CONFIG_FILE if CONFIG_FILE.exists() else (LEGACY_CONFIG_FILE if LEGACY_CONFIG_FILE.exists() else None)
|
|
15
|
+
if not file_to_read:
|
|
16
|
+
return {"api_url": DEFAULT_API_URL, "token": None, "user": None}
|
|
17
|
+
try:
|
|
18
|
+
with open(file_to_read, "r", encoding="utf-8") as f:
|
|
19
|
+
data = json.load(f)
|
|
20
|
+
if "api_url" not in data:
|
|
21
|
+
data["api_url"] = DEFAULT_API_URL
|
|
22
|
+
return data
|
|
23
|
+
except Exception:
|
|
24
|
+
return {"api_url": DEFAULT_API_URL, "token": None, "user": None}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def save_config(config_data: Dict[str, Any]) -> None:
|
|
28
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
30
|
+
json.dump(config_data, f, indent=2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_token() -> Optional[str]:
|
|
34
|
+
# Check env var first, then config file
|
|
35
|
+
env_token = os.environ.get("AISTOREHUB_TOKEN") or os.environ.get("AIHUB_TOKEN") or os.environ.get("SIHUB_TOKEN")
|
|
36
|
+
if env_token:
|
|
37
|
+
return env_token.strip()
|
|
38
|
+
config = get_config()
|
|
39
|
+
return config.get("token")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def set_token(token: str, user_info: Optional[Dict[str, Any]] = None) -> None:
|
|
43
|
+
cfg = get_config()
|
|
44
|
+
cfg["token"] = token
|
|
45
|
+
if user_info:
|
|
46
|
+
cfg["user"] = user_info
|
|
47
|
+
save_config(cfg)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def clear_auth() -> None:
|
|
51
|
+
cfg = get_config()
|
|
52
|
+
cfg["token"] = None
|
|
53
|
+
cfg["user"] = None
|
|
54
|
+
save_config(cfg)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from .config import get_config, set_token, clear_auth, get_token, DEFAULT_API_URL
|
|
7
|
+
from .client import APIClient
|
|
8
|
+
from .mcp import inject_mcp_config
|
|
9
|
+
from .packager import inspect_directory, create_bundle_zip
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
@click.version_option("0.1.0", prog_name="aistorehub")
|
|
14
|
+
def cli():
|
|
15
|
+
"""
|
|
16
|
+
aistorehub - The Official AIStoreHub Developer CLI.
|
|
17
|
+
Discover, pull, publish, and auto-configure AI systems and MCP servers.
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
@click.option("--token", prompt=True, hide_input=True, help="Your AIStoreHub Personal Access Token (PAT)")
|
|
24
|
+
@click.option("--url", default=None, help="Custom AIStoreHub API URL")
|
|
25
|
+
def login(token: str, url: str):
|
|
26
|
+
"""Log in to AIStoreHub with a Personal Access Token."""
|
|
27
|
+
cfg = get_config()
|
|
28
|
+
if url:
|
|
29
|
+
cfg["api_url"] = url
|
|
30
|
+
set_token(token.strip())
|
|
31
|
+
|
|
32
|
+
client = APIClient()
|
|
33
|
+
try:
|
|
34
|
+
user = client.whoami()
|
|
35
|
+
username = user.get("username", "developer")
|
|
36
|
+
click.secho(f"✓ Successfully authenticated as @{username}!", fg="green", bold=True)
|
|
37
|
+
set_token(token.strip(), user_info=user)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
click.secho(f"✗ Login verification failed: {e}", fg="red")
|
|
40
|
+
clear_auth()
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cli.command()
|
|
45
|
+
def logout():
|
|
46
|
+
"""Log out and remove stored credentials."""
|
|
47
|
+
clear_auth()
|
|
48
|
+
click.secho("✓ Successfully logged out from AIStoreHub.", fg="green")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@cli.command()
|
|
52
|
+
def whoami():
|
|
53
|
+
"""Display the currently authenticated user."""
|
|
54
|
+
client = APIClient()
|
|
55
|
+
token = get_token()
|
|
56
|
+
if not token:
|
|
57
|
+
click.secho("Not logged in. Run 'aistorehub login' or set AISTOREHUB_TOKEN.", fg="yellow")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
user = client.whoami()
|
|
62
|
+
click.echo("---")
|
|
63
|
+
click.secho(f"User: {user.get('display_name', '')} (@{user.get('username')})", bold=True)
|
|
64
|
+
click.echo(f"Email: {user.get('email', '')}")
|
|
65
|
+
click.echo(f"ID: {user.get('id', '')}")
|
|
66
|
+
click.echo(f"Admin: {user.get('is_admin', False)}")
|
|
67
|
+
click.echo("---")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
click.secho(f"Error: {e}", fg="red")
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@cli.command()
|
|
74
|
+
@click.argument("query", default="")
|
|
75
|
+
@click.option("--limit", default=10, help="Number of items to return")
|
|
76
|
+
def search(query: str, limit: int):
|
|
77
|
+
"""Search AI systems, agents, and MCP servers in the store."""
|
|
78
|
+
client = APIClient()
|
|
79
|
+
try:
|
|
80
|
+
items = client.search(query=query, limit=limit)
|
|
81
|
+
if not items:
|
|
82
|
+
click.echo("No systems found matching your query.")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
click.secho(f"Found {len(items)} AI systems in store:\n", bold=True)
|
|
86
|
+
for it in items:
|
|
87
|
+
name = it.get("name", "Unknown")
|
|
88
|
+
slug = it.get("slug", "")
|
|
89
|
+
owner = it.get("repository", {}).get("owner", {}).get("username", "store")
|
|
90
|
+
sys_type = it.get("type", "agent")
|
|
91
|
+
desc = it.get("description") or "No description provided."
|
|
92
|
+
stars = it.get("save_count", 0)
|
|
93
|
+
|
|
94
|
+
click.secho(f"• {owner}/{slug} ", fg="cyan", bold=True, nl=False)
|
|
95
|
+
click.secho(f"[{sys_type.upper()}] ★ {stars}", fg="yellow")
|
|
96
|
+
click.echo(f" {desc[:90]}")
|
|
97
|
+
click.echo("")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
click.secho(f"Search error: {e}", fg="red")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@cli.command()
|
|
104
|
+
@click.argument("target")
|
|
105
|
+
@click.option("--client", "target_client", default="all", type=click.Choice(["all", "claude", "cursor", "windsurf"]), help="Target AI client")
|
|
106
|
+
def mcp(target: str, target_client: str):
|
|
107
|
+
"""
|
|
108
|
+
Auto-configure an MCP server into Claude Desktop, Cursor, or Windsurf.
|
|
109
|
+
Example: aistorehub mcp abhinav/sql-optimizer
|
|
110
|
+
"""
|
|
111
|
+
# Parse target
|
|
112
|
+
parts = target.strip("/").split("/")
|
|
113
|
+
owner = parts[0] if len(parts) > 1 else "public"
|
|
114
|
+
system_slug = parts[-1]
|
|
115
|
+
|
|
116
|
+
api = APIClient()
|
|
117
|
+
click.echo(f"Fetching MCP configuration for {owner}/{system_slug}...")
|
|
118
|
+
|
|
119
|
+
server_url = f"https://mcp.aistorehub.in/{owner}/{system_slug}/sse"
|
|
120
|
+
try:
|
|
121
|
+
sys_data = api.get_system(owner=owner, system_slug=system_slug)
|
|
122
|
+
offers = sys_data.get("distribution_offers", [])
|
|
123
|
+
mcp_offer = next((o for o in offers if o.get("channel") == "mcp"), None)
|
|
124
|
+
if mcp_offer and mcp_offer.get("endpoint_url"):
|
|
125
|
+
server_url = mcp_offer["endpoint_url"]
|
|
126
|
+
except Exception:
|
|
127
|
+
# Proceed with canonical url
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
server_config = {
|
|
131
|
+
"url": server_url,
|
|
132
|
+
"transport": "sse",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
results = inject_mcp_config(system_slug, server_config, target_client=target_client)
|
|
136
|
+
if not results:
|
|
137
|
+
click.secho("No supported AI clients detected on your machine.", fg="yellow")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
click.echo("")
|
|
141
|
+
for client_name, path, ok in results:
|
|
142
|
+
if ok:
|
|
143
|
+
click.secho(f"✓ Configured for {client_name}", fg="green", bold=True)
|
|
144
|
+
click.echo(f" Location: {path}")
|
|
145
|
+
else:
|
|
146
|
+
click.secho(f"✗ Failed to configure {client_name} at {path}", fg="red")
|
|
147
|
+
|
|
148
|
+
click.secho("\nRestart your AI client to start using the tools!", fg="bright_white", bold=True)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@cli.command()
|
|
152
|
+
@click.argument("target")
|
|
153
|
+
@click.option("--dest", default=None, help="Destination directory (defaults to system slug)")
|
|
154
|
+
def pull(target: str, dest: str):
|
|
155
|
+
"""
|
|
156
|
+
Pull an AI system bundle or instructions into a local directory.
|
|
157
|
+
Example: aistorehub pull abhinav/invoice-agent
|
|
158
|
+
"""
|
|
159
|
+
parts = target.strip("/").split("/")
|
|
160
|
+
owner = parts[0] if len(parts) > 1 else "store"
|
|
161
|
+
system_slug = parts[-1]
|
|
162
|
+
|
|
163
|
+
api = APIClient()
|
|
164
|
+
click.echo(f"Pulling {owner}/{system_slug} from AIStoreHub...")
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
sys_data = api.get_system(owner=owner, system_slug=system_slug)
|
|
168
|
+
out_dir = Path(dest) if dest else Path.cwd() / system_slug
|
|
169
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
|
|
171
|
+
# Write manifest metadata
|
|
172
|
+
manifest = {
|
|
173
|
+
"name": sys_data.get("name"),
|
|
174
|
+
"slug": sys_data.get("slug"),
|
|
175
|
+
"type": sys_data.get("type"),
|
|
176
|
+
"description": sys_data.get("description"),
|
|
177
|
+
}
|
|
178
|
+
with open(out_dir / "aistorehub.json", "w", encoding="utf-8") as f:
|
|
179
|
+
import json
|
|
180
|
+
json.dump(manifest, f, indent=2)
|
|
181
|
+
|
|
182
|
+
# Write instructions if available
|
|
183
|
+
curr_ver = sys_data.get("current_version") or {}
|
|
184
|
+
if curr_ver.get("instructions"):
|
|
185
|
+
with open(out_dir / "instructions.txt", "w", encoding="utf-8") as f:
|
|
186
|
+
f.write(curr_ver["instructions"])
|
|
187
|
+
|
|
188
|
+
click.secho(f"✓ Successfully pulled {owner}/{system_slug} to {out_dir.resolve()}", fg="green", bold=True)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
click.secho(f"Pull error: {e}", fg="red")
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@cli.command()
|
|
195
|
+
@click.argument("path", default=".")
|
|
196
|
+
def inspect(path: str):
|
|
197
|
+
"""Inspect a local folder and preview its auto-detected manifest."""
|
|
198
|
+
dir_path = Path(path).resolve()
|
|
199
|
+
if not dir_path.is_dir():
|
|
200
|
+
click.secho(f"Error: {dir_path} is not a directory", fg="red")
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
detected = inspect_directory(dir_path)
|
|
204
|
+
click.secho(f"Auto-Detected System Specification for {dir_path.name}:", bold=True, fg="cyan")
|
|
205
|
+
click.echo(f" Name: {detected['name']}")
|
|
206
|
+
click.echo(f" Slug: {detected['slug']}")
|
|
207
|
+
click.echo(f" Type: {detected['type']}")
|
|
208
|
+
click.echo(f" Version: {detected['version']}")
|
|
209
|
+
click.echo(f" Channel: {detected['channel']}")
|
|
210
|
+
if detected.get("description"):
|
|
211
|
+
click.echo(f" Description: {detected['description']}")
|
|
212
|
+
if detected.get("instructions"):
|
|
213
|
+
click.echo(f" Instructions: {len(detected['instructions'])} characters detected")
|
|
214
|
+
if detected.get("tools"):
|
|
215
|
+
click.echo(f" Tools: {len(detected['tools'])} tools detected")
|
|
216
|
+
click.secho("\nReady to publish with 'aistorehub publish'.", fg="green")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
cli()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_claude_config_path() -> Path:
|
|
10
|
+
if sys.platform == "win32":
|
|
11
|
+
appdata = os.environ.get("APPDATA")
|
|
12
|
+
base = Path(appdata) if appdata else Path.home() / "AppData" / "Roaming"
|
|
13
|
+
return base / "Claude" / "claude_desktop_config.json"
|
|
14
|
+
elif sys.platform == "darwin":
|
|
15
|
+
return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
|
|
16
|
+
else:
|
|
17
|
+
return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_cursor_config_paths() -> List[Path]:
|
|
21
|
+
paths = []
|
|
22
|
+
# Local workspace .cursor/mcp.json
|
|
23
|
+
local_cursor = Path.cwd() / ".cursor" / "mcp.json"
|
|
24
|
+
paths.append(local_cursor)
|
|
25
|
+
|
|
26
|
+
# User global .cursor/mcp.json
|
|
27
|
+
user_cursor = Path.home() / ".cursor" / "mcp.json"
|
|
28
|
+
paths.append(user_cursor)
|
|
29
|
+
return paths
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_windsurf_config_path() -> Path:
|
|
33
|
+
return Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_zed_config_path() -> Path:
|
|
37
|
+
if sys.platform == "win32":
|
|
38
|
+
appdata = os.environ.get("APPDATA")
|
|
39
|
+
base = Path(appdata) if appdata else Path.home() / "AppData" / "Roaming"
|
|
40
|
+
return base / "Zed" / "settings.json"
|
|
41
|
+
else:
|
|
42
|
+
return Path.home() / ".config" / "zed" / "settings.json"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def inject_mcp_config(
|
|
46
|
+
server_name: str,
|
|
47
|
+
server_config: Dict[str, Any],
|
|
48
|
+
target_client: str = "all",
|
|
49
|
+
) -> List[Tuple[str, Path, bool]]:
|
|
50
|
+
"""
|
|
51
|
+
Injects or updates server_config in detected AI clients.
|
|
52
|
+
Returns list of (client_name, path, success).
|
|
53
|
+
"""
|
|
54
|
+
results = []
|
|
55
|
+
|
|
56
|
+
# 1. Claude Desktop
|
|
57
|
+
if target_client in ("all", "claude"):
|
|
58
|
+
claude_path = get_claude_config_path()
|
|
59
|
+
try:
|
|
60
|
+
claude_path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
data: Dict[str, Any] = {"mcpServers": {}}
|
|
62
|
+
if claude_path.exists():
|
|
63
|
+
try:
|
|
64
|
+
with open(claude_path, "r", encoding="utf-8") as f:
|
|
65
|
+
data = json.load(f)
|
|
66
|
+
shutil.copy2(claude_path, str(claude_path) + ".bak")
|
|
67
|
+
except Exception:
|
|
68
|
+
data = {"mcpServers": {}}
|
|
69
|
+
|
|
70
|
+
if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
|
|
71
|
+
data["mcpServers"] = {}
|
|
72
|
+
|
|
73
|
+
data["mcpServers"][server_name] = server_config
|
|
74
|
+
|
|
75
|
+
with open(claude_path, "w", encoding="utf-8") as f:
|
|
76
|
+
json.dump(data, f, indent=2)
|
|
77
|
+
|
|
78
|
+
results.append(("Claude Desktop", claude_path, True))
|
|
79
|
+
except Exception as e:
|
|
80
|
+
results.append(("Claude Desktop", claude_path, False))
|
|
81
|
+
|
|
82
|
+
# 2. Cursor IDE
|
|
83
|
+
if target_client in ("all", "cursor"):
|
|
84
|
+
for cursor_path in get_cursor_config_paths():
|
|
85
|
+
try:
|
|
86
|
+
cursor_path.parent.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
data = {"mcpServers": {}}
|
|
88
|
+
if cursor_path.exists():
|
|
89
|
+
try:
|
|
90
|
+
with open(cursor_path, "r", encoding="utf-8") as f:
|
|
91
|
+
data = json.load(f)
|
|
92
|
+
shutil.copy2(cursor_path, str(cursor_path) + ".bak")
|
|
93
|
+
except Exception:
|
|
94
|
+
data = {"mcpServers": {}}
|
|
95
|
+
|
|
96
|
+
if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
|
|
97
|
+
data["mcpServers"] = {}
|
|
98
|
+
|
|
99
|
+
data["mcpServers"][server_name] = server_config
|
|
100
|
+
|
|
101
|
+
with open(cursor_path, "w", encoding="utf-8") as f:
|
|
102
|
+
json.dump(data, f, indent=2)
|
|
103
|
+
|
|
104
|
+
results.append(("Cursor IDE", cursor_path, True))
|
|
105
|
+
break # Updated primary cursor config
|
|
106
|
+
except Exception:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# 3. Windsurf
|
|
110
|
+
if target_client in ("all", "windsurf"):
|
|
111
|
+
windsurf_path = get_windsurf_config_path()
|
|
112
|
+
try:
|
|
113
|
+
windsurf_path.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
data = {"mcpServers": {}}
|
|
115
|
+
if windsurf_path.exists():
|
|
116
|
+
try:
|
|
117
|
+
with open(windsurf_path, "r", encoding="utf-8") as f:
|
|
118
|
+
data = json.load(f)
|
|
119
|
+
shutil.copy2(windsurf_path, str(windsurf_path) + ".bak")
|
|
120
|
+
except Exception:
|
|
121
|
+
data = {"mcpServers": {}}
|
|
122
|
+
|
|
123
|
+
if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
|
|
124
|
+
data["mcpServers"] = {}
|
|
125
|
+
|
|
126
|
+
data["mcpServers"][server_name] = server_config
|
|
127
|
+
|
|
128
|
+
with open(windsurf_path, "w", encoding="utf-8") as f:
|
|
129
|
+
json.dump(data, f, indent=2)
|
|
130
|
+
|
|
131
|
+
results.append(("Windsurf", windsurf_path, True))
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
return results
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def inspect_directory(dir_path: Path) -> Dict[str, Any]:
|
|
9
|
+
"""
|
|
10
|
+
Auto-detects manifest properties, prompts, instructions, and tools from a directory.
|
|
11
|
+
"""
|
|
12
|
+
detected: Dict[str, Any] = {
|
|
13
|
+
"name": dir_path.name.replace("-", " ").replace("_", " ").title(),
|
|
14
|
+
"slug": dir_path.name.lower().replace(" ", "-"),
|
|
15
|
+
"description": "",
|
|
16
|
+
"type": "prompt",
|
|
17
|
+
"instructions": "",
|
|
18
|
+
"version": "1.0.0",
|
|
19
|
+
"tools": [],
|
|
20
|
+
"variables": [],
|
|
21
|
+
"channel": "source",
|
|
22
|
+
"endpoint_url": None,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# 1. Check README.md
|
|
26
|
+
readme_file = next((f for f in dir_path.iterdir() if f.is_file() and f.name.lower() in ("readme.md", "readme.txt")), None)
|
|
27
|
+
if readme_file:
|
|
28
|
+
try:
|
|
29
|
+
content = readme_file.read_text(encoding="utf-8")
|
|
30
|
+
detected["readme"] = content
|
|
31
|
+
lines = [l.strip() for l in content.splitlines() if l.strip()]
|
|
32
|
+
if lines:
|
|
33
|
+
# First heading
|
|
34
|
+
for l in lines:
|
|
35
|
+
if l.startswith("# "):
|
|
36
|
+
detected["name"] = l.lstrip("# ").strip()
|
|
37
|
+
break
|
|
38
|
+
# First paragraph as description
|
|
39
|
+
for l in lines:
|
|
40
|
+
if not l.startswith("#") and len(l) > 10:
|
|
41
|
+
detected["description"] = l[:240]
|
|
42
|
+
break
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# 2. Check MCP Manifest: mcp.json / server.json
|
|
47
|
+
mcp_file = next((f for f in dir_path.iterdir() if f.is_file() and f.name.lower() in ("mcp.json", "server.json")), None)
|
|
48
|
+
if mcp_file:
|
|
49
|
+
try:
|
|
50
|
+
mcp_data = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
51
|
+
detected["type"] = "mcp_server"
|
|
52
|
+
detected["channel"] = "mcp"
|
|
53
|
+
if "name" in mcp_data:
|
|
54
|
+
detected["name"] = mcp_data["name"]
|
|
55
|
+
if "description" in mcp_data:
|
|
56
|
+
detected["description"] = mcp_data["description"]
|
|
57
|
+
if "tools" in mcp_data and isinstance(mcp_data["tools"], list):
|
|
58
|
+
detected["tools"] = mcp_data["tools"]
|
|
59
|
+
if "endpoint" in mcp_data or "url" in mcp_data:
|
|
60
|
+
detected["endpoint_url"] = mcp_data.get("endpoint") or mcp_data.get("url")
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
# 3. Check package.json
|
|
65
|
+
pkg_file = dir_path / "package.json"
|
|
66
|
+
if pkg_file.is_file():
|
|
67
|
+
try:
|
|
68
|
+
pkg_data = json.loads(pkg_file.read_text(encoding="utf-8"))
|
|
69
|
+
if "name" in pkg_data:
|
|
70
|
+
detected["name"] = pkg_data["name"].split("/")[-1].replace("-", " ").title()
|
|
71
|
+
detected["slug"] = pkg_data["name"].split("/")[-1].lower()
|
|
72
|
+
if "version" in pkg_data:
|
|
73
|
+
detected["version"] = pkg_data["version"]
|
|
74
|
+
if "description" in pkg_data:
|
|
75
|
+
detected["description"] = pkg_data["description"]
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# 4. Check prompt or instructions files
|
|
80
|
+
for candidate in ("instructions.txt", "instructions.md", "system_prompt.txt", "prompt.txt", "agent.py", "main.py"):
|
|
81
|
+
p_file = dir_path / candidate
|
|
82
|
+
if p_file.is_file():
|
|
83
|
+
try:
|
|
84
|
+
text = p_file.read_text(encoding="utf-8")
|
|
85
|
+
detected["instructions"] = text[:5000]
|
|
86
|
+
if not detected["type"] or detected["type"] == "prompt":
|
|
87
|
+
detected["type"] = "agent" if candidate in ("agent.py", "main.py") else "prompt"
|
|
88
|
+
break
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
return detected
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def create_bundle_zip(dir_path: Path, output_zip: Path) -> Path:
|
|
96
|
+
"""
|
|
97
|
+
Creates a zip bundle of the directory excluding temp/git artifacts.
|
|
98
|
+
"""
|
|
99
|
+
ignored_patterns = {".git", ".venv", "node_modules", "__pycache__", ".pytest_cache", ".DS_Store"}
|
|
100
|
+
with zipfile.ZipFile(output_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
101
|
+
for root, dirs, files in os.walk(dir_path):
|
|
102
|
+
dirs[:] = [d for d in dirs if d not in ignored_patterns]
|
|
103
|
+
for file in files:
|
|
104
|
+
if file in ignored_patterns:
|
|
105
|
+
continue
|
|
106
|
+
file_path = Path(root) / file
|
|
107
|
+
arcname = file_path.relative_to(dir_path)
|
|
108
|
+
zipf.write(file_path, arcname)
|
|
109
|
+
return output_zip
|