@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 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)
@@ -25,6 +25,7 @@ KNOWN_PERMISSIONS: frozenset[str] = frozenset(
25
25
  "routines:read",
26
26
  "routines:write",
27
27
  "members:read",
28
+ "members:write",
28
29
  "resources:read",
29
30
  "resources:write",
30
31
  }
@@ -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
@@ -18,6 +18,7 @@ const KNOWN_PERMISSIONS = new Set([
18
18
  "routines:read",
19
19
  "routines:write",
20
20
  "members:read",
21
+ "members:write",
21
22
  "resources:read",
22
23
  "resources:write",
23
24
  ])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palettelab/cli",
3
- "version": "0.3.34",
3
+ "version": "0.3.35",
4
4
  "description": "Developer CLI for building Palette platform plugins — no platform source access required.",
5
5
  "bin": {
6
6
  "pltt": "bin/pltt.js"