@palettelab/cli 0.3.25 → 0.3.27

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.
Files changed (30) hide show
  1. package/README.md +21 -3
  2. package/backend-sdk/palette_sdk/__init__.py +1 -1
  3. package/backend-sdk/palette_sdk/data_rooms.py +17 -0
  4. package/backend-sdk/palette_sdk/db/alembic_env.py +4 -2
  5. package/backend-sdk/palette_sdk/plugin_context.py +6 -0
  6. package/backend-sdk/pyproject.toml +1 -1
  7. package/docs/python-backend-sdk.md +641 -0
  8. package/lib/commands/build.js +4 -0
  9. package/lib/dev-simulator.js +30 -1
  10. package/package.json +2 -1
  11. package/template-fallback/frontend/src/index.tsx +35 -23
  12. package/template-fallback/frontend/src/translations.ts +36 -0
  13. package/template-fallback/package.json +1 -1
  14. package/template-fallback/palette-plugin.json +1 -1
  15. package/template-fallback/templates/dashboard/frontend/src/index.tsx +5 -3
  16. package/template-fallback/templates/dashboard/frontend/src/translations.ts +12 -0
  17. package/template-fallback/templates/dashboard/package.json +1 -1
  18. package/template-fallback/templates/dashboard/palette-plugin.json +1 -1
  19. package/template-fallback/templates/database/frontend/src/index.tsx +13 -12
  20. package/template-fallback/templates/database/frontend/src/translations.ts +30 -0
  21. package/template-fallback/templates/database/package.json +1 -1
  22. package/template-fallback/templates/database/palette-plugin.json +1 -1
  23. package/template-fallback/templates/external-service/frontend/src/index.tsx +5 -3
  24. package/template-fallback/templates/external-service/frontend/src/translations.ts +12 -0
  25. package/template-fallback/templates/external-service/package.json +1 -1
  26. package/template-fallback/templates/external-service/palette-plugin.json +1 -1
  27. package/template-fallback/templates/frontend-only/frontend/src/index.tsx +6 -3
  28. package/template-fallback/templates/frontend-only/frontend/src/translations.ts +12 -0
  29. package/template-fallback/templates/frontend-only/package.json +1 -1
  30. package/template-fallback/templates/frontend-only/palette-plugin.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palettelab/cli",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "Developer CLI for building Palette platform plugins — no platform source access required.",
