@palettelab/cli 0.3.34 → 0.3.35
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.
- package/README.md +2 -1
- package/backend-sdk/palette_sdk/__init__.py +2 -0
- package/backend-sdk/palette_sdk/members.py +45 -0
- package/backend-sdk/palette_sdk/permissions.py +1 -0
- package/backend-sdk/palette_sdk/plugin_context.py +7 -0
- package/docs/python-backend-sdk.md +18 -1
- package/lib/manifest.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -275,10 +275,11 @@ async def create_invoice(body: InvoiceIn, ctx: PluginContext = Depends(get_plugi
|
|
|
275
275
|
|
|
276
276
|
Backend SDK features for app-owned data:
|
|
277
277
|
|
|
278
|
-
- `PluginContext` exposes `user_id`, `organization_id`, `plugin_id`, `permissions`, `config`, `storage`, `ctx.db`, `ctx.redis`, and `ctx.vector`.
|
|
278
|
+
- `PluginContext` exposes `user_id`, `organization_id`, `plugin_id`, `permissions`, `config`, `storage`, `ctx.db`, `ctx.members`, `ctx.redis`, and `ctx.vector`.
|
|
279
279
|
- `ctx.db` is the full scoped SQLAlchemy `AsyncSession` for app-owned database data.
|
|
280
280
|
- `ctx.repo(Model)` gives org-safe CRUD helpers for app tables.
|
|
281
281
|
- `ctx.data_rooms` gives backend access to Palette Data Rooms without importing platform internals.
|
|
282
|
+
- `ctx.members` gives backend access to current organisation members; it exposes list/get/invite/update-role helpers, but no delete/remove helper.
|
|
282
283
|
- `ctx.has_permission("...")`, `ctx.has_any_permission([...])`, and `ctx.has_all_permissions([...])` check declared permissions.
|
|
283
284
|
- `ctx.config_value("key")` and `ctx.require_config("key")` read app install/config values.
|
|
284
285
|
- `ctx.secret("KEY")` reads app secrets from config or environment variables.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from palette_sdk.plugin_router import PluginRouter
|
|
4
4
|
from palette_sdk.plugin_context import MissingSecretError, PluginContext, get_plugin_context
|
|
5
5
|
from palette_sdk.data_rooms import DataRoomsClient
|
|
6
|
+
from palette_sdk.members import OrganizationMembersClient
|
|
6
7
|
from palette_sdk.platform_services import (
|
|
7
8
|
LocalRedisService,
|
|
8
9
|
LocalVectorService,
|
|
@@ -42,6 +43,7 @@ __all__ = [
|
|
|
42
43
|
"MissingSecretError",
|
|
43
44
|
"get_plugin_context",
|
|
44
45
|
"DataRoomsClient",
|
|
46
|
+
"OrganizationMembersClient",
|
|
45
47
|
"LocalRedisService",
|
|
46
48
|
"LocalVectorService",
|
|
47
49
|
"PlatformServiceUnavailable",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Backend organization member helpers for plugin Python code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OrganizationMembersClient:
|
|
9
|
+
"""Thin wrapper around the platform-injected organization member service."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, service: Any = None, permissions: list[str] | None = None):
|
|
12
|
+
self._service = service
|
|
13
|
+
self._permissions = permissions or []
|
|
14
|
+
|
|
15
|
+
def _require_service(self) -> Any:
|
|
16
|
+
if self._service is None:
|
|
17
|
+
raise RuntimeError(
|
|
18
|
+
"Organization member service is not available in this runtime. "
|
|
19
|
+
"Run inside Palette OS/hosted sandbox or inject a fake service in tests."
|
|
20
|
+
)
|
|
21
|
+
return self._service
|
|
22
|
+
|
|
23
|
+
def _require_permission(self, permission: str) -> None:
|
|
24
|
+
if permission not in self._permissions:
|
|
25
|
+
raise PermissionError(f"App does not declare required permission: {permission}")
|
|
26
|
+
|
|
27
|
+
async def list(self) -> list[dict[str, Any]]:
|
|
28
|
+
self._require_permission("members:read")
|
|
29
|
+
return await self._require_service().list_members()
|
|
30
|
+
|
|
31
|
+
async def get(self, user_id: str) -> dict[str, Any] | None:
|
|
32
|
+
self._require_permission("members:read")
|
|
33
|
+
return await self._require_service().get_member(user_id)
|
|
34
|
+
|
|
35
|
+
async def get_by_email(self, email: str) -> dict[str, Any] | None:
|
|
36
|
+
self._require_permission("members:read")
|
|
37
|
+
return await self._require_service().get_member_by_email(email)
|
|
38
|
+
|
|
39
|
+
async def invite(self, email: str, role: str = "member") -> dict[str, Any]:
|
|
40
|
+
self._require_permission("members:write")
|
|
41
|
+
return await self._require_service().invite_member(email, role)
|
|
42
|
+
|
|
43
|
+
async def update_role(self, user_id: str, role: str) -> dict[str, Any]:
|
|
44
|
+
self._require_permission("members:write")
|
|
45
|
+
return await self._require_service().update_member_role(user_id, role)
|
|
@@ -11,6 +11,7 @@ from fastapi import Depends, Request
|
|
|
11
11
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
12
|
|
|
13
13
|
from palette_sdk.data_rooms import DataRoomsClient
|
|
14
|
+
from palette_sdk.members import OrganizationMembersClient
|
|
14
15
|
from palette_sdk.platform_services import UnavailablePlatformService
|
|
15
16
|
|
|
16
17
|
|
|
@@ -35,6 +36,7 @@ class PluginContext:
|
|
|
35
36
|
plugin_id: The plugin's ID from its manifest
|
|
36
37
|
permissions: List of permissions declared in the manifest
|
|
37
38
|
storage: Storage service for file upload/download
|
|
39
|
+
members: Organization member helpers for the current org
|
|
38
40
|
"""
|
|
39
41
|
db: AsyncSession
|
|
40
42
|
user_id: str
|
|
@@ -44,6 +46,7 @@ class PluginContext:
|
|
|
44
46
|
permissions: list[str] = field(default_factory=list)
|
|
45
47
|
storage: Any = None # Platform storage service injected at runtime
|
|
46
48
|
data_rooms: DataRoomsClient = field(default_factory=DataRoomsClient)
|
|
49
|
+
members: OrganizationMembersClient = field(default_factory=OrganizationMembersClient)
|
|
47
50
|
redis: Any = field(default_factory=lambda: UnavailablePlatformService("redis"))
|
|
48
51
|
vector: Any = field(default_factory=lambda: UnavailablePlatformService("vector"))
|
|
49
52
|
config: dict[str, Any] = field(default_factory=dict)
|
|
@@ -110,6 +113,10 @@ async def get_plugin_context(request: Request) -> PluginContext:
|
|
|
110
113
|
permissions=getattr(state, "plugin_permissions", []),
|
|
111
114
|
storage=getattr(state, "storage", None),
|
|
112
115
|
data_rooms=DataRoomsClient(getattr(state, "data_rooms", None)),
|
|
116
|
+
members=OrganizationMembersClient(
|
|
117
|
+
getattr(state, "org_members", None),
|
|
118
|
+
getattr(state, "plugin_permissions", []),
|
|
119
|
+
),
|
|
113
120
|
redis=getattr(state, "redis", None) or UnavailablePlatformService("redis"),
|
|
114
121
|
vector=getattr(state, "vector", None) or UnavailablePlatformService("vector"),
|
|
115
122
|
config=getattr(state, "plugin_config", {}),
|
|
@@ -112,6 +112,7 @@ Available context values:
|
|
|
112
112
|
| `ctx.permissions` | Permissions granted from `palette-plugin.json` |
|
|
113
113
|
| `ctx.storage` | Runtime storage service, when available |
|
|
114
114
|
| `ctx.data_rooms` | Backend Data Room client |
|
|
115
|
+
| `ctx.members` | Current organisation member client |
|
|
115
116
|
| `ctx.redis` | Plugin/org-scoped Redis-style service when `platform_services` includes `redis` |
|
|
116
117
|
| `ctx.vector` | Plugin/org-scoped vector service when `platform_services` includes `vector` |
|
|
117
118
|
| `ctx.config` | App install/config values |
|
|
@@ -167,10 +168,26 @@ data_rooms:read, data_rooms:write
|
|
|
167
168
|
agents:read, agents:write
|
|
168
169
|
chat:read, chat:write
|
|
169
170
|
routines:read, routines:write
|
|
170
|
-
members:read
|
|
171
|
+
members:read, members:write
|
|
171
172
|
resources:read, resources:write
|
|
172
173
|
```
|
|
173
174
|
|
|
175
|
+
Organisation member helpers:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
@router.get("/members", dependencies=[require_permission("members:read")])
|
|
179
|
+
async def members(ctx: PluginContext = Depends(get_plugin_context)):
|
|
180
|
+
return await ctx.members.list()
|
|
181
|
+
|
|
182
|
+
@router.post("/members/invite", dependencies=[require_permission("members:write")])
|
|
183
|
+
async def invite_member(email: str, ctx: PluginContext = Depends(get_plugin_context)):
|
|
184
|
+
return await ctx.members.invite(email, role="member")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`ctx.members` exposes `list()`, `get(user_id)`, `get_by_email(email)`,
|
|
188
|
+
`invite(email, role="member")`, and `update_role(user_id, role)`. Member
|
|
189
|
+
deletion/removal is intentionally not exposed through the app SDK.
|
|
190
|
+
|
|
174
191
|
## 6. App-Owned Data
|
|
175
192
|
|
|
176
193
|
Apps can ship their own tables and migrations. Use `OrgScopedTable` for data
|
package/lib/manifest.js
CHANGED