@polpo-ai/dashboard 0.1.0

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 (135) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +44 -0
  3. package/app/(dashboard)/error.tsx +47 -0
  4. package/app/(dashboard)/layout.tsx +41 -0
  5. package/app/(dashboard)/loading.tsx +30 -0
  6. package/app/(dashboard)/projects/[id]/agents/[name]/memory/view.tsx +36 -0
  7. package/app/(dashboard)/projects/[id]/agents/[name]/models-view.tsx +30 -0
  8. package/app/(dashboard)/projects/[id]/agents/[name]/page.tsx +51 -0
  9. package/app/(dashboard)/projects/[id]/agents/[name]/prompt/view.tsx +211 -0
  10. package/app/(dashboard)/projects/[id]/agents/[name]/skills/view.tsx +84 -0
  11. package/app/(dashboard)/projects/[id]/agents/[name]/tools/view.tsx +525 -0
  12. package/app/(dashboard)/projects/[id]/agents/[name]/vault/view.tsx +61 -0
  13. package/app/(dashboard)/projects/[id]/agents/page.tsx +39 -0
  14. package/app/(dashboard)/projects/[id]/agents/view.tsx +283 -0
  15. package/app/(dashboard)/projects/[id]/layout.tsx +11 -0
  16. package/app/(dashboard)/projects/[id]/logs/page.tsx +27 -0
  17. package/app/(dashboard)/projects/[id]/logs/view.tsx +184 -0
  18. package/app/(dashboard)/projects/[id]/memory/page.tsx +40 -0
  19. package/app/(dashboard)/projects/[id]/memory/view.tsx +17 -0
  20. package/app/(dashboard)/projects/[id]/missions/[missionId]/page.tsx +88 -0
  21. package/app/(dashboard)/projects/[id]/missions/[missionId]/view.tsx +569 -0
  22. package/app/(dashboard)/projects/[id]/missions/page.tsx +32 -0
  23. package/app/(dashboard)/projects/[id]/missions/view.tsx +282 -0
  24. package/app/(dashboard)/projects/[id]/onboarding-checklist.tsx +359 -0
  25. package/app/(dashboard)/projects/[id]/page.tsx +68 -0
  26. package/app/(dashboard)/projects/[id]/playbooks/[name]/page.tsx +5 -0
  27. package/app/(dashboard)/projects/[id]/playbooks/[name]/view.tsx +433 -0
  28. package/app/(dashboard)/projects/[id]/playbooks/page.tsx +5 -0
  29. package/app/(dashboard)/projects/[id]/playbooks/view.tsx +222 -0
  30. package/app/(dashboard)/projects/[id]/schedules/page.tsx +44 -0
  31. package/app/(dashboard)/projects/[id]/schedules/view.tsx +385 -0
  32. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/page.tsx +55 -0
  33. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/raw-view.tsx +373 -0
  34. package/app/(dashboard)/projects/[id]/sessions/[sessionId]/view.tsx +124 -0
  35. package/app/(dashboard)/projects/[id]/sessions/page.tsx +31 -0
  36. package/app/(dashboard)/projects/[id]/sessions/view.tsx +242 -0
  37. package/app/(dashboard)/projects/[id]/settings/page.tsx +52 -0
  38. package/app/(dashboard)/projects/[id]/skills/[name]/page.tsx +65 -0
  39. package/app/(dashboard)/projects/[id]/skills/[name]/view.tsx +227 -0
  40. package/app/(dashboard)/projects/[id]/skills/page.tsx +31 -0
  41. package/app/(dashboard)/projects/[id]/skills/view.tsx +243 -0
  42. package/app/(dashboard)/projects/[id]/storage/page.tsx +5 -0
  43. package/app/(dashboard)/projects/[id]/storage/view.tsx +385 -0
  44. package/app/(dashboard)/projects/[id]/tasks/[taskId]/activity/view.tsx +336 -0
  45. package/app/(dashboard)/projects/[id]/tasks/[taskId]/assessment/view.tsx +113 -0
  46. package/app/(dashboard)/projects/[id]/tasks/[taskId]/blueprint.ts +72 -0
  47. package/app/(dashboard)/projects/[id]/tasks/[taskId]/output/view.tsx +90 -0
  48. package/app/(dashboard)/projects/[id]/tasks/[taskId]/page.tsx +63 -0
  49. package/app/(dashboard)/projects/[id]/tasks/[taskId]/view.tsx +137 -0
  50. package/app/(dashboard)/projects/[id]/tasks/page.tsx +40 -0
  51. package/app/(dashboard)/projects/[id]/tasks/view.tsx +421 -0
  52. package/app/(dashboard)/projects/[id]/view.tsx +421 -0
  53. package/app/(dashboard)/projects/[id]/welcome-banner.tsx +156 -0
  54. package/app/(dashboard)/projects/projects-list.tsx +126 -0
  55. package/app/(playground)/error.tsx +42 -0
  56. package/app/(playground)/layout.tsx +27 -0
  57. package/app/(playground)/projects/[id]/playground/page.tsx +57 -0
  58. package/app/(playground)/projects/[id]/playground/view.tsx +167 -0
  59. package/app/(playground)/projects/[id]/playground-legacy/page.tsx +47 -0
  60. package/app/(playground)/projects/[id]/playground-legacy/skeleton.tsx +63 -0
  61. package/app/(playground)/projects/[id]/playground-legacy/view.tsx +237 -0
  62. package/app/favicon.ico +0 -0
  63. package/app/globals.css +385 -0
  64. package/app/icon.svg +6 -0
  65. package/app/layout.tsx +39 -0
  66. package/app/page.tsx +11 -0
  67. package/components/ai-elements/code-block.tsx +562 -0
  68. package/components/dashboard/agent-capabilities.tsx +326 -0
  69. package/components/dashboard/agent-identity-header.tsx +50 -0
  70. package/components/dashboard/agent-model-picker.tsx +195 -0
  71. package/components/dashboard/agent-model-selector.tsx +121 -0
  72. package/components/dashboard/agent-picker-card.tsx +46 -0
  73. package/components/dashboard/agent-studio.tsx +242 -0
  74. package/components/dashboard/agents-table.tsx +48 -0
  75. package/components/dashboard/breadcrumb.tsx +70 -0
  76. package/components/dashboard/builder-chat.tsx +418 -0
  77. package/components/dashboard/chat-shell.tsx +40 -0
  78. package/components/dashboard/client-picker.tsx +63 -0
  79. package/components/dashboard/command-snippet.tsx +77 -0
  80. package/components/dashboard/connect-dialog.tsx +680 -0
  81. package/components/dashboard/copy-button.tsx +45 -0
  82. package/components/dashboard/copy-card.tsx +48 -0
  83. package/components/dashboard/file-browser.tsx +161 -0
  84. package/components/dashboard/hint.tsx +22 -0
  85. package/components/dashboard/inference-mode.tsx +15 -0
  86. package/components/dashboard/manual-refresh-button.tsx +52 -0
  87. package/components/dashboard/markdown.tsx +31 -0
  88. package/components/dashboard/mcp-install-panel.tsx +196 -0
  89. package/components/dashboard/mission-graph.tsx +262 -0
  90. package/components/dashboard/nav-tabs.tsx +86 -0
  91. package/components/dashboard/polpo-chat.tsx +468 -0
  92. package/components/dashboard/project-copilot.tsx +253 -0
  93. package/components/dashboard/sdk-snippet-panel.tsx +194 -0
  94. package/components/dashboard/section-header.tsx +37 -0
  95. package/components/dashboard/settings-form.tsx +2477 -0
  96. package/components/dashboard/sidebar.tsx +385 -0
  97. package/components/dashboard/skeletons.tsx +1054 -0
  98. package/components/dashboard/skills-install-wizard.tsx +273 -0
  99. package/components/dashboard/swarm-runs.tsx +316 -0
  100. package/components/dashboard/tab-toggle.tsx +35 -0
  101. package/components/dashboard/task-studio.tsx +224 -0
  102. package/components/dashboard/top-header.tsx +203 -0
  103. package/components/dashboard/webhook-deliveries.tsx +234 -0
  104. package/components/icons/coding-agents.tsx +151 -0
  105. package/components/json-ld.tsx +8 -0
  106. package/components/providers.tsx +25 -0
  107. package/components/ui/badge.tsx +52 -0
  108. package/components/ui/button.tsx +60 -0
  109. package/components/ui/card.tsx +103 -0
  110. package/components/ui/chart.tsx +356 -0
  111. package/components/ui/command.tsx +196 -0
  112. package/components/ui/dialog.tsx +157 -0
  113. package/components/ui/input-group.tsx +158 -0
  114. package/components/ui/input.tsx +20 -0
  115. package/components/ui/multi-select.tsx +161 -0
  116. package/components/ui/popover.tsx +90 -0
  117. package/components/ui/select.tsx +201 -0
  118. package/components/ui/separator.tsx +25 -0
  119. package/components/ui/sheet.tsx +135 -0
  120. package/components/ui/skeleton.tsx +13 -0
  121. package/components/ui/tabs.tsx +56 -0
  122. package/components/ui/textarea.tsx +18 -0
  123. package/components/ui/tooltip.tsx +66 -0
  124. package/hooks/use-desktop-sidebar.tsx +50 -0
  125. package/hooks/use-mobile-sidebar.tsx +37 -0
  126. package/index.ts +14 -0
  127. package/lib/api.ts +194 -0
  128. package/lib/builder-context.ts +60 -0
  129. package/lib/data-client.ts +68 -0
  130. package/lib/get-query-client.ts +25 -0
  131. package/lib/polpo-client.tsx +49 -0
  132. package/lib/tool-catalog.ts +234 -0
  133. package/lib/use-event-catalog.ts +28 -0
  134. package/lib/utils.ts +6 -0
  135. package/package.json +99 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Client-side data fetching for the (self-hosted) data plane.
