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.
@@ -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
+ [![PyPI Version](https://img.shields.io/pypi/v/aistorehub.svg)](https://pypi.org/project/aistorehub/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
46
+ [![Python: 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](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
+ [![PyPI Version](https://img.shields.io/pypi/v/aistorehub.svg)](https://pypi.org/project/aistorehub/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Python: 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](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,4 @@
1
+ """AIStoreHub CLI package alias."""
2
+ from sihub.main import cli
3
+
4
+ __all__ = ["cli"]
@@ -0,0 +1,4 @@
1
+ from sihub.main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -0,0 +1,4 @@
1
+ """AIStoreHub CLI package alias."""
2
+ from sihub.main import cli
3
+
4
+ __all__ = ["cli"]
@@ -0,0 +1,4 @@
1
+ from sihub.main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -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
+ [![PyPI Version](https://img.shields.io/pypi/v/aistorehub.svg)](https://pypi.org/project/aistorehub/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
46
+ [![Python: 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](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,4 @@
1
+ [console_scripts]
2
+ aihub = sihub.main:cli
3
+ aistorehub = sihub.main:cli
4
+ sihub = sihub.main:cli
@@ -0,0 +1,2 @@
1
+ click>=8.0.0
2
+ httpx>=0.25.0
@@ -0,0 +1,3 @@
1
+ aihub
2
+ aistorehub
3
+ sihub
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,4 @@
1
+ """
2
+ sihub - The Official CLI for AIStoreHub
3
+ """
4
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -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