@palettelab/cli 0.3.35 → 0.3.37
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 +33 -4
- package/backend-sdk/palette_sdk/__init__.py +3 -1
- package/backend-sdk/palette_sdk/plugin_context.py +1 -1
- package/backend-sdk/palette_sdk/storage.py +105 -0
- package/backend-sdk/pyproject.toml +1 -1
- package/docs/python-backend-sdk.md +69 -8
- package/lib/dev-simulator.js +12 -1
- package/package.json +1 -1
- package/template-fallback/package.json +1 -1
- package/template-fallback/palette-plugin.json +1 -1
- package/template-fallback/templates/dashboard/package.json +1 -1
- package/template-fallback/templates/dashboard/palette-plugin.json +1 -1
- package/template-fallback/templates/database/package.json +1 -1
- package/template-fallback/templates/database/palette-plugin.json +1 -1
- package/template-fallback/templates/external-service/package.json +1 -1
- package/template-fallback/templates/external-service/palette-plugin.json +1 -1
- package/template-fallback/templates/frontend-only/package.json +1 -1
- package/template-fallback/templates/frontend-only/palette-plugin.json +1 -1
- package/template-fallback/templates/next/package.json +1 -1
- package/template-fallback/templates/next/palette-plugin.json +1 -1
package/README.md
CHANGED
|
@@ -275,7 +275,8 @@ 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
|
-
- `
|
|
278
|
+
- `PluginRouter`, `PluginContext`, and `get_plugin_context` provide the FastAPI route and request-context surface.
|
|
279
|
+
- `PluginContext` exposes `user_id`, `organization_id`, `org_role`, `plugin_id`, `permissions`, `storage`, `ctx.db`, `ctx.data_rooms`, `ctx.members`, `ctx.redis`, `ctx.vector`, `ctx.config`, and `ctx.logger`.
|
|
279
280
|
- `ctx.db` is the full scoped SQLAlchemy `AsyncSession` for app-owned database data.
|
|
280
281
|
- `ctx.repo(Model)` gives org-safe CRUD helpers for app tables.
|
|
281
282
|
- `ctx.data_rooms` gives backend access to Palette Data Rooms without importing platform internals.
|
|
@@ -283,10 +284,20 @@ Backend SDK features for app-owned data:
|
|
|
283
284
|
- `ctx.has_permission("...")`, `ctx.has_any_permission([...])`, and `ctx.has_all_permissions([...])` check declared permissions.
|
|
284
285
|
- `ctx.config_value("key")` and `ctx.require_config("key")` read app install/config values.
|
|
285
286
|
- `ctx.secret("KEY")` reads app secrets from config or environment variables.
|
|
287
|
+
- `get_config(ctx, key)` and `require_config(ctx, key)` are functional config helper forms.
|
|
288
|
+
- `require_permission(permission)`, `KNOWN_PERMISSIONS`, and `is_known_permission(permission)` support route and manifest permission checks.
|
|
286
289
|
- `ctx.redis` gives a Redis-backed, plugin/org-scoped Redis API when `"redis"` is declared in `platform_services`.
|
|
287
290
|
- `ctx.vector` gives a Qdrant-backed, plugin/org-scoped vector API when `"vector"` is declared in `platform_services`.
|
|
291
|
+
- `ctx.storage` gives app/org-scoped file upload helpers when `"storage"` is declared in `platform_services`.
|
|
288
292
|
- `LifecycleHooks` lets apps define install/update/enable/disable/uninstall hooks.
|
|
289
293
|
- `OrgScopedTable` and `PluginBase` keep app data inside the plugin schema model set.
|
|
294
|
+
- `plugin_safe_id(...)`, `plugin_schema(...)`, `plugin_table_prefix(...)`, and `ensure_org_rls(...)` keep database names and row-level security consistent.
|
|
295
|
+
- `Event` and `subscribe_event(...)` register in-process platform event handlers.
|
|
296
|
+
- `sign_webhook(...)` and `verify_webhook_signature(...)` handle HMAC-SHA256 webhook signing checks.
|
|
297
|
+
- `ToolDefinition` is the base class for custom agent tools.
|
|
298
|
+
- `PluginManifest` and `load_manifest(...)` parse and validate `palette-plugin.json`.
|
|
299
|
+
- `SuccessResponse`, `ErrorResponse`, and `PaginatedResponse` are reusable response schemas.
|
|
300
|
+
- `route_permission_issues(router, public_routes=None)` is the test helper for detecting ungated backend routes.
|
|
290
301
|
|
|
291
302
|
Python backend Data Room example:
|
|
292
303
|
|
|
@@ -316,6 +327,24 @@ async def sync_invoices(ctx: PluginContext = Depends(get_plugin_context)):
|
|
|
316
327
|
return {"room": room, "folder": folder, "bytes": len(content or b"")}
|
|
317
328
|
```
|
|
318
329
|
|
|
330
|
+
Python backend app-storage example:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
@router.post("/reports", dependencies=[require_permission("reports:write")])
|
|
334
|
+
async def save_report(ctx: PluginContext = Depends(get_plugin_context)):
|
|
335
|
+
saved = await ctx.storage.upload_file(
|
|
336
|
+
"summary.json",
|
|
337
|
+
b'{"ok": true}',
|
|
338
|
+
"application/json",
|
|
339
|
+
key="reports/summary.json",
|
|
340
|
+
)
|
|
341
|
+
return saved
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Frontend apps can use `createPaletteClient(platform).storage.upload(file, {
|
|
345
|
+
onProgress })`. Palette writes every object under the app folder and current
|
|
346
|
+
organisation folder, then uses GCS resumable uploads in hosted environments.
|
|
347
|
+
|
|
319
348
|
The npm `@palettelab/sdk` package is for frontend JavaScript/React apps.
|
|
320
349
|
Python backend code uses `palette_sdk`, which is embedded in the CLI for
|
|
321
350
|
local dev/tests and injected by the hosted Palette runtime.
|
|
@@ -524,7 +553,7 @@ await ctx.redis.incr("counter")
|
|
|
524
553
|
await ctx.redis.decr("counter")
|
|
525
554
|
await ctx.redis.scan(prefix="cache:", limit=100)
|
|
526
555
|
|
|
527
|
-
# Redis hashes, lists, sets, sorted sets,
|
|
556
|
+
# Redis hashes, lists, sets, sorted sets, queues, locks
|
|
528
557
|
await ctx.redis.hset("hash", "field", {"value": 1})
|
|
529
558
|
await ctx.redis.hgetall("hash")
|
|
530
559
|
await ctx.redis.lpush("queue", {"job": 1})
|
|
@@ -533,8 +562,8 @@ await ctx.redis.sadd("tags", "red", "blue")
|
|
|
533
562
|
await ctx.redis.smembers("tags")
|
|
534
563
|
await ctx.redis.zadd("scores", {"alice": 10})
|
|
535
564
|
await ctx.redis.zrange("scores", 0, -1, with_scores=True)
|
|
536
|
-
await ctx.redis.
|
|
537
|
-
await ctx.redis.
|
|
565
|
+
await ctx.redis.enqueue("jobs", {"task": "sync"})
|
|
566
|
+
await ctx.redis.dequeue("jobs")
|
|
538
567
|
await ctx.redis.lock("invoice:1", token, ttl=30)
|
|
539
568
|
await ctx.redis.unlock("invoice:1", token)
|
|
540
569
|
|
|
@@ -36,6 +36,7 @@ from palette_sdk.events import Event, subscribe_event
|
|
|
36
36
|
from palette_sdk.config import get_config, require_config
|
|
37
37
|
from palette_sdk.webhooks import sign_webhook, verify_webhook_signature
|
|
38
38
|
from palette_sdk.testing import route_permission_issues
|
|
39
|
+
from palette_sdk.storage import LocalStorageService
|
|
39
40
|
|
|
40
41
|
__all__ = [
|
|
41
42
|
"PluginRouter",
|
|
@@ -72,6 +73,7 @@ __all__ = [
|
|
|
72
73
|
"sign_webhook",
|
|
73
74
|
"verify_webhook_signature",
|
|
74
75
|
"route_permission_issues",
|
|
76
|
+
"LocalStorageService",
|
|
75
77
|
]
|
|
76
78
|
|
|
77
|
-
__version__ = "0.1.
|
|
79
|
+
__version__ = "0.1.8"
|
|
@@ -111,7 +111,7 @@ async def get_plugin_context(request: Request) -> PluginContext:
|
|
|
111
111
|
org_role=getattr(state, "org_role", None),
|
|
112
112
|
plugin_id=getattr(state, "plugin_id", ""),
|
|
113
113
|
permissions=getattr(state, "plugin_permissions", []),
|
|
114
|
-
storage=getattr(state, "storage", None),
|
|
114
|
+
storage=getattr(state, "storage", None) or UnavailablePlatformService("storage"),
|
|
115
115
|
data_rooms=DataRoomsClient(getattr(state, "data_rooms", None)),
|
|
116
116
|
members=OrganizationMembersClient(
|
|
117
117
|
getattr(state, "org_members", None),
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import mimetypes
|
|
4
|
+
import re
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _slug_segment(value: str | None, fallback: str) -> str:
|
|
12
|
+
raw = (value or fallback).strip().lower()
|
|
13
|
+
slug = re.sub(r"[^a-z0-9]+", "_", raw).strip("_")
|
|
14
|
+
return slug or fallback
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _sanitize_filename(filename: str | None) -> str:
|
|
18
|
+
raw = (filename or "upload").strip().replace("\\", "/").rsplit("/", 1)[-1]
|
|
19
|
+
safe = re.sub(r"[^\w.\- ]+", "", raw)
|
|
20
|
+
safe = re.sub(r"\s+", "_", safe).strip("._")
|
|
21
|
+
return safe or "upload"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _validate_relative_key(key: str) -> str:
|
|
25
|
+
key = key.strip().replace("\\", "/")
|
|
26
|
+
if not key or key.startswith("/") or key.endswith("/"):
|
|
27
|
+
raise ValueError("key must be a non-empty relative file path")
|
|
28
|
+
parts = [part for part in key.split("/") if part]
|
|
29
|
+
if any(part in {".", ".."} for part in parts):
|
|
30
|
+
raise ValueError("key cannot contain relative path segments")
|
|
31
|
+
return "/".join(_sanitize_filename(part) for part in parts)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _content_type(filename: str | None, content_type: str | None) -> str:
|
|
35
|
+
browser_ct = content_type or "application/octet-stream"
|
|
36
|
+
if browser_ct == "application/octet-stream":
|
|
37
|
+
guessed, _ = mimetypes.guess_type(filename or "")
|
|
38
|
+
return guessed or browser_ct
|
|
39
|
+
return browser_ct
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class LocalStorageService:
|
|
44
|
+
"""Filesystem-backed ctx.storage implementation used by local SDK dev."""
|
|
45
|
+
|
|
46
|
+
root: str | Path
|
|
47
|
+
plugin_id: str
|
|
48
|
+
app_name: str | None = None
|
|
49
|
+
organization_id: int = 1
|
|
50
|
+
organization_slug: str | None = "palette-dev"
|
|
51
|
+
organization_name: str | None = "Palette Dev"
|
|
52
|
+
|
|
53
|
+
def _prefix(self) -> str:
|
|
54
|
+
app_folder = f"{_slug_segment(self.app_name or self.plugin_id, 'app')}_{_slug_segment(self.plugin_id, 'plugin')}"
|
|
55
|
+
org_label = self.organization_slug or self.organization_name or f"org_{self.organization_id}"
|
|
56
|
+
org_folder = f"{_slug_segment(org_label, 'organisation')}_{self.organization_id}"
|
|
57
|
+
return f"uploads/apps/{app_folder}/{org_folder}"
|
|
58
|
+
|
|
59
|
+
def object_path(self, filename: str | None = None, *, key: str | None = None) -> str:
|
|
60
|
+
relative = _validate_relative_key(key) if key else f"{uuid.uuid4().hex}_{_sanitize_filename(filename)}"
|
|
61
|
+
return f"{self._prefix()}/{relative}"
|
|
62
|
+
|
|
63
|
+
def _target(self, object_path: str) -> Path:
|
|
64
|
+
root = Path(self.root).resolve()
|
|
65
|
+
target = (root / object_path).resolve()
|
|
66
|
+
target.relative_to(root)
|
|
67
|
+
return target
|
|
68
|
+
|
|
69
|
+
async def upload_file(
|
|
70
|
+
self,
|
|
71
|
+
filename: str,
|
|
72
|
+
content: bytes,
|
|
73
|
+
content_type: str | None = None,
|
|
74
|
+
*,
|
|
75
|
+
key: str | None = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
object_path = self.object_path(filename, key=key)
|
|
78
|
+
target = self._target(object_path)
|
|
79
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
target.write_bytes(content)
|
|
81
|
+
return {
|
|
82
|
+
"bucket": "local",
|
|
83
|
+
"object_path": object_path,
|
|
84
|
+
"file_url": target.as_uri(),
|
|
85
|
+
"content_type": _content_type(filename, content_type),
|
|
86
|
+
"size": len(content),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async def create_resumable_upload(
|
|
90
|
+
self,
|
|
91
|
+
filename: str,
|
|
92
|
+
content_type: str | None = None,
|
|
93
|
+
size: int | None = None,
|
|
94
|
+
*,
|
|
95
|
+
key: str | None = None,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
object_path = self.object_path(filename, key=key)
|
|
98
|
+
return {
|
|
99
|
+
"bucket": "local",
|
|
100
|
+
"object_path": object_path,
|
|
101
|
+
"file_url": self._target(object_path).as_uri(),
|
|
102
|
+
"upload_url": None,
|
|
103
|
+
"content_type": _content_type(filename, content_type),
|
|
104
|
+
"size": size,
|
|
105
|
+
}
|
|
@@ -125,6 +125,34 @@ Available context values:
|
|
|
125
125
|
| `ctx.require_config(key)` | Read required config or raise |
|
|
126
126
|
| `ctx.secret(key, default)` | Read a secret from app config or environment |
|
|
127
127
|
|
|
128
|
+
## 4a. Backend Helper API Index
|
|
129
|
+
|
|
130
|
+
These are the public Python helpers exported by `palette_sdk`.
|
|
131
|
+
|
|
132
|
+
| Helper | Use it for |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `PluginRouter` | FastAPI router mounted under `/api/v1/plugins/<plugin-id>` |
|
|
135
|
+
| `PluginContext`, `get_plugin_context` | Authenticated request context dependency |
|
|
136
|
+
| `MissingSecretError` | Handling missing required declared secrets from `ctx.secret(...)` |
|
|
137
|
+
| `require_permission(permission)` | Route-level permission gate required for protected routes |
|
|
138
|
+
| `KNOWN_PERMISSIONS`, `is_known_permission(...)` | Permission vocabulary checks for manifests/tools |
|
|
139
|
+
| `DataRoomsClient`, `ctx.data_rooms` | Backend Data Room room/folder/file helpers |
|
|
140
|
+
| `OrganizationMembersClient`, `ctx.members` | Current-organization member lookup, invite, and role helpers |
|
|
141
|
+
| `OrgRepository`, `ctx.repo(Model)` | Org-safe convenience CRUD for app-owned models |
|
|
142
|
+
| `PluginBase`, `OrgScopedTable` | SQLAlchemy declarative bases for plugin-owned tables |
|
|
143
|
+
| `ensure_org_rls(op, table)` | Alembic helper that enables org row-level security |
|
|
144
|
+
| `plugin_safe_id(...)`, `plugin_schema(...)`, `plugin_table_prefix(...)` | Manifest id to database-safe naming helpers |
|
|
145
|
+
| `get_config(ctx, key)`, `require_config(ctx, key)` | Functional form of config reads when not using `ctx.config_value(...)` |
|
|
146
|
+
| `LocalRedisService`, `LocalVectorService` | Local `pltt dev` service emulators and test fakes |
|
|
147
|
+
| `PlatformServiceUnavailable`, `UnavailablePlatformService` | Clear errors when an undeclared platform service is used |
|
|
148
|
+
| `LifecycleHooks` | Install/update/enable/disable/uninstall callbacks |
|
|
149
|
+
| `Event`, `subscribe_event(...)` | In-process platform event subscriptions |
|
|
150
|
+
| `sign_webhook(...)`, `verify_webhook_signature(...)` | HMAC-SHA256 webhook signing and verification |
|
|
151
|
+
| `ToolDefinition` | Base class for custom agent tools |
|
|
152
|
+
| `PluginManifest`, `load_manifest(...)` | Typed manifest parsing from `palette-plugin.json` |
|
|
153
|
+
| `SuccessResponse`, `ErrorResponse`, `PaginatedResponse` | Common response schemas for plugin APIs |
|
|
154
|
+
| `route_permission_issues(router, public_routes=None)` | Test helper that reports routes missing `require_permission(...)` |
|
|
155
|
+
|
|
128
156
|
## 5. Permissions
|
|
129
157
|
|
|
130
158
|
Use route-level permission guards for normal APIs:
|
|
@@ -603,11 +631,11 @@ install config, plugin-scope encrypted secrets, or local `.palette/.env.local`
|
|
|
603
631
|
during `pltt dev`. Undeclared keys still fall back to the process environment
|
|
604
632
|
for local compatibility.
|
|
605
633
|
|
|
606
|
-
Managed Redis and
|
|
634
|
+
Managed Redis, vector, and storage services are declared in the manifest:
|
|
607
635
|
|
|
608
636
|
```json
|
|
609
637
|
{
|
|
610
|
-
"platform_services": ["redis", "vector"]
|
|
638
|
+
"platform_services": ["redis", "vector", "storage"]
|
|
611
639
|
}
|
|
612
640
|
```
|
|
613
641
|
|
|
@@ -626,6 +654,43 @@ Palette scopes every Redis key and vector operation by `plugin_id` and
|
|
|
626
654
|
`organization_id`; hosted previews also include the publish id. Plugin code
|
|
627
655
|
cannot read, list, update, or delete records owned by another app or org.
|
|
628
656
|
|
|
657
|
+
Palette scopes storage the same way. Files written through `ctx.storage` or the
|
|
658
|
+
frontend storage client live under:
|
|
659
|
+
|
|
660
|
+
```text
|
|
661
|
+
uploads/apps/{app_name}_{plugin_id}/{organisation_slug}_{organisation_id}/{file}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Backend storage helpers:
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
saved = await ctx.storage.upload_file(
|
|
668
|
+
"summary.json",
|
|
669
|
+
b'{"ok": true}',
|
|
670
|
+
"application/json",
|
|
671
|
+
key="reports/summary.json",
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
session = await ctx.storage.create_resumable_upload(
|
|
675
|
+
"video.mp4",
|
|
676
|
+
"video/mp4",
|
|
677
|
+
size=video_size,
|
|
678
|
+
key="videos/video.mp4",
|
|
679
|
+
)
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Frontend apps can use the browser SDK for chunked/resumable upload progress:
|
|
683
|
+
|
|
684
|
+
```tsx
|
|
685
|
+
const palette = createPaletteClient(platform)
|
|
686
|
+
|
|
687
|
+
await palette.storage.upload(file, {
|
|
688
|
+
key: `videos/${file.name}`,
|
|
689
|
+
chunkSize: 8 * 1024 * 1024,
|
|
690
|
+
onProgress: (p) => setPercent(p.percentage),
|
|
691
|
+
})
|
|
692
|
+
```
|
|
693
|
+
|
|
629
694
|
Advanced provider features are still available through scoped helpers:
|
|
630
695
|
`ctx.redis.execute(...)` forwards Redis data-plane commands after key rewriting,
|
|
631
696
|
while blocking server/admin commands. `ctx.vector.client()` returns the Qdrant
|
|
@@ -666,8 +731,6 @@ await ctx.redis.zrem("scores", "bob")
|
|
|
666
731
|
|
|
667
732
|
await ctx.redis.enqueue("jobs", {"task": "sync"})
|
|
668
733
|
await ctx.redis.dequeue("jobs")
|
|
669
|
-
await ctx.redis.xadd("events", {"type": "created"})
|
|
670
|
-
await ctx.redis.xread({"events": "0-0"}, count=10)
|
|
671
734
|
await ctx.redis.lock("invoice:1", token, ttl=30)
|
|
672
735
|
await ctx.redis.unlock("invoice:1", token)
|
|
673
736
|
```
|
|
@@ -747,12 +810,10 @@ Frontend code should call backend routes through the platform API helper, not by
|
|
|
747
810
|
hardcoding backend origins.
|
|
748
811
|
|
|
749
812
|
```tsx
|
|
750
|
-
import {
|
|
751
|
-
|
|
752
|
-
const palette = createPaletteClient()
|
|
813
|
+
import { apiFetch } from "@palettelab/sdk"
|
|
753
814
|
|
|
754
815
|
async function loadInvoices() {
|
|
755
|
-
const res = await
|
|
816
|
+
const res = await apiFetch("/api/v1/plugins/finance-tools/invoices")
|
|
756
817
|
return res.json()
|
|
757
818
|
}
|
|
758
819
|
```
|
package/lib/dev-simulator.js
CHANGED
|
@@ -134,12 +134,23 @@ sys.path.insert(0, str(ENTRY.parent))
|
|
|
134
134
|
|
|
135
135
|
DEV_REDIS = None
|
|
136
136
|
DEV_VECTOR = None
|
|
137
|
+
DEV_STORAGE = None
|
|
137
138
|
if _service_enabled("redis"):
|
|
138
139
|
from palette_sdk.platform_services import LocalRedisService
|
|
139
140
|
DEV_REDIS = LocalRedisService()
|
|
140
141
|
if _service_enabled("vector"):
|
|
141
142
|
from palette_sdk.platform_services import LocalVectorService
|
|
142
143
|
DEV_VECTOR = LocalVectorService()
|
|
144
|
+
if _service_enabled("storage"):
|
|
145
|
+
from palette_sdk.storage import LocalStorageService
|
|
146
|
+
DEV_STORAGE = LocalStorageService(
|
|
147
|
+
ROOT / ".palette" / "dev-storage",
|
|
148
|
+
MANIFEST.get("id", ""),
|
|
149
|
+
app_name=MANIFEST.get("name"),
|
|
150
|
+
organization_id=1,
|
|
151
|
+
organization_slug="palette-dev",
|
|
152
|
+
organization_name="Palette Dev",
|
|
153
|
+
)
|
|
143
154
|
|
|
144
155
|
spec = importlib.util.spec_from_file_location("palette_local_backend", ENTRY)
|
|
145
156
|
module = importlib.util.module_from_spec(spec)
|
|
@@ -178,7 +189,7 @@ class DevPluginContextMiddleware(BaseHTTPMiddleware):
|
|
|
178
189
|
"secret_specs": MANIFEST.get("secrets") or {},
|
|
179
190
|
"secret_scope": "dev",
|
|
180
191
|
}
|
|
181
|
-
request.state.storage =
|
|
192
|
+
request.state.storage = DEV_STORAGE
|
|
182
193
|
if DEV_REDIS is not None:
|
|
183
194
|
request.state.redis = DEV_REDIS
|
|
184
195
|
if DEV_VECTOR is not None:
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"description": "A widget that exposes a dashboard data source and renders a chart from it.",
|
|
10
10
|
"icon": "ChartBar",
|
|
11
11
|
"gradient": { "bg": "linear-gradient(135deg, #06B6D4, #6366F1)", "text": "#fff" },
|
|
12
|
-
"sdk": { "frontend": "^0.1.
|
|
12
|
+
"sdk": { "frontend": "^0.1.15", "backend": "^0.1.8" },
|
|
13
13
|
"platform": { "min_version": "0.1.0" },
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"frontend": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"description": "Stores notes per organization with RLS-enforced isolation.",
|
|
10
10
|
"icon": "Database",
|
|
11
11
|
"gradient": { "bg": "linear-gradient(135deg, #8B5CF6, #EC4899)", "text": "#fff" },
|
|
12
|
-
"sdk": { "frontend": "^0.1.
|
|
12
|
+
"sdk": { "frontend": "^0.1.15", "backend": "^0.1.8" },
|
|
13
13
|
"platform": { "min_version": "0.1.0" },
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"frontend": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"description": "Demonstrates declared external_network access and a scoped per-org config token.",
|
|
10
10
|
"icon": "CloudArrowUp",
|
|
11
11
|
"gradient": { "bg": "linear-gradient(135deg, #10B981, #06B6D4)", "text": "#fff" },
|
|
12
|
-
"sdk": { "frontend": "^0.1.
|
|
12
|
+
"sdk": { "frontend": "^0.1.15", "backend": "^0.1.8" },
|
|
13
13
|
"platform": { "min_version": "0.1.0" },
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"frontend": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"description": "A frontend-only plugin — renders inside the platform iframe sandbox with no backend.",
|
|
10
10
|
"icon": "Puzzle",
|
|
11
11
|
"gradient": { "bg": "linear-gradient(135deg, #6366F1, #8B5CF6)", "text": "#fff" },
|
|
12
|
-
"sdk": { "frontend": "^0.1.
|
|
12
|
+
"sdk": { "frontend": "^0.1.15" },
|
|
13
13
|
"platform": { "min_version": "0.1.0" },
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"frontend": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"description": "Uses frontend.framework=next so pltt reads frontend/next.config.ts while still publishing a native Palette module.",
|
|
10
10
|
"icon": "Puzzle",
|
|
11
11
|
"gradient": { "bg": "linear-gradient(135deg, #0F766E, #2563EB)", "text": "#fff" },
|
|
12
|
-
"sdk": { "frontend": "^0.1.
|
|
12
|
+
"sdk": { "frontend": "^0.1.15" },
|
|
13
13
|
"platform": { "min_version": "0.1.0" },
|
|
14
14
|
"capabilities": {
|
|
15
15
|
"frontend": true,
|