nutria-plugin 0.0.1a0__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.
- nutria_plugin-0.0.1a0/.github/workflows/publish.yml +44 -0
- nutria_plugin-0.0.1a0/.gitignore +10 -0
- nutria_plugin-0.0.1a0/CHANGELOG.md +86 -0
- nutria_plugin-0.0.1a0/PKG-INFO +180 -0
- nutria_plugin-0.0.1a0/README.md +161 -0
- nutria_plugin-0.0.1a0/docs/cli.md +189 -0
- nutria_plugin-0.0.1a0/docs/connection-types.md +320 -0
- nutria_plugin-0.0.1a0/docs/index.md +53 -0
- nutria_plugin-0.0.1a0/docs/manifest.md +252 -0
- nutria_plugin-0.0.1a0/docs/python-api.md +412 -0
- nutria_plugin-0.0.1a0/docs/quickstart.md +215 -0
- nutria_plugin-0.0.1a0/docs/security.md +162 -0
- nutria_plugin-0.0.1a0/docs/skill-format.md +201 -0
- nutria_plugin-0.0.1a0/examples/my-first-plugin/README.md +7 -0
- nutria_plugin-0.0.1a0/examples/my-first-plugin/hooks/hooks.json +1 -0
- nutria_plugin-0.0.1a0/examples/my-first-plugin/plugin.json +14 -0
- nutria_plugin-0.0.1a0/examples/my-first-plugin/settings.schema.json +1 -0
- nutria_plugin-0.0.1a0/pyproject.toml +51 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/__init__.py +54 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/bundle.py +172 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/cli.py +157 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/manifest.py +182 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/packaging.py +202 -0
- nutria_plugin-0.0.1a0/src/nutria_plugin/signing.py +142 -0
- nutria_plugin-0.0.1a0/tests/test_bundle.py +157 -0
- nutria_plugin-0.0.1a0/tests/test_cli.py +69 -0
- nutria_plugin-0.0.1a0/tests/test_manifest.py +135 -0
- nutria_plugin-0.0.1a0/tests/test_packaging.py +131 -0
- nutria_plugin-0.0.1a0/tests/test_signing.py +133 -0
- nutria_plugin-0.0.1a0/uv.lock +524 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Build packages
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Upload dist artifacts
|
|
24
|
+
uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
name: Publish to PyPI
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment: pypi
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write # required for OIDC trusted publishing
|
|
36
|
+
steps:
|
|
37
|
+
- name: Download dist artifacts
|
|
38
|
+
uses: actions/download-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
|
|
43
|
+
- name: Publish to PyPI
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `nutria-plugin` are documented here.
|
|
4
|
+
|
|
5
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [0.0.1-alpha] — 2025-03-08
|
|
11
|
+
|
|
12
|
+
Initial alpha release. API and file format are not yet stable.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
**Manifest model (`manifest.py`)**
|
|
17
|
+
- `PluginManifest` — Pydantic v2 model for `plugin.json` with strict validation
|
|
18
|
+
- Fields: `id`, `name`, `version`, `description`, `author`, `runtime_types`,
|
|
19
|
+
`default_scope`, `compatibility`, `paths`, `required_secrets`,
|
|
20
|
+
`remote_endpoints`, `capabilities`, `tags`, `homepage`, `license`, `signature`
|
|
21
|
+
- `PluginRuntimeType` enum: `remote_mcp`, `declarative_api`, `openapi_bridge`, `soap_bridge`
|
|
22
|
+
- `PluginScope` enum: `platform`, `store`, `persona`
|
|
23
|
+
- `PluginCompatibility` model with semver-validated `min_nutria_version` / `max_nutria_version`
|
|
24
|
+
- `PluginPaths` model for overriding default component paths
|
|
25
|
+
- `PluginManifest.from_file()`, `from_json_bytes()`, `to_file()` helpers
|
|
26
|
+
|
|
27
|
+
**Bundle operations (`bundle.py`)**
|
|
28
|
+
- `load_plugin_bundle(data: bytes) -> PluginManifest` — parse and validate a plugin ZIP
|
|
29
|
+
- `extract_plugin_bundle(data: bytes, target_dir: Path) -> PluginManifest` — safe extraction
|
|
30
|
+
- `validate_zip(data: bytes) -> list[str]` — non-extracting ZIP validation
|
|
31
|
+
- `PluginBundleError` exception
|
|
32
|
+
- Decompression bomb guard: 100 MB uncompressed size limit
|
|
33
|
+
- Extension allowlist: `.json .md .yaml .yml .txt .png .jpg .jpeg .svg .ico .pdf .wsdl .xsd .xml .csv`
|
|
34
|
+
- Path traversal prevention via `PurePosixPath` normalization
|
|
35
|
+
- Maximum ZIP size: 20 MB
|
|
36
|
+
|
|
37
|
+
**Packaging (`packaging.py`)**
|
|
38
|
+
- `scaffold_plugin(plugin_id, name, target_dir)` — create standard plugin directory
|
|
39
|
+
- `pack_plugin(plugin_dir, output_path, sign, private_key_pem) -> Path` — validate and pack
|
|
40
|
+
- `validate_plugin_dir(plugin_dir) -> list[str]` — directory validation (hidden files skipped)
|
|
41
|
+
- `PackagingError` exception
|
|
42
|
+
- Symlink rejection at pack time
|
|
43
|
+
- Hidden file skipping (consistent between `validate_plugin_dir` and `pack_plugin`)
|
|
44
|
+
|
|
45
|
+
**Signing (`signing.py`)**
|
|
46
|
+
- `generate_keypair() -> tuple[str, str]` — ECDSA P-256 key pair generation
|
|
47
|
+
- `sign_manifest(manifest: dict, private_key_pem: str) -> str` — sign and return hex DER signature
|
|
48
|
+
- `verify_manifest(manifest: dict) -> SignatureStatus` — verify against `NUTRIA_PLUGIN_TRUSTED_KEYS`
|
|
49
|
+
- `SignatureStatus` enum: `VERIFIED`, `UNSIGNED`, `INVALID`, `UNTRUSTED`, `MISSING`
|
|
50
|
+
- Canonical payload serialization (sorted keys, no `signature` field, no whitespace)
|
|
51
|
+
- `NUTRIA_PLUGIN_TRUSTED_KEYS` env var for trusted public key list
|
|
52
|
+
|
|
53
|
+
**CLI (`cli.py`)**
|
|
54
|
+
- `nutria-plugin new <id>` — scaffold plugin directory
|
|
55
|
+
- `nutria-plugin validate [dir]` — validate plugin directory
|
|
56
|
+
- `nutria-plugin pack [dir]` — validate and pack to ZIP
|
|
57
|
+
- `nutria-plugin sign [manifest] --key <pem>` — sign manifest in-place
|
|
58
|
+
- `nutria-plugin keygen [--out <stem>]` — generate ECDSA P-256 key pair
|
|
59
|
+
- `--key` flag on `pack` for inline sign-and-pack
|
|
60
|
+
- `--output`/`-o` flag on `pack` for custom output path
|
|
61
|
+
|
|
62
|
+
**Documentation**
|
|
63
|
+
- `docs/index.md` — overview and navigation
|
|
64
|
+
- `docs/quickstart.md` — first plugin in 5 minutes
|
|
65
|
+
- `docs/manifest.md` — complete `plugin.json` field reference
|
|
66
|
+
- `docs/connection-types.md` — all 4 runtime types with annotated examples
|
|
67
|
+
- `docs/skill-format.md` — `SKILL.md` frontmatter schema and authoring guide
|
|
68
|
+
- `docs/security.md` — signing, trust policies, ZIP safety, secrets
|
|
69
|
+
- `docs/cli.md` — all CLI commands and flags
|
|
70
|
+
- `docs/python-api.md` — Python API reference
|
|
71
|
+
|
|
72
|
+
**Security hardening**
|
|
73
|
+
- SSRF protection: `remote_endpoints` blocks loopback, private, link-local, reserved IPs
|
|
74
|
+
- Decompression bomb: 100 MB limit on `ZipInfo.file_size` before extraction
|
|
75
|
+
- Symlink blocking: `PackagingError` raised on any symlink in plugin source
|
|
76
|
+
- Extension allowlist (not blocklist)
|
|
77
|
+
- CLI `keygen --out` path traversal prevention (output path must be within CWD)
|
|
78
|
+
- `_safe_zip_path`: checks `".." in path.parts` on normalized `PurePosixPath`
|
|
79
|
+
|
|
80
|
+
### Notes
|
|
81
|
+
|
|
82
|
+
- This is an alpha release. Manifest schema, connection file format, and
|
|
83
|
+
Python API may change before `0.1.0`.
|
|
84
|
+
- `declarative_api` connection file format is not yet formally versioned.
|
|
85
|
+
- WSDL/OpenAPI bridge tool name conventions are established but the bridge
|
|
86
|
+
runtime is implemented in the ChatBotNutralia host, not this package.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nutria-plugin
|
|
3
|
+
Version: 0.0.1a0
|
|
4
|
+
Summary: SDK for building, validating, signing, and packaging Nutria plugins
|
|
5
|
+
Project-URL: Homepage, https://github.com/AlRos14/nutria-plugin-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/AlRos14/nutria-plugin-sdk
|
|
7
|
+
Project-URL: Changelog, https://github.com/AlRos14/nutria-plugin-sdk/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Nutria <dev@nutria.ai>
|
|
9
|
+
License: MIT
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: cryptography>=41.0
|
|
12
|
+
Requires-Dist: lxml>=4.9
|
|
13
|
+
Requires-Dist: pydantic>=2.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# nutria-plugin SDK
|
|
21
|
+
|
|
22
|
+
SDK for building, validating, signing, and packaging Nutria plugins.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install nutria-plugin
|
|
28
|
+
# or with uv
|
|
29
|
+
uv add nutria-plugin
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
### 1. Scaffold a new plugin
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
nutria-plugin new my-workspace-plugin --name "My Workspace Plugin"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This creates:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
my-workspace-plugin/
|
|
44
|
+
plugin.json # manifest — edit this
|
|
45
|
+
README.md
|
|
46
|
+
connections/ # one JSON file per connection
|
|
47
|
+
skills/ # SKILL.md files
|
|
48
|
+
context_docs/ # Markdown docs injected into persona prompts
|
|
49
|
+
specs/ # OpenAPI/WSDL specs
|
|
50
|
+
hooks/hooks.json # declarative hooks
|
|
51
|
+
settings.schema.json # config schema shown in admin UI
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Edit plugin.json
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"schema_version": "1.0",
|
|
59
|
+
"id": "my-workspace-plugin",
|
|
60
|
+
"name": "My Workspace Plugin",
|
|
61
|
+
"version": "0.1.0",
|
|
62
|
+
"description": "Connects Nutria to My Workspace tool",
|
|
63
|
+
"author": "Your Name",
|
|
64
|
+
"runtime_types": ["declarative_api"],
|
|
65
|
+
"required_secrets": ["API_KEY"],
|
|
66
|
+
"remote_endpoints": ["https://api.myworkspace.com"]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Validate
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
nutria-plugin validate .
|
|
74
|
+
# OK
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 4. Pack
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
nutria-plugin pack . --output my-workspace-plugin-0.1.0.zip
|
|
81
|
+
# Packed: my-workspace-plugin-0.1.0.zip
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 5. Sign (optional)
|
|
85
|
+
|
|
86
|
+
Generate a key pair once:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
nutria-plugin keygen --out my-signing-key
|
|
90
|
+
# Private key: my-signing-key.pem
|
|
91
|
+
# Public key: my-signing-key.pub.pem
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Sign before packing:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
nutria-plugin sign plugin.json --key my-signing-key.pem
|
|
98
|
+
nutria-plugin pack . --output my-workspace-plugin-0.1.0.zip
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or sign during pack:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
nutria-plugin pack . --key my-signing-key.pem --output my-workspace-plugin-0.1.0.zip
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Configure the Nutria instance to trust your public key:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
export NUTRIA_PLUGIN_TRUSTED_KEYS='["-----BEGIN PUBLIC KEY-----\n..."]'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Python API
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from nutria_plugin import (
|
|
117
|
+
PluginManifest,
|
|
118
|
+
load_plugin_bundle,
|
|
119
|
+
extract_plugin_bundle,
|
|
120
|
+
validate_zip,
|
|
121
|
+
scaffold_plugin,
|
|
122
|
+
pack_plugin,
|
|
123
|
+
validate_plugin_dir,
|
|
124
|
+
generate_keypair,
|
|
125
|
+
sign_manifest,
|
|
126
|
+
verify_manifest,
|
|
127
|
+
SignatureStatus,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Parse a manifest
|
|
131
|
+
manifest = PluginManifest.from_file(Path("plugin.json"))
|
|
132
|
+
|
|
133
|
+
# Load from ZIP bytes
|
|
134
|
+
manifest = load_plugin_bundle(zip_bytes)
|
|
135
|
+
|
|
136
|
+
# Extract to disk
|
|
137
|
+
manifest = extract_plugin_bundle(zip_bytes, target_dir)
|
|
138
|
+
|
|
139
|
+
# Sign and verify
|
|
140
|
+
private_pem, public_pem = generate_keypair()
|
|
141
|
+
sig = sign_manifest(manifest.model_dump(), private_pem)
|
|
142
|
+
status = verify_manifest(manifest.model_dump())
|
|
143
|
+
assert status == SignatureStatus.VERIFIED
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Plugin ZIP format
|
|
147
|
+
|
|
148
|
+
| Path | Description |
|
|
149
|
+
|------|-------------|
|
|
150
|
+
| `plugin.json` | Manifest (required) |
|
|
151
|
+
| `README.md` | Human-readable description |
|
|
152
|
+
| `connections/*.json` | Connection definitions |
|
|
153
|
+
| `skills/<name>/SKILL.md` | Skill instructions |
|
|
154
|
+
| `context_docs/*.md` | Docs injected into persona prompts |
|
|
155
|
+
| `specs/openapi.json` | OpenAPI spec (for `openapi_bridge` runtime) |
|
|
156
|
+
| `specs/service.wsdl` | WSDL spec (for `soap_bridge` runtime) |
|
|
157
|
+
| `hooks/hooks.json` | Declarative hooks |
|
|
158
|
+
| `settings.schema.json` | JSON Schema for configuration |
|
|
159
|
+
| `assets/icon.png` | Plugin icon |
|
|
160
|
+
|
|
161
|
+
### Security rules
|
|
162
|
+
|
|
163
|
+
- No executable files (`.py`, `.js`, `.sh`, etc.) allowed in the ZIP.
|
|
164
|
+
- No hidden files or directories.
|
|
165
|
+
- No absolute paths or path traversal in ZIP entries.
|
|
166
|
+
- Maximum bundle size: 20 MB.
|
|
167
|
+
- Secrets are **never** stored in the ZIP — they are configured after install.
|
|
168
|
+
|
|
169
|
+
## Runtime types
|
|
170
|
+
|
|
171
|
+
| Value | Description |
|
|
172
|
+
|-------|-------------|
|
|
173
|
+
| `remote_mcp` | Plugin connects to a separately deployed MCP server |
|
|
174
|
+
| `declarative_api` | Plugin uses declarative connection JSON files (no server needed) |
|
|
175
|
+
| `openapi_bridge` | Nutria auto-generates tools from an OpenAPI/Swagger spec |
|
|
176
|
+
| `soap_bridge` | Nutria auto-generates tools from a WSDL/SOAP spec |
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# nutria-plugin SDK
|
|
2
|
+
|
|
3
|
+
SDK for building, validating, signing, and packaging Nutria plugins.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nutria-plugin
|
|
9
|
+
# or with uv
|
|
10
|
+
uv add nutria-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
### 1. Scaffold a new plugin
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
nutria-plugin new my-workspace-plugin --name "My Workspace Plugin"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This creates:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
my-workspace-plugin/
|
|
25
|
+
plugin.json # manifest — edit this
|
|
26
|
+
README.md
|
|
27
|
+
connections/ # one JSON file per connection
|
|
28
|
+
skills/ # SKILL.md files
|
|
29
|
+
context_docs/ # Markdown docs injected into persona prompts
|
|
30
|
+
specs/ # OpenAPI/WSDL specs
|
|
31
|
+
hooks/hooks.json # declarative hooks
|
|
32
|
+
settings.schema.json # config schema shown in admin UI
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Edit plugin.json
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"schema_version": "1.0",
|
|
40
|
+
"id": "my-workspace-plugin",
|
|
41
|
+
"name": "My Workspace Plugin",
|
|
42
|
+
"version": "0.1.0",
|
|
43
|
+
"description": "Connects Nutria to My Workspace tool",
|
|
44
|
+
"author": "Your Name",
|
|
45
|
+
"runtime_types": ["declarative_api"],
|
|
46
|
+
"required_secrets": ["API_KEY"],
|
|
47
|
+
"remote_endpoints": ["https://api.myworkspace.com"]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Validate
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
nutria-plugin validate .
|
|
55
|
+
# OK
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 4. Pack
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
nutria-plugin pack . --output my-workspace-plugin-0.1.0.zip
|
|
62
|
+
# Packed: my-workspace-plugin-0.1.0.zip
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 5. Sign (optional)
|
|
66
|
+
|
|
67
|
+
Generate a key pair once:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
nutria-plugin keygen --out my-signing-key
|
|
71
|
+
# Private key: my-signing-key.pem
|
|
72
|
+
# Public key: my-signing-key.pub.pem
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Sign before packing:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
nutria-plugin sign plugin.json --key my-signing-key.pem
|
|
79
|
+
nutria-plugin pack . --output my-workspace-plugin-0.1.0.zip
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or sign during pack:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
nutria-plugin pack . --key my-signing-key.pem --output my-workspace-plugin-0.1.0.zip
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Configure the Nutria instance to trust your public key:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
export NUTRIA_PLUGIN_TRUSTED_KEYS='["-----BEGIN PUBLIC KEY-----\n..."]'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Python API
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from nutria_plugin import (
|
|
98
|
+
PluginManifest,
|
|
99
|
+
load_plugin_bundle,
|
|
100
|
+
extract_plugin_bundle,
|
|
101
|
+
validate_zip,
|
|
102
|
+
scaffold_plugin,
|
|
103
|
+
pack_plugin,
|
|
104
|
+
validate_plugin_dir,
|
|
105
|
+
generate_keypair,
|
|
106
|
+
sign_manifest,
|
|
107
|
+
verify_manifest,
|
|
108
|
+
SignatureStatus,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Parse a manifest
|
|
112
|
+
manifest = PluginManifest.from_file(Path("plugin.json"))
|
|
113
|
+
|
|
114
|
+
# Load from ZIP bytes
|
|
115
|
+
manifest = load_plugin_bundle(zip_bytes)
|
|
116
|
+
|
|
117
|
+
# Extract to disk
|
|
118
|
+
manifest = extract_plugin_bundle(zip_bytes, target_dir)
|
|
119
|
+
|
|
120
|
+
# Sign and verify
|
|
121
|
+
private_pem, public_pem = generate_keypair()
|
|
122
|
+
sig = sign_manifest(manifest.model_dump(), private_pem)
|
|
123
|
+
status = verify_manifest(manifest.model_dump())
|
|
124
|
+
assert status == SignatureStatus.VERIFIED
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Plugin ZIP format
|
|
128
|
+
|
|
129
|
+
| Path | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `plugin.json` | Manifest (required) |
|
|
132
|
+
| `README.md` | Human-readable description |
|
|
133
|
+
| `connections/*.json` | Connection definitions |
|
|
134
|
+
| `skills/<name>/SKILL.md` | Skill instructions |
|
|
135
|
+
| `context_docs/*.md` | Docs injected into persona prompts |
|
|
136
|
+
| `specs/openapi.json` | OpenAPI spec (for `openapi_bridge` runtime) |
|
|
137
|
+
| `specs/service.wsdl` | WSDL spec (for `soap_bridge` runtime) |
|
|
138
|
+
| `hooks/hooks.json` | Declarative hooks |
|
|
139
|
+
| `settings.schema.json` | JSON Schema for configuration |
|
|
140
|
+
| `assets/icon.png` | Plugin icon |
|
|
141
|
+
|
|
142
|
+
### Security rules
|
|
143
|
+
|
|
144
|
+
- No executable files (`.py`, `.js`, `.sh`, etc.) allowed in the ZIP.
|
|
145
|
+
- No hidden files or directories.
|
|
146
|
+
- No absolute paths or path traversal in ZIP entries.
|
|
147
|
+
- Maximum bundle size: 20 MB.
|
|
148
|
+
- Secrets are **never** stored in the ZIP — they are configured after install.
|
|
149
|
+
|
|
150
|
+
## Runtime types
|
|
151
|
+
|
|
152
|
+
| Value | Description |
|
|
153
|
+
|-------|-------------|
|
|
154
|
+
| `remote_mcp` | Plugin connects to a separately deployed MCP server |
|
|
155
|
+
| `declarative_api` | Plugin uses declarative connection JSON files (no server needed) |
|
|
156
|
+
| `openapi_bridge` | Nutria auto-generates tools from an OpenAPI/Swagger spec |
|
|
157
|
+
| `soap_bridge` | Nutria auto-generates tools from a WSDL/SOAP spec |
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
The `nutria-plugin` CLI is the primary tool for plugin development.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add nutria-plugin
|
|
9
|
+
# or
|
|
10
|
+
pip install nutria-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## `nutria-plugin new` — Scaffold a plugin
|
|
16
|
+
|
|
17
|
+
Create a new plugin directory with the standard structure.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
nutria-plugin new <id> [--name <display-name>] [--dir <target-dir>]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Argument | Description |
|
|
24
|
+
|----------|-------------|
|
|
25
|
+
| `id` | Plugin ID (lowercase, hyphens allowed, e.g. `my-plugin`) |
|
|
26
|
+
| `--name` | Display name. Defaults to title-cased `id` |
|
|
27
|
+
| `--dir` | Target directory. Defaults to `<id>/` in the current directory |
|
|
28
|
+
|
|
29
|
+
**Examples:**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Scaffold in ./my-crm-plugin/
|
|
33
|
+
nutria-plugin new my-crm-plugin
|
|
34
|
+
|
|
35
|
+
# Custom name and directory
|
|
36
|
+
nutria-plugin new my-crm-plugin --name "My CRM Plugin" --dir ~/plugins/my-crm
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Created structure:**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
<dir>/
|
|
43
|
+
plugin.json # pre-filled manifest
|
|
44
|
+
README.md
|
|
45
|
+
connections/ # empty — add your connection JSON files
|
|
46
|
+
skills/ # empty — add your SKILL.md subdirectories
|
|
47
|
+
context_docs/ # empty — add your Markdown reference docs
|
|
48
|
+
specs/ # empty — add OpenAPI/WSDL files
|
|
49
|
+
hooks/hooks.json # empty hooks array
|
|
50
|
+
settings.schema.json # empty JSON Schema
|
|
51
|
+
assets/ # empty
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## `nutria-plugin validate` — Validate a plugin
|
|
57
|
+
|
|
58
|
+
Validate a plugin directory (does not pack).
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
nutria-plugin validate [<dir>]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Argument | Description |
|
|
65
|
+
|----------|-------------|
|
|
66
|
+
| `dir` | Plugin directory to validate. Defaults to `.` |
|
|
67
|
+
|
|
68
|
+
**Exit codes:** `0` = valid, `1` = one or more errors found.
|
|
69
|
+
|
|
70
|
+
**Output:**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
$ nutria-plugin validate my-plugin/
|
|
74
|
+
OK
|
|
75
|
+
|
|
76
|
+
$ nutria-plugin validate broken-plugin/
|
|
77
|
+
Validation errors:
|
|
78
|
+
- invalid plugin.json: version must use semantic versioning
|
|
79
|
+
- invalid plugin.json: remote_endpoints[0] targets a private/internal address
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Validation checks:
|
|
83
|
+
- All `plugin.json` fields (see [manifest.md](manifest.md))
|
|
84
|
+
- Required files present (`plugin.json`, at least one connection or skill)
|
|
85
|
+
- No hidden files in the directory
|
|
86
|
+
- No symlinks
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## `nutria-plugin pack` — Validate and pack a ZIP
|
|
91
|
+
|
|
92
|
+
Validate the plugin and produce a distributable ZIP archive.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
nutria-plugin pack [<dir>] [--output <path>] [--key <pem-file>]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Argument | Description |
|
|
99
|
+
|----------|-------------|
|
|
100
|
+
| `dir` | Plugin directory to pack. Defaults to `.` |
|
|
101
|
+
| `--output`, `-o` | Output ZIP path. Defaults to `<id>-<version>.zip` in the current directory |
|
|
102
|
+
| `--key` | Path to a PEM private key file. If provided, the manifest is signed before packing |
|
|
103
|
+
|
|
104
|
+
**Examples:**
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Pack to default name (my-plugin-0.1.0.zip)
|
|
108
|
+
nutria-plugin pack my-plugin/
|
|
109
|
+
|
|
110
|
+
# Custom output path
|
|
111
|
+
nutria-plugin pack my-plugin/ --output dist/my-plugin-0.1.0.zip
|
|
112
|
+
|
|
113
|
+
# Sign and pack
|
|
114
|
+
nutria-plugin pack my-plugin/ --key my-signing-key.pem --output dist/my-plugin-0.1.0.zip
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**What pack does:**
|
|
118
|
+
1. Runs full validation — aborts on any error
|
|
119
|
+
2. Collects all files, skipping hidden files and checking extensions
|
|
120
|
+
3. Rejects symlinks
|
|
121
|
+
4. Writes a ZIP with stored (non-compressed) entries and safe paths
|
|
122
|
+
5. Optionally signs the manifest before writing
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## `nutria-plugin sign` — Sign a manifest in-place
|
|
127
|
+
|
|
128
|
+
Sign an existing `plugin.json` file. Modifies the file by adding or updating
|
|
129
|
+
the `signature` field.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
nutria-plugin sign [<manifest>] --key <pem-file>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Argument | Description |
|
|
136
|
+
|----------|-------------|
|
|
137
|
+
| `manifest` | Path to `plugin.json`. Defaults to `plugin.json` in the current directory |
|
|
138
|
+
| `--key` | Path to the PEM private key file (required) |
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
nutria-plugin sign plugin.json --key my-signing-key.pem
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Signing does not pack — run `nutria-plugin pack` after signing to produce
|
|
147
|
+
the distributable ZIP.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## `nutria-plugin keygen` — Generate a signing key pair
|
|
152
|
+
|
|
153
|
+
Generate an ECDSA P-256 key pair for plugin signing.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
nutria-plugin keygen [--out <stem>]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Argument | Description |
|
|
160
|
+
|----------|-------------|
|
|
161
|
+
| `--out` | Output file stem. Defaults to `nutria-plugin`. Output path must be within the current directory |
|
|
162
|
+
|
|
163
|
+
**Output files:**
|
|
164
|
+
|
|
165
|
+
| File | Contents |
|
|
166
|
+
|------|----------|
|
|
167
|
+
| `<stem>.pem` | PEM-encoded private key — keep secret, never commit |
|
|
168
|
+
| `<stem>.pub.pem` | PEM-encoded public key — distribute to Nutria instances |
|
|
169
|
+
|
|
170
|
+
**Example:**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
nutria-plugin keygen --out acme-publisher
|
|
174
|
+
# Private key: acme-publisher.pem
|
|
175
|
+
# Public key: acme-publisher.pub.pem
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Security note:** The output path is resolved and must be within the current
|
|
179
|
+
working directory. Path traversal in `--out` is rejected.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Exit codes
|
|
184
|
+
|
|
185
|
+
| Code | Meaning |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| `0` | Success |
|
|
188
|
+
| `1` | Validation error, signing error, or unexpected failure |
|
|
189
|
+
| `2` | CLI usage error (invalid arguments) |
|