3
+ *
4
+ * Dogfoods the public SDK (`@polpo-ai/sdk`): a single `PolpoClient` carries the
5
+ * transport, auth and error handling. We address resources by path and re-wrap
6
+ * the result in the `{ ok, data }` envelope the views already expect — so no
7
+ * view changes are needed.
8
+ *
9
+ * OSS (single-tenant): one instance, Bearer key → local server. The `projectId`
10
+ * argument is accepted for signature parity with the cloud proxy but ignored.
11
+ * Cloud overrides this module with its session-proxy variant.
12
+ */
13
+
14
+ import { PolpoClient } from "@polpo-ai/sdk";
15
+
16
+ const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
17
+ const API_PREFIX = process.env.NEXT_PUBLIC_POLPO_API_PREFIX ?? "/api/v1";
18
+
19
+ const client = new PolpoClient({
20
+ baseUrl: API_URL,
21
+ apiPrefix: API_PREFIX,
22
+ apiKey: process.env.NEXT_PUBLIC_POLPO_API_KEY,
23
+ });
24
+
25
+ // Callers pass `/v1/...` paths (verbatim, as the cloud proxy forwarded them).
26
+ // `apiPrefix` already carries the API root (default `/api/v1`), so strip the
27
+ // leading `/v1` to avoid doubling it → `${baseUrl}/api/v1/...`.
28
+ const norm = (path: string) => path.replace(/^\/v1(?=\/|$)/, "");
29
+
30
+ type Envelope<T> = { ok: true; data: T };
31
+
32
+ /** Fetch from the data plane. Returns the `{ ok, data }` envelope. */
33
+ export async function fetchDataPlane<T>(_projectId: string, path: string): Promise<T> {
34
+ const data = await client.requestPath<unknown>("GET", norm(path));
35
+ return { ok: true, data } as Envelope<unknown> as T;
36
+ }
37
+
38
+ /** Mutate (POST/PUT/PATCH/DELETE) on the data plane. Returns the envelope. */
39
+ export async function mutateDataPlane<T>(
40
+ _projectId: string,
41
+ path: string,
42
+ opts: { method: string; body?: unknown },
43
+ ): Promise<T> {
44
+ const data = await client.requestPath<unknown>(opts.method, norm(path), opts.body);
45
+ return { ok: true, data } as Envelope<unknown> as T;
46
+ }
47
+
48
+ /**
49
+ * Control-plane fetch. There is no control plane in single-tenant self-host —
50
+ * orgs, billing and keys live only in the cloud.
51
+ *
52
+ * GATING (temporary): short-circuit with benign single-tenant stubs so the
53
+ * shell (sidebar / copilot project chrome) renders without hitting endpoints
54
+ * that don't exist on the OSS server. This is a band-aid, NOT the final design.
55
+ *
56
+ * TODO — un-mix scope per component instead of gating:
57
+ * - project name/org (`/v1/projects/:id`) → pure cloud chrome → drop / static.
58
+ * - BYOK (`/v1/byok/:id`) → has an OSS equivalent: re-point to the data-plane
59
+ * `vault` route. Same concept, different plane (control in cloud, vault in OSS).
60
+ * - api-keys / autumn billing → pure cloud → not in the OSS shell.
61
+ */
62
+ export async function fetchControlPlane<T>(path: string): Promise<T> {
63
+ // `/v1/projects/:id` → the chrome only reads { name, orgId } for display.
64
+ if (/^\/v1\/projects\/[^/]+$/.test(path)) {
65
+ return { name: "Local", orgId: "local" } as T;
66
+ }
67
+ return {} as T;
68
+ }
@@ -0,0 +1,25 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import { cache } from "react";
3
+
4
+ /**
5
+ * Request-scoped QueryClient singleton for Server Components.
6
+ * React's cache() ensures one QueryClient per server request,
7
+ * preventing cross-request data leakage.
8
+ *
9
+ * Usage: call getQueryClient() in Server Components to prefetch queries,
10
+ * then wrap children in <HydrationBoundary state={dehydrate(queryClient)}>.
11
+ */
12
+ const getQueryClient = cache(
13
+ () =>
14
+ new QueryClient({
15
+ defaultOptions: {
16
+ queries: {
17
+ staleTime: 30_000,
18
+ refetchOnWindowFocus: true,
19
+ retry: 1,
20
+ },
21
+ },
22
+ })
23
+ );
24
+
25
+ export default getQueryClient;
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { PolpoClient } from "@polpo-ai/sdk";
4
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
5
+
6
+ const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
7
+ const API_PREFIX = process.env.NEXT_PUBLIC_POLPO_API_PREFIX ?? "/api/v1";
8
+ const API_KEY = process.env.NEXT_PUBLIC_POLPO_API_KEY;
9
+
10
+ export type PolpoClientFactory = (projectId: string) => PolpoClient;
11
+
12
+ /**
13
+ * Default factory: single-tenant self-host. Points straight at the OSS server
14
+ * with a Bearer key. `projectId` is accepted for parity but ignored.
15
+ *
16
+ * Same SDK we publish to customers — every bug they'd hit, we hit first.
17
+ */
18
+ export function createPolpoClient(_projectId: string): PolpoClient {
19
+ return new PolpoClient({
20
+ baseUrl: API_URL,
21
+ apiPrefix: API_PREFIX,
22
+ apiKey: API_KEY,
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Lets a host app inject its own client factory so the SAME view components can
28
+ * run against a different transport. OSS renders with no provider (→ the direct
29
+ * factory above); the cloud wraps the shared views in
30
+ * `<DataClientProvider factory={sessionProxyFactory}>` so they route through its
31
+ * per-project session proxy. This is the Phase-2 seam for de-duping the views.
32
+ */
33
+ const DataClientContext = createContext<PolpoClientFactory | null>(null);
34
+
35
+ export function DataClientProvider({
36
+ factory,
37
+ children,
38
+ }: {
39
+ factory: PolpoClientFactory;
40
+ children: ReactNode;
41
+ }) {
42
+ return <DataClientContext.Provider value={factory}>{children}</DataClientContext.Provider>;
43
+ }
44
+
45
+ /** Memoized client — uses the injected factory if present, else the OSS default. */
46
+ export function usePolpoClient(projectId: string): PolpoClient {
47
+ const factory = useContext(DataClientContext) ?? createPolpoClient;
48
+ return useMemo(() => factory(projectId), [factory, projectId]);
49
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Static catalog of agent runtime tools (the strings used in an agent's
3
+ * `allowedTools`). Mirrors the `@polpo-ai/tools` package so the dashboard
4
+ * can show the FULL catalog — not just what a given agent has enabled —
5
+ * with human-readable descriptions and per-category metadata.
6
+ *
7
+ * Descriptions are taken from the tool definitions in @polpo-ai/tools.
8
+ * Keep in sync when the package adds/changes tools. (A future
9
+ * GET /v1/tools endpoint will replace this — see todo-tools-catalog-endpoint.)
10
+ *
11
+ * `core: true` → always available, cannot be disabled (no allowedTools entry needed).
12
+ * `requiresKey` → needs an external credential (vault entry or env var) to run.
13
+ */
14
+
15
+ export interface CatalogTool {
16
+ name: string;
17
+ description: string;
18
+ }
19
+
20
+ export interface CatalogGroup {
21
+ /** Stable key + the tool-name prefix for non-core groups (e.g. "image_"). */
22
+ key: string;
23
+ /** Display label, e.g. "Image". */
24
+ label: string;
25
+ /** Lucide icon name, resolved to a component in the view. */
26
+ icon: string;
27
+ /** Core groups are always available and have no on/off state. */
28
+ core?: boolean;
29
+ /**
30
+ * Baseline groups are ON by default (when the agent has no `allowedTools`
31
+ * set) but CAN be turned off — they mirror the runtime, which keeps these
32
+ * tools only while `allowedTools` is unset, and filters them otherwise.
33
+ */
34
+ baseline?: boolean;
35
+ /** At least one tool in the group needs an external API key / credential. */
36
+ requiresKey?: boolean;
37
+ /** One-line note about what the group unlocks / requires. */
38
+ note?: string;
39
+ tools: CatalogTool[];
40
+ }
41
+
42
+ export const TOOL_CATALOG: CatalogGroup[] = [
43
+ {
44
+ key: "core",
45
+ label: "Core",
46
+ icon: "ShieldCheck",
47
+ core: true,
48
+ note: "Always on for every agent — vault access and task outcomes. Cannot be disabled.",
49
+ tools: [
50
+ { name: "vault_get", description: "Retrieve credentials for a service from the agent vault." },
51
+ { name: "vault_list", description: "List available vault services (names + key names, no values)." },
52
+ { name: "register_outcome", description: "Register a file, text, URL or data as a task deliverable — the only way to declare outcomes. Task runs only." },
53
+ ],
54
+ },
55
+ {
56
+ key: "fs",
57
+ label: "Coding",
58
+ icon: "Terminal",
59
+ baseline: true,
60
+ note: "Read/write files and run shell commands. On by default — turn tools off to sandbox the agent. (Once you customise tools, only the ones left on here stay available.)",
61
+ tools: [
62
+ { name: "read", description: "Read the contents of a file. Returns line-numbered text; supports offset/limit for large files." },
63
+ { name: "write", description: "Create or overwrite a file. Parent directories are created automatically." },
64
+ { name: "edit", description: "Replace a unique string in a file. The old text must appear exactly once." },
65
+ { name: "bash", description: "Execute a shell command and return its output — tests, packages, git, etc." },
66
+ { name: "glob", description: "Find files matching a glob pattern (**, *, ?, [abc], {a,b})." },
67
+ { name: "grep", description: "Search files for a regex pattern (PCRE). Returns matching lines with paths + line numbers." },
68
+ { name: "ls", description: "List files and directories at a given path." },
69
+ ],
70
+ },
71
+ {
72
+ key: "http_",
73
+ label: "HTTP",
74
+ icon: "Globe",
75
+ baseline: true,
76
+ note: "Outbound HTTP — fetch URLs and download files. SSRF-protected. On by default.",
77
+ tools: [
78
+ { name: "http_fetch", description: "Make an HTTP request (any method, headers, body). Returns status, headers and body. SSRF-protected." },
79
+ { name: "http_download", description: "Download a file from a URL and save it locally." },
80
+ ],
81
+ },
82
+ {
83
+ key: "browser_",
84
+ label: "Browser",
85
+ icon: "Globe",
86
+ note: "Browser automation via the agent-browser runtime.",
87
+ tools: [
88
+ { name: "browser_navigate", description: "Open a URL in the browser. Launches it if not running." },
89
+ { name: "browser_snapshot", description: "Get the accessibility tree with element refs (@e1, @e2) for reliable targeting." },
90
+ { name: "browser_click", description: "Click an element. Use refs from a snapshot." },
91
+ { name: "browser_fill", description: "Clear an input and type new text." },
92
+ { name: "browser_type", description: "Type text into an element without clearing it first." },
93
+ { name: "browser_press", description: "Press a keyboard key, including modifiers (Control+a, Shift+Enter)." },
94
+ { name: "browser_screenshot", description: "Screenshot the current page. Returns the saved image path." },
95
+ { name: "browser_get", description: "Read element text/html/value, page title, or current URL." },
96
+ { name: "browser_select", description: "Select an option from a <select> dropdown." },
97
+ { name: "browser_hover", description: "Hover an element to trigger tooltips or menus." },
98
+ { name: "browser_scroll", description: "Scroll the page to load lazy content or navigate." },
99
+ { name: "browser_wait", description: "Wait for an element, text, URL pattern, or load state." },
100
+ { name: "browser_eval", description: "Run JavaScript in the page context and return the result." },
101
+ { name: "browser_close", description: "Close the browser session (profile data is saved)." },
102
+ { name: "browser_back", description: "Navigate back in browser history." },
103
+ { name: "browser_forward", description: "Navigate forward in browser history." },
104
+ { name: "browser_reload", description: "Reload the current page." },
105
+ { name: "browser_tabs", description: "Manage tabs: list, open, switch, or close." },
106
+ ],
107
+ },
108
+ {
109
+ key: "image_",
110
+ label: "Image",
111
+ icon: "Image",
112
+ requiresKey: true,
113
+ note: "Image generation + vision analysis. Needs a fal / OpenAI / Anthropic key.",
114
+ tools: [
115
+ { name: "image_generate", description: "Generate an image from a text prompt. Model from agent.image_model (default fal/flux/dev)." },
116
+ { name: "image_analyze", description: "Analyze an image with a vision model — describe, OCR, answer questions. Model from agent.vision_model." },
117
+ ],
118
+ },
119
+ {
120
+ key: "video_",
121
+ label: "Video",
122
+ icon: "Video",
123
+ requiresKey: true,
124
+ note: "Video generation. Needs a fal key.",
125
+ tools: [
126
+ { name: "video_generate", description: "Generate a video from a text prompt (MP4). Model from agent.video_model (default luma-ray-2-flash)." },
127
+ ],
128
+ },
129
+ {
130
+ key: "audio_",
131
+ label: "Audio",
132
+ icon: "Volume2",
133
+ requiresKey: true,
134
+ note: "Speech-to-text + text-to-speech. Needs OpenAI / Deepgram / ElevenLabs (Edge TTS is free).",
135
+ tools: [
136
+ { name: "audio_transcribe", description: "Transcribe an audio file to text. Model from agent.transcribe_model (default whisper-1)." },
137
+ { name: "audio_speak", description: "Synthesize speech from text. Model from agent.tts_model (default tts-1; edge is free, no key)." },
138
+ ],
139
+ },
140
+ {
141
+ key: "search_",
142
+ label: "Search",
143
+ icon: "Search",
144
+ note: "Managed web search — runs through the platform (Perplexity via the AI Gateway). No key required.",
145
+ tools: [
146
+ { name: "search_web", description: "Search the web with natural-language queries. Returns titles, URLs and snippets." },
147
+ { name: "search_find_similar", description: "Find web pages similar to a given URL — alternatives, competitors, related resources." },
148
+ ],
149
+ },
150
+ {
151
+ key: "email_",
152
+ label: "Email",
153
+ icon: "Mail",
154
+ requiresKey: true,
155
+ note: "SMTP/IMAP email. Needs mail server credentials.",
156
+ tools: [
157
+ { name: "email_send", description: "Send an email via SMTP. HTML, to/cc/bcc, attachments, reply-to. Domain allowlist enforced." },
158
+ { name: "email_draft", description: "Create a draft by appending a composed message to the IMAP Drafts folder." },
159
+ { name: "email_list", description: "List recent emails from a folder — subject, from, date, UID." },
160
+ { name: "email_read", description: "Read a full email by UID — headers, body, attachment metadata." },
161
+ { name: "email_search", description: "Search the mailbox for messages matching a query." },
162
+ { name: "email_download_attachment", description: "Download a specific attachment from a message to the output directory." },
163
+ { name: "email_verify", description: "Verify SMTP credentials / connectivity before sending." },
164
+ { name: "email_count", description: "Count messages in a mailbox folder." },
165
+ ],
166
+ },
167
+ {
168
+ key: "excel_",
169
+ label: "Spreadsheet",
170
+ icon: "Sheet",
171
+ note: "Spreadsheet (.xlsx / CSV) read + write.",
172
+ tools: [
173
+ { name: "excel_read", description: "Read rows from an Excel/CSV file with column headers. Supports sheet + range." },
174
+ { name: "excel_write", description: "Write structured rows to an Excel/CSV file." },
175
+ { name: "excel_query", description: "Query a spreadsheet with filters/aggregations and return matching rows." },
176
+ { name: "excel_info", description: "Inspect a workbook — sheets, dimensions, column headers." },
177
+ ],
178
+ },
179
+ {
180
+ key: "pdf_",
181
+ label: "PDF",
182
+ icon: "FileText",
183
+ note: "PDF read, create (HTML→PDF), merge.",
184
+ tools: [
185
+ { name: "pdf_read", description: "Extract text from a PDF — all or specific pages." },
186
+ { name: "pdf_create", description: "Render a full HTML document to a professional PDF via Chromium (full CSS support)." },
187
+ { name: "pdf_merge", description: "Merge multiple PDFs into a single document." },
188
+ { name: "pdf_info", description: "Inspect a PDF — page count and metadata." },
189
+ ],
190
+ },
191
+ {
192
+ key: "docx_",
193
+ label: "Docs",
194
+ icon: "FileType",
195
+ note: "Word document (.docx) read + create.",
196
+ tools: [
197
+ { name: "docx_read", description: "Read a .docx and extract content as text, Markdown or HTML (preserves structure)." },
198
+ { name: "docx_create", description: "Create a .docx with headings, paragraphs, lists and bold/italic formatting." },
199
+ ],
200
+ },
201
+ {
202
+ key: "memory_",
203
+ label: "Memory",
204
+ icon: "Brain",
205
+ note: "The agent's own persistent memory across sessions.",
206
+ tools: [
207
+ { name: "memory_get", description: "Read the agent's persistent memory (empty if none yet)." },
208
+ { name: "memory_save", description: "Overwrite the entire persistent memory with new content." },
209
+ { name: "memory_append", description: "Append a timestamped line to the persistent memory." },
210
+ { name: "memory_update", description: "Find-and-replace a unique section in the persistent memory." },
211
+ ],
212
+ },
213
+ ];
214
+
215
+ /** Every catalogued tool name, for detecting custom/unknown enabled tools. */
216
+ export const CATALOG_TOOL_NAMES = new Set(
217
+ TOOL_CATALOG.flatMap((g) => g.tools.map((t) => t.name)),
218
+ );
219
+
220
+ /**
221
+ * Is `toolName` enabled by an agent's `allowedTools`? True on an exact match
222
+ * or a matching wildcard (e.g. "image_*" enables "image_generate").
223
+ */
224
+ export function isToolEnabled(toolName: string, allowed: string[]): boolean {
225
+ if (allowed.includes(toolName)) return true;
226
+ return allowed.some(
227
+ (a) => a.endsWith("*") && toolName.startsWith(a.slice(0, -1)),
228
+ );
229
+ }
230
+
231
+ /** Does `allowed` enable a whole group via its wildcard (e.g. "image_*")? */
232
+ export function hasGroupWildcard(groupKey: string, allowed: string[]): boolean {
233
+ return allowed.includes(`${groupKey}*`);
234
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import type { EventCatalogGroup } from "@polpo-ai/core";
5
+
6
+ /**
7
+ * Fetches the event catalog from `/v1/events/catalog` (public, unauth).
8
+ * The catalog is the source of truth for the webhook UI's event picker —
9
+ * it lives in `@polpo-ai/core` and is served by the cloud control plane.
10
+ *
11
+ * Cached for 1h (matches the server's `Cache-Control: max-age=3600`):
12
+ * the catalog only changes when a new Polpo runtime version ships.
13
+ */
14
+ export type { EventCatalogGroup };
15
+
16
+ export function useEventCatalog() {
17
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
18
+ return useQuery<EventCatalogGroup[]>({
19
+ queryKey: ["events-catalog"],
20
+ queryFn: async () => {
21
+ const res = await fetch(`${apiUrl}/v1/events/catalog`);
22
+ if (!res.ok) throw new Error("Failed to load event catalog");
23
+ const json = await res.json();
24
+ return json.data?.groups ?? [];
25
+ },
26
+ staleTime: 60 * 60_000, // 1h — drift is acceptable
27
+ });
28
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@polpo-ai/dashboard",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "files": [
8
+ "index.ts",
9
+ "app",
10
+ "components",
11
+ "lib",
12
+ "hooks",
13
+ "README.md"
14
+ ],
15
+ "imports": {
16
+ "#/*": "./*"
17
+ },
18
+ "exports": {
19
+ ".": "./index.ts"
20
+ },
21
+ "dependencies": {
22
+ "@base-ui/react": "^1.3.0",
23
+ "@lobehub/icons": "^5.8.0",
24
+ "@lumea-labs/canvas": "^0.1.2",
25
+ "@lumea-labs/chat": "^0.1.5",
26
+ "@lumea-labs/chat-canvas": "^0.1.1",
27
+ "@lumea-labs/chat-polpo": "^0.1.2",
28
+ "@lumea-labs/docs": "^0.1.3",
29
+ "@lumea-labs/file-manager": "^0.1.1",
30
+ "@lumea-labs/file-manager-polpo": "^0.1.3",
31
+ "@lumea-labs/llm": "^0.1.3",
32
+ "@lumea-labs/llm-polpo": "^0.1.1",
33
+ "@lumea-labs/markdown": "^0.1.2",
34
+ "@lumea-labs/orchestrator": "^0.1.1",
35
+ "@lumea-labs/skills": "^0.1.1",
36
+ "@lumea-labs/skills-polpo": "^0.1.1",
37
+ "@lumea-labs/spreadsheet": "^0.1.3",
38
+ "@lumea-labs/tool-calls": "^0.1.2",
39
+ "@polpo-ai/chat": "^0.11.1",
40
+ "@streamdown/code": "^1.1.1",
41
+ "@tailwindcss/typography": "^0.5.19",
42
+ "@tanstack/react-query": "^5.90.21",
43
+ "@tiptap/extension-image": "^3.23.6",
44
+ "@tiptap/extension-placeholder": "^3.23.6",
45
+ "@tiptap/extension-table": "^3.23.6",
46
+ "@tiptap/extension-table-cell": "^3.23.6",
47
+ "@tiptap/extension-table-header": "^3.23.6",
48
+ "@tiptap/extension-table-row": "^3.23.6",
49
+ "@tiptap/markdown": "^3.23.6",
50
+ "@tiptap/react": "^3.23.6",
51
+ "@tiptap/starter-kit": "^3.23.6",
52
+ "@xyflow/react": "^12.10.1",
53
+ "@zwight/luckyexcel": "^1.1.6",
54
+ "class-variance-authority": "^0.7.1",
55
+ "clsx": "^2.1.1",
56
+ "cmdk": "^1.1.1",
57
+ "lucide-react": "^0.577.0",
58
+ "motion": "^12.36.0",
59
+ "next": "16.2.6",
60
+ "next-intl": "^4.12.0",
61
+ "next-themes": "^0.4.6",
62
+ "node-html-markdown": "^2.0.0",
63
+ "react": "19.2.6",
64
+ "react-dom": "19.2.6",
65
+ "react-markdown": "^10.1.0",
66
+ "react-virtuoso": "^4.12.0",
67
+ "recharts": "^2.15.4",
68
+ "shadcn": "^4.0.8",
69
+ "shiki": "^4.0.2",
70
+ "sonner": "^2.0.7",
71
+ "streamdown": "^2.5.0",
72
+ "sugar-high": "^1.1.0",
73
+ "tailwind-merge": "^3.5.0",
74
+ "tw-animate-css": "^1.4.0",
75
+ "@polpo-ai/core": "0.7.18",
76
+ "@polpo-ai/react": "0.7.18",
77
+ "@polpo-ai/sdk": "0.7.18"
78
+ },
79
+ "devDependencies": {
80
+ "@playwright/test": "^1.58.2",
81
+ "@tailwindcss/postcss": "^4",
82
+ "@types/node": "^20",
83
+ "@types/react": "^19",
84
+ "@types/react-dom": "^19",
85
+ "eslint": "^9",
86
+ "eslint-config-next": "16.2.6",
87
+ "tailwindcss": "^4",
88
+ "typescript": "^5"
89
+ },
90
+ "scripts": {
91
+ "dev": "next dev --turbopack",
92
+ "build": "next build",
93
+ "start": "next start",
94
+ "lint": "eslint",
95
+ "test:e2e": "playwright test",
96
+ "test:e2e:ui": "playwright test --ui",
97
+ "test:e2e:headed": "playwright test --headed"
98
+ }
99
+ }