funchub-sdk 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.
- funchub_sdk-0.1.0/PKG-INFO +187 -0
- funchub_sdk-0.1.0/README.md +165 -0
- funchub_sdk-0.1.0/funchub/__init__.py +3 -0
- funchub_sdk-0.1.0/funchub/cli.py +204 -0
- funchub_sdk-0.1.0/funchub/client.py +224 -0
- funchub_sdk-0.1.0/funchub/decorators.py +84 -0
- funchub_sdk-0.1.0/funchub/exceptions.py +42 -0
- funchub_sdk-0.1.0/funchub/github_client.py +244 -0
- funchub_sdk-0.1.0/funchub/loader.py +151 -0
- funchub_sdk-0.1.0/funchub/models.py +33 -0
- funchub_sdk-0.1.0/funchub/version_parser.py +99 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/PKG-INFO +187 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/SOURCES.txt +26 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/dependency_links.txt +1 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/entry_points.txt +2 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/requires.txt +14 -0
- funchub_sdk-0.1.0/funchub_sdk.egg-info/top_level.txt +1 -0
- funchub_sdk-0.1.0/pyproject.toml +54 -0
- funchub_sdk-0.1.0/setup.cfg +4 -0
- funchub_sdk-0.1.0/tests/test_cache.py +154 -0
- funchub_sdk-0.1.0/tests/test_cli.py +158 -0
- funchub_sdk-0.1.0/tests/test_decorator.py +59 -0
- funchub_sdk-0.1.0/tests/test_exceptions.py +56 -0
- funchub_sdk-0.1.0/tests/test_github_client.py +368 -0
- funchub_sdk-0.1.0/tests/test_install.py +148 -0
- funchub_sdk-0.1.0/tests/test_loader.py +116 -0
- funchub_sdk-0.1.0/tests/test_version_parser.py +130 -0
- funchub_sdk-0.1.0/tests/test_windows_paths.py +71 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: funchub-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FuncHub - A tool registry and dynamic loader for AI agents
|
|
5
|
+
Author: FuncHub Team
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: click>=8.0
|
|
10
|
+
Requires-Dist: requests>=2.28
|
|
11
|
+
Requires-Dist: packaging>=23.0
|
|
12
|
+
Requires-Dist: PyYAML>=6.0
|
|
13
|
+
Requires-Dist: pydantic>=2.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-mock>=3.0; extra == "dev"
|
|
18
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
19
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
20
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
21
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# FuncHub
|
|
24
|
+
|
|
25
|
+
> ⚠️ **Security Warning**: FuncHub dynamically loads and executes code from remote Git repositories. **Only install tools from sources you trust**, and use in isolated environments.
|
|
26
|
+
|
|
27
|
+
FuncHub is a bilingual (Python + NestJS) tool registry and dynamic loader designed for AI Agents. It allows developers to publish, discover, install, and dynamically invoke tool functions.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Dual SDKs**: Python SDK + NestJS SDK, sharing the same design philosophy
|
|
32
|
+
- **Semver version management**: Supports `^1.2.3`, `1.x`, `latest`, branch names, etc.
|
|
33
|
+
- **GitHub publishing integration**: Automatic Fork + PR workflow via GitHub API v3
|
|
34
|
+
- **Cache management**: `.version` file tracks installed versions; expired cache auto-refetches
|
|
35
|
+
- **Private registry support**: Supports custom indexes and mirrors via config
|
|
36
|
+
- **Safety confirmation**: Interactive confirmation on install; `--yes` to bypass
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### Python
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install funchub-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### NestJS
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install funchub-nestjs
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Configure GitHub Token
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Python
|
|
62
|
+
funchub login --token ghp_xxxxxxxxxxxx
|
|
63
|
+
|
|
64
|
+
# NestJS
|
|
65
|
+
npx funchub login --token ghp_xxxxxxxxxxxx
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Search for Tools
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
funchub search scraper
|
|
72
|
+
funchub search web
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Install a Tool
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Install latest version
|
|
79
|
+
funchub install web_scraper
|
|
80
|
+
|
|
81
|
+
# Install with version constraint
|
|
82
|
+
funchub install web_scraper@^1.0
|
|
83
|
+
|
|
84
|
+
# Install development branch
|
|
85
|
+
funchub install web_scraper@main
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 4. List Installed Tools
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
funchub list
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 5. Use a Tool in Code
|
|
95
|
+
|
|
96
|
+
**Python:**
|
|
97
|
+
```python
|
|
98
|
+
from funchub import FuncHub
|
|
99
|
+
|
|
100
|
+
hub = FuncHub()
|
|
101
|
+
scraper = hub.load("web_scraper")
|
|
102
|
+
result = scraper(url="https://example.com")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**NestJS:**
|
|
106
|
+
```typescript
|
|
107
|
+
import { FuncHub } from '@funchub/nestjs';
|
|
108
|
+
|
|
109
|
+
const hub = new FuncHub();
|
|
110
|
+
const scraper = await hub.load('web_scraper');
|
|
111
|
+
const result = await scraper({ url: 'https://example.com' });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Command Reference
|
|
117
|
+
|
|
118
|
+
| Command | Description |
|
|
119
|
+
|---------|-------------|
|
|
120
|
+
| `funchub login --token <PAT>` | Save GitHub Personal Access Token |
|
|
121
|
+
| `funchub config set <key> <value>` | Set configuration (e.g., custom registry) |
|
|
122
|
+
| `funchub publish --version v1.0.0` | Publish current directory as a tool |
|
|
123
|
+
| `funchub publish --version v1.0.0 --force` | Overwrite existing tool with same name |
|
|
124
|
+
| `funchub publish --version v1.0.0 --dry-run` | Preview without actual submission |
|
|
125
|
+
| `funchub search <query>` | Search for tools in the registry |
|
|
126
|
+
| `funchub install <name>@<constraint>` | Install a tool |
|
|
127
|
+
| `funchub list` | List locally installed tools |
|
|
128
|
+
| `funchub update <name>` | Update a tool to latest version |
|
|
129
|
+
| `funchub update --all` | Update all installed tools |
|
|
130
|
+
| `funchub info <name>` | Show tool details |
|
|
131
|
+
| `funchub uninstall <name>` | Remove local tool cache |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Publishing a Tool
|
|
136
|
+
|
|
137
|
+
1. Create a `funchub.json` or `funchub.yaml` tool definition file in your project root:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"name": "my_tool",
|
|
142
|
+
"description": "My awesome tool",
|
|
143
|
+
"version": "1.0.0",
|
|
144
|
+
"entry_point": "src/index:handler",
|
|
145
|
+
"parameters": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"input": { "type": "string" }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
2. Publish:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
funchub publish --version v1.0.0
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If you have write access to the registry repo, the tool is committed directly. Otherwise, a Fork + PR is automatically created.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Decorator Usage (Python)
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from funchub import funchub_tool
|
|
168
|
+
|
|
169
|
+
@funchub_tool(
|
|
170
|
+
name="web_scraper",
|
|
171
|
+
description="Scrape web page title",
|
|
172
|
+
version="1.0.0"
|
|
173
|
+
)
|
|
174
|
+
def scrape_url(url: str) -> str:
|
|
175
|
+
"""Scrape the title from a URL."""
|
|
176
|
+
import requests
|
|
177
|
+
from bs4 import BeautifulSoup
|
|
178
|
+
resp = requests.get(url)
|
|
179
|
+
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
180
|
+
return soup.title.string if soup.title else "No title found"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
Apache License 2.0
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# FuncHub
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Security Warning**: FuncHub dynamically loads and executes code from remote Git repositories. **Only install tools from sources you trust**, and use in isolated environments.
|
|
4
|
+
|
|
5
|
+
FuncHub is a bilingual (Python + NestJS) tool registry and dynamic loader designed for AI Agents. It allows developers to publish, discover, install, and dynamically invoke tool functions.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Dual SDKs**: Python SDK + NestJS SDK, sharing the same design philosophy
|
|
10
|
+
- **Semver version management**: Supports `^1.2.3`, `1.x`, `latest`, branch names, etc.
|
|
11
|
+
- **GitHub publishing integration**: Automatic Fork + PR workflow via GitHub API v3
|
|
12
|
+
- **Cache management**: `.version` file tracks installed versions; expired cache auto-refetches
|
|
13
|
+
- **Private registry support**: Supports custom indexes and mirrors via config
|
|
14
|
+
- **Safety confirmation**: Interactive confirmation on install; `--yes` to bypass
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Python
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install funchub-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### NestJS
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install funchub-nestjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Configure GitHub Token
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Python
|
|
40
|
+
funchub login --token ghp_xxxxxxxxxxxx
|
|
41
|
+
|
|
42
|
+
# NestJS
|
|
43
|
+
npx funchub login --token ghp_xxxxxxxxxxxx
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Search for Tools
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
funchub search scraper
|
|
50
|
+
funchub search web
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Install a Tool
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Install latest version
|
|
57
|
+
funchub install web_scraper
|
|
58
|
+
|
|
59
|
+
# Install with version constraint
|
|
60
|
+
funchub install web_scraper@^1.0
|
|
61
|
+
|
|
62
|
+
# Install development branch
|
|
63
|
+
funchub install web_scraper@main
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. List Installed Tools
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
funchub list
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 5. Use a Tool in Code
|
|
73
|
+
|
|
74
|
+
**Python:**
|
|
75
|
+
```python
|
|
76
|
+
from funchub import FuncHub
|
|
77
|
+
|
|
78
|
+
hub = FuncHub()
|
|
79
|
+
scraper = hub.load("web_scraper")
|
|
80
|
+
result = scraper(url="https://example.com")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**NestJS:**
|
|
84
|
+
```typescript
|
|
85
|
+
import { FuncHub } from '@funchub/nestjs';
|
|
86
|
+
|
|
87
|
+
const hub = new FuncHub();
|
|
88
|
+
const scraper = await hub.load('web_scraper');
|
|
89
|
+
const result = await scraper({ url: 'https://example.com' });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Command Reference
|
|
95
|
+
|
|
96
|
+
| Command | Description |
|
|
97
|
+
|---------|-------------|
|
|
98
|
+
| `funchub login --token <PAT>` | Save GitHub Personal Access Token |
|
|
99
|
+
| `funchub config set <key> <value>` | Set configuration (e.g., custom registry) |
|
|
100
|
+
| `funchub publish --version v1.0.0` | Publish current directory as a tool |
|
|
101
|
+
| `funchub publish --version v1.0.0 --force` | Overwrite existing tool with same name |
|
|
102
|
+
| `funchub publish --version v1.0.0 --dry-run` | Preview without actual submission |
|
|
103
|
+
| `funchub search <query>` | Search for tools in the registry |
|
|
104
|
+
| `funchub install <name>@<constraint>` | Install a tool |
|
|
105
|
+
| `funchub list` | List locally installed tools |
|
|
106
|
+
| `funchub update <name>` | Update a tool to latest version |
|
|
107
|
+
| `funchub update --all` | Update all installed tools |
|
|
108
|
+
| `funchub info <name>` | Show tool details |
|
|
109
|
+
| `funchub uninstall <name>` | Remove local tool cache |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Publishing a Tool
|
|
114
|
+
|
|
115
|
+
1. Create a `funchub.json` or `funchub.yaml` tool definition file in your project root:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"name": "my_tool",
|
|
120
|
+
"description": "My awesome tool",
|
|
121
|
+
"version": "1.0.0",
|
|
122
|
+
"entry_point": "src/index:handler",
|
|
123
|
+
"parameters": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"properties": {
|
|
126
|
+
"input": { "type": "string" }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
2. Publish:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
funchub publish --version v1.0.0
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If you have write access to the registry repo, the tool is committed directly. Otherwise, a Fork + PR is automatically created.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Decorator Usage (Python)
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from funchub import funchub_tool
|
|
146
|
+
|
|
147
|
+
@funchub_tool(
|
|
148
|
+
name="web_scraper",
|
|
149
|
+
description="Scrape web page title",
|
|
150
|
+
version="1.0.0"
|
|
151
|
+
)
|
|
152
|
+
def scrape_url(url: str) -> str:
|
|
153
|
+
"""Scrape the title from a URL."""
|
|
154
|
+
import requests
|
|
155
|
+
from bs4 import BeautifulSoup
|
|
156
|
+
resp = requests.get(url)
|
|
157
|
+
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
158
|
+
return soup.title.string if soup.title else "No title found"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
Apache License 2.0
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from funchub.client import FuncHub
|
|
9
|
+
from funchub.exceptions import ConflictError, FuncHubError, ToolNotFoundError, VersionNotFoundError
|
|
10
|
+
from funchub.github_client import load_config, save_config
|
|
11
|
+
from funchub.models import ToolDefinition, ToolVersion
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
@click.option("--registry", "-r", envvar="FUNCHUB_REGISTRY", help="中央索引 URL")
|
|
16
|
+
@click.option("--token", envvar="GITHUB_TOKEN", help="GitHub PAT")
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def cli(ctx: click.Context, registry: Optional[str], token: Optional[str]) -> None:
|
|
19
|
+
ctx.ensure_object(dict)
|
|
20
|
+
ctx.obj["hub"] = FuncHub(registry=registry, token=token)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@cli.command()
|
|
24
|
+
@click.option("--token", required=True, help="GitHub Personal Access Token")
|
|
25
|
+
def login(token: str) -> None:
|
|
26
|
+
cfg = load_config()
|
|
27
|
+
cfg["github_token"] = token
|
|
28
|
+
save_config(cfg)
|
|
29
|
+
click.echo("GitHub Token 已保存到 ~/.funchub/config.yaml")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@cli.command()
|
|
33
|
+
@click.argument("key")
|
|
34
|
+
@click.argument("value")
|
|
35
|
+
def config(key: str, value: str) -> None:
|
|
36
|
+
cfg = load_config()
|
|
37
|
+
cfg[key] = value
|
|
38
|
+
save_config(cfg)
|
|
39
|
+
click.echo(f"配置项 {key} 已设置为 {value}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@cli.command()
|
|
43
|
+
@click.option("--version", "ver", required=True, help="发布的版本号")
|
|
44
|
+
@click.option("--force", is_flag=True, help="覆盖同名工具")
|
|
45
|
+
@click.option("--dry-run", is_flag=True, help="预览不实际提交")
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def publish(ctx: click.Context, ver: str, force: bool, dry_run: bool) -> None:
|
|
48
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
49
|
+
tool_file = Path("funchub-tool.yaml")
|
|
50
|
+
if not tool_file.exists():
|
|
51
|
+
click.echo("错误: 当前目录未找到 funchub-tool.yaml", err=True)
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
raw = tool_file.read_text(encoding="utf-8")
|
|
54
|
+
data = yaml.safe_load(raw)
|
|
55
|
+
is_prerelease = any(tag in ver.lower() for tag in ("alpha", "beta", "rc", "pre"))
|
|
56
|
+
tv = ToolVersion(
|
|
57
|
+
version=ver,
|
|
58
|
+
source_repo=data.get("source_repo", ""),
|
|
59
|
+
source_ref=data.get("source_ref", f"v{ver}"),
|
|
60
|
+
dependencies=data.get("dependencies", []),
|
|
61
|
+
is_prerelease=is_prerelease,
|
|
62
|
+
)
|
|
63
|
+
tool_def = ToolDefinition(
|
|
64
|
+
name=data["name"],
|
|
65
|
+
description=data.get("description", ""),
|
|
66
|
+
parameters=data.get("parameters", {"type": "object", "properties": {}}),
|
|
67
|
+
author=data.get("author", "anonymous"),
|
|
68
|
+
entry_point=data.get("entry_point", "index:main"),
|
|
69
|
+
versions=[tv],
|
|
70
|
+
)
|
|
71
|
+
try:
|
|
72
|
+
result = hub.publish(tool_def, force=force, dry_run=dry_run)
|
|
73
|
+
if dry_run:
|
|
74
|
+
click.echo(f"[DRY RUN] {result}")
|
|
75
|
+
else:
|
|
76
|
+
click.echo(f"发布成功: {result}")
|
|
77
|
+
except ConflictError as e:
|
|
78
|
+
click.echo(f"错误: {e}", err=True)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
except FuncHubError as e:
|
|
81
|
+
click.echo(f"错误: {e}", err=True)
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@cli.command()
|
|
86
|
+
@click.argument("query")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def search(ctx: click.Context, query: str) -> None:
|
|
89
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
90
|
+
results = hub.search(query)
|
|
91
|
+
if not results:
|
|
92
|
+
click.echo("未找到匹配的工具")
|
|
93
|
+
return
|
|
94
|
+
for t in results:
|
|
95
|
+
click.echo(f"{t.name} - {t.description}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@cli.command()
|
|
99
|
+
@click.argument("tool_spec")
|
|
100
|
+
@click.option("--prerelease", is_flag=True, help="包含预发布版本")
|
|
101
|
+
@click.option("--yes", is_flag=True, help="跳过安全确认")
|
|
102
|
+
@click.pass_context
|
|
103
|
+
def install(ctx: click.Context, tool_spec: str, prerelease: bool, yes: bool) -> None:
|
|
104
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
105
|
+
constraint: Optional[str] = None
|
|
106
|
+
tool_name = tool_spec
|
|
107
|
+
if "@" in tool_spec:
|
|
108
|
+
tool_name, constraint = tool_spec.split("@", 1)
|
|
109
|
+
try:
|
|
110
|
+
hub.install(
|
|
111
|
+
tool_name,
|
|
112
|
+
constraint=constraint,
|
|
113
|
+
include_prerelease=prerelease,
|
|
114
|
+
yes=yes,
|
|
115
|
+
)
|
|
116
|
+
click.echo(f"✅ 工具 {tool_name} 安装成功")
|
|
117
|
+
except (ToolNotFoundError, VersionNotFoundError) as e:
|
|
118
|
+
click.echo(f"错误: {e}", err=True)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
except FuncHubError as e:
|
|
121
|
+
click.echo(f"错误: {e}", err=True)
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@cli.command("list")
|
|
126
|
+
@click.pass_context
|
|
127
|
+
def list_tools(ctx: click.Context) -> None:
|
|
128
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
129
|
+
items = hub.list_installed()
|
|
130
|
+
if not items:
|
|
131
|
+
click.echo("未安装任何工具")
|
|
132
|
+
return
|
|
133
|
+
for item in items:
|
|
134
|
+
click.echo(f"{item['name']}@{item['version']} ({item['source_repo']})")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@cli.command()
|
|
138
|
+
@click.argument("name", required=False)
|
|
139
|
+
@click.option("--all", "update_all", is_flag=True, help="更新所有工具")
|
|
140
|
+
@click.option("--prerelease", is_flag=True, help="包含预发布版本")
|
|
141
|
+
@click.option("--yes", is_flag=True, help="跳过安全确认")
|
|
142
|
+
@click.pass_context
|
|
143
|
+
def update(
|
|
144
|
+
ctx: click.Context,
|
|
145
|
+
name: Optional[str],
|
|
146
|
+
update_all: bool,
|
|
147
|
+
prerelease: bool,
|
|
148
|
+
yes: bool,
|
|
149
|
+
) -> None:
|
|
150
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
151
|
+
if update_all:
|
|
152
|
+
results = hub.update_all(include_prerelease=prerelease, yes=yes)
|
|
153
|
+
if not results:
|
|
154
|
+
click.echo("所有工具已是最新")
|
|
155
|
+
for r in results:
|
|
156
|
+
click.echo(r)
|
|
157
|
+
return
|
|
158
|
+
if not name:
|
|
159
|
+
click.echo("请指定工具名称或使用 --all", err=True)
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
try:
|
|
162
|
+
result = hub.update(name, include_prerelease=prerelease, yes=yes)
|
|
163
|
+
click.echo(f"✅ 工具 {name} 已更新到 {result}")
|
|
164
|
+
except FuncHubError as e:
|
|
165
|
+
click.echo(f"错误: {e}", err=True)
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@cli.command()
|
|
170
|
+
@click.argument("name")
|
|
171
|
+
@click.pass_context
|
|
172
|
+
def info(ctx: click.Context, name: str) -> None:
|
|
173
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
174
|
+
tool_def = hub.info(name)
|
|
175
|
+
if tool_def is None:
|
|
176
|
+
click.echo(f"工具 '{name}' 未找到")
|
|
177
|
+
return
|
|
178
|
+
click.echo(f"名称: {tool_def.name}")
|
|
179
|
+
click.echo(f"描述: {tool_def.description}")
|
|
180
|
+
click.echo(f"作者: {tool_def.author}")
|
|
181
|
+
click.echo(f"入口: {tool_def.entry_point}")
|
|
182
|
+
click.echo("版本:")
|
|
183
|
+
for v in tool_def.versions:
|
|
184
|
+
pre = " (预发布)" if v.is_prerelease else ""
|
|
185
|
+
click.echo(f" - {v.version}{pre}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@cli.command()
|
|
189
|
+
@click.argument("name")
|
|
190
|
+
@click.pass_context
|
|
191
|
+
def uninstall(ctx: click.Context, name: str) -> None:
|
|
192
|
+
hub: FuncHub = ctx.obj["hub"]
|
|
193
|
+
if hub.uninstall(name):
|
|
194
|
+
click.echo(f"✅ 工具 {name} 已卸载")
|
|
195
|
+
else:
|
|
196
|
+
click.echo(f"工具 {name} 未安装")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def main() -> None:
|
|
200
|
+
cli()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
main()
|