@polpo-ai/dashboard 0.1.3 → 0.1.4

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.
@@ -2,17 +2,8 @@
2
2
 
3
3
  import { useMemo, useState } from "react";
4
4
  import { useParams } from "next/navigation";
5
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
- import {
7
- ArrowLeft,
8
- BookMarked,
9
- Check,
10
- Download,
11
- FilePlus2,
12
- Link2,
13
- Loader2,
14
- Search,
15
- } from "lucide-react";
5
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
6
+ import { ArrowLeft, Check, Download, FilePlus2, Link2, Loader2 } from "lucide-react";
16
7
  import { usePolpoClient } from "../../lib/polpo-client";
17
8
  import {
18
9
  SkillsProvider,
@@ -31,41 +22,17 @@ import {
31
22
  } from "../../components/ui/dialog";
32
23
 
33
24
  /**
34
- * SkillsInstallWizard — replaces the old read-only "how to install" dialog
35
- * with a real, guided stepper that ACTUALLY installs skills, dogfooding the
36
- * public SDK end to end:
37
- * - registry browsethe Mastra `skills-api` / skills.sh registry (the
38
- * browse surface Polpo itself doesn't expose) → client.installSkills(url)
39
- * - from a URL → client.installSkills(githubUrl)
40
- * - paste / create → @lumea-labs/skills <SkillCreateForm> over the
41
- * Polpo-wired adapter (adapter.createSkill)
25
+ * SkillsInstallWizard — guided stepper that actually installs skills, dogfooding
26
+ * the SDK end to end:
27
+ * - From a URL → client.installSkills(githubUrl)
28
+ * - Paste/create@lumea-labs/skills <SkillCreateForm> over a minimal
29
+ * SkillsAdapter built on the PolpoClient (adapter.createSkill).
42
30
  *
43
- * The wizard lives inside <SkillsProvider adapter={polpoAdapter}> so the
44
- * lumea-agents skills UI (SkillCreateForm here, more later) is wired to Polpo.
31
+ * (Registry browse via Mastra's @mastra/skills-api is a future addition — it's a
32
+ * Node lib with bundled data, so it needs a server action, not a browser fetch.)
45
33
  */
46
34
 
47
- const SKILLS_API = "https://skills.sh/api/skills";
48
-
49
- type Source = "registry" | "url" | "paste";
50
-
51
- interface RegistrySkill {
52
- id?: string;
53
- name: string;
54
- description?: string;
55
- owner?: string;
56
- repo?: string;
57
- repository?: string;
58
- repositoryUrl?: string;
59
- installs?: number;
60
- }
61
-
62
- /** Best-effort GitHub source for a skills.sh registry record. */
63
- function sourceFor(s: RegistrySkill): string {
64
- if (s.repositoryUrl) return s.repositoryUrl;
65
- if (s.repository?.startsWith("http")) return s.repository;
66
- if (s.owner && s.repo) return `https://github.com/${s.owner}/${s.repo}`;
67
- return s.repository ?? s.name;
68
- }
35
+ type Source = "url" | "paste";
69
36
 
70
37
  export function SkillsInstallWizard() {
71
38
  return <WizardDialog />;
@@ -76,10 +43,13 @@ function WizardDialog() {
76
43
  const client = usePolpoClient(id);
77
44
  const qc = useQueryClient();
78
45
 
79
- // Minimal SkillsAdapter built directly on the PolpoClient (the same seam
80
- // usePolpoClient resolves: OSS direct, cloud session-proxy). Avoids
81
- // @polpo-ai/react's <PolpoProvider>. Only createSkill is needed here —
82
- // registry/URL installs go through client.installSkills directly.
46
+ const [open, setOpen] = useState(false);
47
+ const [source, setSource] = useState<Source | null>(null);
48
+ const [url, setUrl] = useState("");
49
+ const [done, setDone] = useState<string | null>(null);
50
+
51
+ // Minimal SkillsAdapter on the PolpoClient (same de-dup seam usePolpoClient
52
+ // resolves: OSS direct, cloud session-proxy). Only createSkill is needed.
83
53
  const adapter = useMemo<SkillsAdapter>(
84
54
  () => ({
85
55
  createSkill: async (input: SkillCreateInput): Promise<Skill> => {
@@ -95,36 +65,6 @@ function WizardDialog() {
95
65
  [client],
96
66
  );
97
67
 
98
- const [open, setOpen] = useState(false);
99
- const [source, setSource] = useState<Source | null>(null);
100
- const [search, setSearch] = useState("");
101
- const [url, setUrl] = useState("");
102
- const [done, setDone] = useState<string | null>(null);
103
-
104
- const reset = () => {
105
- setSource(null);
106
- setSearch("");
107
- setUrl("");
108
- setDone(null);
109
- install.reset();
110
- };
111
-
112
- const { data: registry = [], isFetching, error: regError } = useQuery({
113
- queryKey: ["skills-registry", search],
114
- queryFn: async (): Promise<RegistrySkill[]> => {
115
- const u = new URL(SKILLS_API);
116
- u.searchParams.set("sortBy", "installs");
117
- u.searchParams.set("pageSize", "30");
118
- if (search.trim()) u.searchParams.set("query", search.trim());
119
- const res = await fetch(u.toString());
120
- if (!res.ok) throw new Error(`registry ${res.status}`);
121
- const json = await res.json();
122
- return (json.skills ?? json.data ?? json.results ?? json) as RegistrySkill[];
123
- },
124
- enabled: open && source === "registry",
125
- staleTime: 60_000,
126
- });
127
-
128
68
  const install = useMutation({
129
69
  mutationFn: (src: string) => client.installSkills(src),
130
70
  onSuccess: (_res, src) => {
@@ -133,6 +73,13 @@ function WizardDialog() {
133
73
  },
134
74
  });
135
75
 
76
+ const reset = () => {
77
+ setSource(null);
78
+ setUrl("");
79
+ setDone(null);
80
+ install.reset();
81
+ };
82
+
136
83
  const afterCreate = () => {
137
84
  qc.invalidateQueries({ queryKey: ["skills", id] });
138
85
  setDone("created");
@@ -153,7 +100,6 @@ function WizardDialog() {
153
100
  </DialogDescription>
154
101
  </DialogHeader>
155
102
 
156
- {/* ── Result ─────────────────────────────────────────── */}
157
103
  {done ? (
158
104
  <div className="flex flex-col items-center gap-3 py-8 text-center">
159
105
  <div className="grid h-10 w-10 place-items-center rounded-full bg-emerald-500/15 text-emerald-500">
@@ -171,14 +117,7 @@ function WizardDialog() {
171
117
  </div>
172
118
  </div>
173
119
  ) : !source ? (
174
- /* ── Step 1: choose a source ──────────────────────── */
175
120
  <div className="grid gap-2 pt-1">
176
- <SourceCard
177
- icon={<BookMarked className="h-4 w-4" />}
178
- title="Browse the registry"
179
- desc="34k+ skills from skills.sh — search and install."
180
- onClick={() => setSource("registry")}
181
- />
182
121
  <SourceCard
183
122
  icon={<Link2 className="h-4 w-4" />}
184
123
  title="From a URL"
@@ -194,49 +133,13 @@ function WizardDialog() {
194
133
  </div>
195
134
  ) : (
196
135
  <div className="flex flex-col gap-3 pt-1">
197
- <button onClick={() => { setSource(null); install.reset(); }} className="inline-flex w-fit items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground">
136
+ <button
137
+ onClick={() => { setSource(null); install.reset(); }}
138
+ className="inline-flex w-fit items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground"
139
+ >
198
140
  <ArrowLeft className="h-3 w-3" /> Back
199
141
  </button>
200
142
 
201
- {/* ── Registry ──────────────────────────────────── */}
202
- {source === "registry" && (
203
- <>
204
- <div className="flex items-center gap-2 border border-border px-2.5">
205
- <Search className="h-3.5 w-3.5 text-muted-foreground" />
206
- <input
207
- value={search}
208
- onChange={(e) => setSearch(e.target.value)}
209
- placeholder="Search skills…"
210
- className="w-full bg-transparent py-2 text-sm outline-none"
211
- />
212
- </div>
213
- {isFetching ? (
214
- <div className="flex items-center gap-2 py-6 text-xs text-muted-foreground"><Loader2 className="h-3.5 w-3.5 animate-spin" /> loading registry…</div>
215
- ) : regError ? (
216
- <p className="py-6 text-center text-xs text-muted-foreground">Registry unavailable. Try the URL option.</p>
217
- ) : (
218
- <ul className="flex max-h-[40vh] flex-col gap-1 overflow-y-auto">
219
- {registry.map((s) => (
220
- <li key={s.id ?? `${s.owner}/${s.repo}/${s.name}`} className="flex items-center justify-between gap-3 border border-border px-3 py-2">
221
- <div className="min-w-0">
222
- <div className="truncate text-sm font-medium">{s.name}</div>
223
- {s.description && <div className="truncate text-[11px] text-muted-foreground">{s.description}</div>}
224
- </div>
225
- <button
226
- disabled={install.isPending}
227
- onClick={() => install.mutate(sourceFor(s))}
228
- className="shrink-0 border border-foreground bg-foreground px-2.5 py-1 text-[11px] text-background hover:bg-foreground/90 disabled:opacity-50"
229
- >
230
- {install.isPending ? <Loader2 className="h-3 w-3 animate-spin" /> : "Install"}
231
- </button>
232
- </li>
233
- ))}
234
- </ul>
235
- )}
236
- </>
237
- )}
238
-
239
- {/* ── URL ────────────────────────────────────────── */}
240
143
  {source === "url" && (
241
144
  <form
242
145
  onSubmit={(e) => { e.preventDefault(); if (url.trim()) install.mutate(url.trim()); }}
@@ -261,16 +164,11 @@ function WizardDialog() {
261
164
  </form>
262
165
  )}
263
166
 
264
- {/* ── Paste / create (dogfood lumea-agents UI) ──── */}
265
167
  {source === "paste" && (
266
168
  <SkillsProvider adapter={adapter}>
267
169
  <SkillCreateForm onCreated={afterCreate} onCancel={() => setSource(null)} />
268
170
  </SkillsProvider>
269
171
  )}
270
-
271
- {install.isError && source === "registry" && (
272
- <p className="text-xs text-destructive">{(install.error as Error).message}</p>
273
- )}
274
172
  </div>
275
173
  )}
276
174
  </DialogContent>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polpo-ai/dashboard",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -70,8 +70,8 @@
70
70
  "tailwind-merge": "^3.5.0",
71
71
  "tw-animate-css": "^1.4.0",
72
72
  "@polpo-ai/core": "0.7.18",
73
- "@polpo-ai/react": "0.7.18",
74
- "@polpo-ai/sdk": "0.7.18"
73
+ "@polpo-ai/sdk": "0.7.18",
74
+ "@polpo-ai/react": "0.7.18"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@playwright/test": "^1.58.2",