5
5
  "bin": {
6
6
  "pltt": "bin/pltt.js"
@@ -8,6 +8,7 @@
8
8
  "files": [
9
9
  "bin",
10
10
  "backend-sdk",
11
+ "docs",
11
12
  "lib",
12
13
  "platform-dev",
13
14
  "template-fallback",
@@ -5,46 +5,58 @@
5
5
  *
6
6
  * This is the root component that the platform renders when your plugin is opened.
7
7
  * It receives a `platform` prop with access to the authenticated user, API client,
8
- * navigation, and toast notifications.
8
+ * navigation, toast notifications, and Palette OS language.
9
9
  *
10
10
  * Available hooks from @palettelab/sdk:
11
- * - usePlatform() access user, org, apiFetch, navigate, showToast
12
- * - usePluginTasks() CRUD tasks
13
- * - usePluginDataRooms() browse data rooms and files
14
- * - usePluginChat() chat with agents
11
+ * - usePlatform() - access user, org, API, language, navigate, showToast
12
+ * - usePluginTranslations() - translate app-owned resource files using OS language
13
+ * - usePluginTasks() - CRUD tasks
14
+ * - usePluginDataRooms() - browse data rooms and files
15
+ * - usePluginChat() - chat with agents
15
16
  */
16
17
 
17
- import { usePlatform, usePluginTasks } from "@palettelab/sdk"
18
+ import { usePlatform, usePluginTasks, usePluginTranslations } from "@palettelab/sdk"
18
19
  import type { PluginComponentProps } from "@palettelab/sdk"
20
+ import { translations } from "./translations"
19
21
 
20
- export default function MyPlugin({ platform }: PluginComponentProps) {
22
+ export default function MyPlugin(_props: PluginComponentProps) {
21
23
  const { user, showToast } = usePlatform()
22
24
  const { tasks, loading, createTask } = usePluginTasks()
25
+ const { t, language, setLanguage } = usePluginTranslations(translations)
23
26
 
24
27
  return (
25
28
  <div className="p-6 space-y-6">
26
- <div>
27
- <h1 className="text-2xl font-bold">My Plugin</h1>
28
- <p className="text-muted-foreground mt-1">
29
- Hello, {user.name}! This is your plugin template.
30
- </p>
29
+ <div className="flex flex-wrap items-start justify-between gap-3">
30
+ <div>
31
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
32
+ <p className="text-muted-foreground mt-1">
33
+ {t("greeting", { name: user.name })}
34
+ </p>
35
+ </div>
36
+ <button
37
+ className="px-3 py-1.5 text-sm border rounded-md hover:bg-muted"
38
+ onClick={() => setLanguage(language === "ko" ? "en" : "ko")}
39
+ aria-label={t("language")}
40
+ >
41
+ {language === "ko" ? "EN" : "KO"}
42
+ </button>
31
43
  </div>
32
44
 
33
45
  <div className="rounded-lg border p-4 space-y-3">
34
- <h2 className="font-semibold">Quick Start</h2>
46
+ <h2 className="font-semibold">{t("quickStart")}</h2>
35
47
  <ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
36
- <li>Edit <code>frontend/src/index.tsx</code> to build your UI</li>
37
- <li>Edit <code>backend/api/main.py</code> to add API endpoints</li>
38
- <li>Use <code>usePlatform()</code> to access the authenticated user and API</li>
39
- <li>Use <code>usePluginTasks()</code> to manage tasks</li>
40
- <li>Use <code>usePluginDataRooms()</code> to browse files</li>
48
+ <li>{t("editUi")}</li>
49
+ <li>{t("editApi")}</li>
50
+ <li>{t("usePlatform")}</li>
51
+ <li>{t("useTasks")}</li>
52
+ <li>{t("useDataRooms")}</li>
41
53
  </ul>
42
54
  </div>
43
55
 
44
56
  <div className="rounded-lg border p-4">
45
- <h2 className="font-semibold mb-2">Tasks ({loading ? "..." : tasks.length})</h2>
57
+ <h2 className="font-semibold mb-2">{t("tasks")} ({loading ? "..." : tasks.length})</h2>
46
58
  {!loading && tasks.length === 0 && (
47
- <p className="text-sm text-muted-foreground">No tasks yet.</p>
59
+ <p className="text-sm text-muted-foreground">{t("noTasks")}</p>
48
60
  )}
49
61
  {tasks.slice(0, 5).map(task => (
50
62
  <div key={task.id} className="flex items-center justify-between py-1 text-sm">
@@ -55,11 +67,11 @@ export default function MyPlugin({ platform }: PluginComponentProps) {
55
67
  <button
56
68
  className="mt-3 px-3 py-1.5 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
57
69
  onClick={async () => {
58
- await createTask({ title: "Sample task from plugin", priority: "medium" })
59
- showToast("Task created!", "success")
70
+ await createTask({ title: t("sampleTaskTitle"), priority: "medium" })
71
+ showToast(t("taskCreated"), "success")
60
72
  }}
61
73
  >
62
- Create Sample Task
74
+ {t("createSampleTask")}
63
75
  </button>
64
76
  </div>
65
77
  </div>
@@ -0,0 +1,36 @@
1
+ import type { TranslationResources } from "@palettelab/sdk"
2
+
3
+ export const translations = {
4
+ en: {
5
+ title: "My Plugin",
6
+ greeting: "Hello, {{name}}! This plugin follows the Palette OS language.",
7
+ quickStart: "Quick Start",
8
+ editUi: "Edit frontend/src/index.tsx to build your UI",
9
+ editApi: "Edit backend/api/main.py to add API endpoints",
10
+ usePlatform: "Use usePlatform() to access the authenticated user and API",
11
+ useTasks: "Use usePluginTasks() to manage tasks",
12
+ useDataRooms: "Use usePluginDataRooms() to browse files",
13
+ tasks: "Tasks",
14
+ noTasks: "No tasks yet.",
15
+ createSampleTask: "Create Sample Task",
16
+ sampleTaskTitle: "Sample task from plugin",
17
+ taskCreated: "Task created!",
18
+ language: "Language",
19
+ },
20
+ ko: {
21
+ title: "내 플러그인",
22
+ greeting: "안녕하세요, {{name}}님! 이 플러그인은 Palette OS 언어를 따릅니다.",
23
+ quickStart: "빠른 시작",
24
+ editUi: "frontend/src/index.tsx를 수정해 UI를 만드세요",
25
+ editApi: "backend/api/main.py를 수정해 API 엔드포인트를 추가하세요",
26
+ usePlatform: "usePlatform()으로 인증 사용자와 API에 접근하세요",
27
+ useTasks: "usePluginTasks()로 작업을 관리하세요",
28
+ useDataRooms: "usePluginDataRooms()로 파일을 둘러보세요",
29
+ tasks: "작업",
30
+ noTasks: "아직 작업이 없습니다.",
31
+ createSampleTask: "샘플 작업 만들기",
32
+ sampleTaskTitle: "플러그인에서 만든 샘플 작업",
33
+ taskCreated: "작업이 생성되었습니다!",
34
+ language: "언어",
35
+ },
36
+ } satisfies TranslationResources
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "description": "A Palette platform plugin",
6
6
  "dependencies": {
7
- "@palettelab/sdk": "^0.1.10"
7
+ "@palettelab/sdk": "^0.1.11"
8
8
  },
9
9
  "devDependencies": {
10
10
  "typescript": "^5.0.0",
@@ -13,7 +13,7 @@
13
13
  "text": "#fff"
14
14
  },
15
15
  "sdk": {
16
- "frontend": "^0.1.0",
16
+ "frontend": "^0.1.11",
17
17
  "backend": "^0.1.0"
18
18
  },
19
19
  "platform": {
@@ -1,13 +1,15 @@
1
1
  "use client"
2
2
 
3
3
  import { useEffect, useState } from "react"
4
- import { usePlatform } from "@palettelab/sdk"
4
+ import { usePlatform, usePluginTranslations } from "@palettelab/sdk"
5
5
  import type { PluginComponentProps } from "@palettelab/sdk"
6
+ import { translations } from "./translations"
6
7
 
7
8
  type Series = { label: string; value: number }
8
9
 
9
10
  export default function DashboardWidget(_props: PluginComponentProps) {
10
11
  const { apiFetch } = usePlatform()
12
+ const { t } = usePluginTranslations(translations)
11
13
  const [data, setData] = useState<Series[]>([])
12
14
  const [loading, setLoading] = useState(true)
13
15
 
@@ -22,9 +24,9 @@ export default function DashboardWidget(_props: PluginComponentProps) {
22
24
 
23
25
  return (
24
26
  <div className="p-6 space-y-4">
25
- <h1 className="text-2xl font-bold">Dashboard Widget</h1>
27
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
26
28
  {loading ? (
27
- <p className="text-muted-foreground">Loading…</p>
29
+ <p className="text-muted-foreground">{t("loading")}</p>
28
30
  ) : (
29
31
  <div className="space-y-2">
30
32
  {data.map((d) => (
@@ -0,0 +1,12 @@
1
+ import type { TranslationResources } from "@palettelab/sdk"
2
+
3
+ export const translations = {
4
+ en: {
5
+ title: "Dashboard Widget",
6
+ loading: "Loading...",
7
+ },
8
+ ko: {
9
+ title: "대시보드 위젯",
10
+ loading: "불러오는 중...",
11
+ },
12
+ } satisfies TranslationResources
@@ -3,7 +3,7 @@
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "dependencies": {
6
- "@palettelab/sdk": "^0.1.10",
6
+ "@palettelab/sdk": "^0.1.11",
7
7
  "react": "^19.0.0"
8
8
  }
9
9
  }
@@ -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.0", "backend": "^0.1.0" },
12
+ "sdk": { "frontend": "^0.1.11", "backend": "^0.1.0" },
13
13
  "platform": { "min_version": "0.1.0" },
14
14
  "capabilities": {
15
15
  "frontend": true,
@@ -1,8 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  import { useEffect, useState } from "react"
4
- import { usePlatform } from "@palettelab/sdk"
4
+ import { usePlatform, usePluginTranslations } from "@palettelab/sdk"
5
5
  import type { PluginComponentProps } from "@palettelab/sdk"
6
+ import { translations } from "./translations"
6
7
 
7
8
  type Note = { id: number; body: string }
8
9
 
@@ -96,6 +97,7 @@ const styles = `
96
97
 
97
98
  export default function NotesApp(_props: PluginComponentProps) {
98
99
  const { apiFetch, showToast, user } = usePlatform()
100
+ const { t } = usePluginTranslations(translations)
99
101
  const [notes, setNotes] = useState<Note[]>([])
100
102
  const [body, setBody] = useState("")
101
103
  const [loading, setLoading] = useState(true)
@@ -105,7 +107,7 @@ export default function NotesApp(_props: PluginComponentProps) {
105
107
  setLoading(true)
106
108
  try {
107
109
  const response = await apiFetch("/api/v1/plugins/my-db-plugin/notes")
108
- if (!response.ok) throw new Error("Could not load notes")
110
+ if (!response.ok) throw new Error(t("loadError"))
109
111
  setNotes(await response.json())
110
112
  } finally {
111
113
  setLoading(false)
@@ -126,9 +128,9 @@ export default function NotesApp(_props: PluginComponentProps) {
126
128
  headers: { "Content-Type": "application/json" },
127
129
  body: JSON.stringify({ body: cleaned }),
128
130
  })
129
- if (!response.ok) throw new Error("Could not save note")
131
+ if (!response.ok) throw new Error(t("saveError"))
130
132
  setBody("")
131
- showToast("Note saved", "success")
133
+ showToast(t("saved"), "success")
132
134
  await load()
133
135
  } finally {
134
136
  setSaving(false)
@@ -139,11 +141,10 @@ export default function NotesApp(_props: PluginComponentProps) {
139
141
  <div className="notes-app">
140
142
  <style>{styles}</style>
141
143
  <main className="notes-shell">
142
- <p className="notes-kicker">Palette DB App</p>
143
- <h1 className="notes-title">Org Notes</h1>
144
+ <p className="notes-kicker">{t("kicker")}</p>
145
+ <h1 className="notes-title">{t("title")}</h1>
144
146
  <p className="notes-copy">
145
- Built for {user.name}. This app uses a Python backend and the Palette
146
- SDK database session exposed as <code>ctx.db</code>.
147
+ {t("copy", { name: user.name })}
147
148
  </p>
148
149
  <section className="notes-form">
149
150
  <input
@@ -153,7 +154,7 @@ export default function NotesApp(_props: PluginComponentProps) {
153
154
  onKeyDown={(event) => {
154
155
  if (event.key === "Enter") void add()
155
156
  }}
156
- placeholder="New note..."
157
+ placeholder={t("placeholder")}
157
158
  />
158
159
  <button
159
160
  type="button"
@@ -161,14 +162,14 @@ export default function NotesApp(_props: PluginComponentProps) {
161
162
  disabled={saving || body.trim().length === 0}
162
163
  className="notes-button"
163
164
  >
164
- {saving ? "Saving" : "Add Note"}
165
+ {saving ? t("saving") : t("add")}
165
166
  </button>
166
167
  </section>
167
168
  <section className="notes-list">
168
169
  {loading ? (
169
- <p className="notes-empty">Loading notes...</p>
170
+ <p className="notes-empty">{t("loading")}</p>
170
171
  ) : notes.length === 0 ? (
171
- <p className="notes-empty">No notes yet.</p>
172
+ <p className="notes-empty">{t("empty")}</p>
172
173
  ) : (
173
174
  notes.map((note) => (
174
175
  <p key={note.id} className="notes-item">
@@ -0,0 +1,30 @@
1
+ import type { TranslationResources } from "@palettelab/sdk"
2
+
3
+ export const translations = {
4
+ en: {
5
+ kicker: "Palette DB App",
6
+ title: "Org Notes",
7
+ copy: "Built for {{name}}. This app uses a Python backend and the Palette SDK database session exposed as ctx.db.",
8
+ placeholder: "New note...",
9
+ add: "Add Note",
10
+ saving: "Saving",
11
+ loading: "Loading notes...",
12
+ empty: "No notes yet.",
13
+ loadError: "Could not load notes",
14
+ saveError: "Could not save note",
15
+ saved: "Note saved",
16
+ },
17
+ ko: {
18
+ kicker: "Palette DB 앱",
19
+ title: "조직 노트",
20
+ copy: "{{name}}님을 위해 생성되었습니다. 이 앱은 Python 백엔드와 ctx.db로 제공되는 Palette SDK 데이터베이스 세션을 사용합니다.",
21
+ placeholder: "새 노트...",
22
+ add: "노트 추가",
23
+ saving: "저장 중",
24
+ loading: "노트를 불러오는 중...",
25
+ empty: "아직 노트가 없습니다.",
26
+ loadError: "노트를 불러올 수 없습니다",
27
+ saveError: "노트를 저장할 수 없습니다",
28
+ saved: "노트가 저장되었습니다",
29
+ },
30
+ } satisfies TranslationResources
@@ -2,5 +2,5 @@
2
2
  "name": "my-db-plugin",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "dependencies": { "@palettelab/sdk": "^0.1.10", "react": "^19.0.0" }
5
+ "dependencies": { "@palettelab/sdk": "^0.1.11", "react": "^19.0.0" }
6
6
  }
@@ -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.0", "backend": "^0.1.0" },
12
+ "sdk": { "frontend": "^0.1.11", "backend": "^0.1.0" },
13
13
  "platform": { "min_version": "0.1.0" },
14
14
  "capabilities": {
15
15
  "frontend": true,
@@ -1,16 +1,18 @@
1
1
  "use client"
2
2
 
3
3
  import { useState } from "react"
4
- import { usePlatform } from "@palettelab/sdk"
4
+ import { usePlatform, usePluginTranslations } from "@palettelab/sdk"
5
5
  import type { PluginComponentProps } from "@palettelab/sdk"
6
+ import { translations } from "./translations"
6
7
 
7
8
  export default function ExternalServiceWidget(_props: PluginComponentProps) {
8
9
  const { apiFetch } = usePlatform()
10
+ const { t } = usePluginTranslations(translations)
9
11
  const [result, setResult] = useState<string>("")
10
12
 
11
13
  return (
12
14
  <div className="p-6 space-y-3">
13
- <h1 className="text-2xl font-bold">External Service</h1>
15
+ <h1 className="text-2xl font-bold">{t("title")}</h1>
14
16
  <button
15
17
  className="px-3 py-1.5 rounded bg-primary text-primary-foreground text-sm"
16
18
  onClick={async () => {
@@ -18,7 +20,7 @@ export default function ExternalServiceWidget(_props: PluginComponentProps) {
18
20
  setResult(await r.text())
19
21
  }}
20
22
  >
21
- Call api.example.com
23
+ {t("call")}
22
24
  </button>
23
25
  {result && <pre className="text-xs">{result}</pre>}
24
26
  </div>
@@ -0,0 +1,12 @@
1
+ import type { TranslationResources } from "@palettelab/sdk"
2
+
3
+ export const translations = {
4
+ en: {
5
+ title: "External Service",
6
+ call: "Call api.example.com",
7
+ },
8
+ ko: {
9
+ title: "외부 서비스",
10
+ call: "api.example.com 호출",
11
+ },
12
+ } satisfies TranslationResources
@@ -2,5 +2,5 @@
2
2
  "name": "my-external-svc",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "dependencies": { "@palettelab/sdk": "^0.1.10", "react": "^19.0.0" }
5
+ "dependencies": { "@palettelab/sdk": "^0.1.11", "react": "^19.0.0" }
6
6
  }
@@ -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.0", "backend": "^0.1.0" },
12
+ "sdk": { "frontend": "^0.1.11", "backend": "^0.1.0" },
13
13
  "platform": { "min_version": "0.1.0" },
14
14
  "capabilities": {
15
15
  "frontend": true,
@@ -1,15 +1,18 @@
1
1
  "use client"
2
2
 
3
- import { usePlatform } from "@palettelab/sdk"
3
+ import { usePlatform, usePluginTranslations } from "@palettelab/sdk"
4
4
  import type { PluginComponentProps } from "@palettelab/sdk"
5
+ import { translations } from "./translations"
5
6
 
6
7
  export default function FrontendOnlyPlugin(_props: PluginComponentProps) {
7
8
  const { user } = usePlatform()
9
+ const { t } = usePluginTranslations(translations)
10
+
8
11
  return (
9
12
  <div className="p-6">
10
- <h1 className="text-2xl font-bold">Hello, {user.name}</h1>
13
+ <h1 className="text-2xl font-bold">{t("title", { name: user.name })}</h1>
11
14
  <p className="text-muted-foreground mt-2">
12
- This plugin runs entirely in the browser sandbox — no backend.
15
+ {t("body")}
13
16
  </p>
14
17
  </div>
15
18
  )
@@ -0,0 +1,12 @@
1
+ import type { TranslationResources } from "@palettelab/sdk"
2
+
3
+ export const translations = {
4
+ en: {
5
+ title: "Hello, {{name}}",
6
+ body: "This plugin runs entirely in the browser sandbox and follows the Palette OS language.",
7
+ },
8
+ ko: {
9
+ title: "안녕하세요, {{name}}님",
10
+ body: "이 플러그인은 브라우저 샌드박스에서만 실행되며 Palette OS 언어를 따릅니다.",
11
+ },
12
+ } satisfies TranslationResources
@@ -3,7 +3,7 @@
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "dependencies": {
6
- "@palettelab/sdk": "^0.1.10",
6
+ "@palettelab/sdk": "^0.1.11",
7
7
  "react": "^19.0.0"
8
8
  }
9
9
  }
@@ -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.0" },
12
+ "sdk": { "frontend": "^0.1.11" },
13
13
  "platform": { "min_version": "0.1.0" },
14
14
  "capabilities": {
15
15
  "frontend": true,