@polpo-ai/dashboard 0.1.2 → 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.
- package/components/dashboard/skills-install-wizard.tsx +49 -130
- package/index.ts +3 -0
- package/package.json +1 -1
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
4
|
import { useParams } from "next/navigation";
|
|
5
|
-
import { useMutation,
|
|
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
|
-
import {
|
|
18
|
-
|
|
8
|
+
import {
|
|
9
|
+
SkillsProvider,
|
|
10
|
+
SkillCreateForm,
|
|
11
|
+
type SkillsAdapter,
|
|
12
|
+
type SkillCreateInput,
|
|
13
|
+
type Skill,
|
|
14
|
+
} from "@lumea-labs/skills";
|
|
19
15
|
import {
|
|
20
16
|
Dialog,
|
|
21
17
|
DialogContent,
|
|
@@ -26,49 +22,20 @@ import {
|
|
|
26
22
|
} from "../../components/ui/dialog";
|
|
27
23
|
|
|
28
24
|
/**
|
|
29
|
-
* SkillsInstallWizard —
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
33
|
-
*
|
|
34
|
-
* - from a URL → client.installSkills(githubUrl)
|
|
35
|
-
* - paste / create → @lumea-labs/skills <SkillCreateForm> over the
|
|
36
|
-
* 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).
|
|
37
30
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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.)
|
|
40
33
|
*/
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
type Source = "registry" | "url" | "paste";
|
|
45
|
-
|
|
46
|
-
interface RegistrySkill {
|
|
47
|
-
id?: string;
|
|
48
|
-
name: string;
|
|
49
|
-
description?: string;
|
|
50
|
-
owner?: string;
|
|
51
|
-
repo?: string;
|
|
52
|
-
repository?: string;
|
|
53
|
-
repositoryUrl?: string;
|
|
54
|
-
installs?: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Best-effort GitHub source for a skills.sh registry record. */
|
|
58
|
-
function sourceFor(s: RegistrySkill): string {
|
|
59
|
-
if (s.repositoryUrl) return s.repositoryUrl;
|
|
60
|
-
if (s.repository?.startsWith("http")) return s.repository;
|
|
61
|
-
if (s.owner && s.repo) return `https://github.com/${s.owner}/${s.repo}`;
|
|
62
|
-
return s.repository ?? s.name;
|
|
63
|
-
}
|
|
35
|
+
type Source = "url" | "paste";
|
|
64
36
|
|
|
65
37
|
export function SkillsInstallWizard() {
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<SkillsProvider adapter={adapter}>
|
|
69
|
-
<WizardDialog />
|
|
70
|
-
</SkillsProvider>
|
|
71
|
-
);
|
|
38
|
+
return <WizardDialog />;
|
|
72
39
|
}
|
|
73
40
|
|
|
74
41
|
function WizardDialog() {
|
|
@@ -78,33 +45,25 @@ function WizardDialog() {
|
|
|
78
45
|
|
|
79
46
|
const [open, setOpen] = useState(false);
|
|
80
47
|
const [source, setSource] = useState<Source | null>(null);
|
|
81
|
-
const [search, setSearch] = useState("");
|
|
82
48
|
const [url, setUrl] = useState("");
|
|
83
49
|
const [done, setDone] = useState<string | null>(null);
|
|
84
50
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!res.ok) throw new Error(`registry ${res.status}`);
|
|
102
|
-
const json = await res.json();
|
|
103
|
-
return (json.skills ?? json.data ?? json.results ?? json) as RegistrySkill[];
|
|
104
|
-
},
|
|
105
|
-
enabled: open && source === "registry",
|
|
106
|
-
staleTime: 60_000,
|
|
107
|
-
});
|
|
51
|
+
// Minimal SkillsAdapter on the PolpoClient (same de-dup seam usePolpoClient
|
|
52
|
+
// resolves: OSS direct, cloud session-proxy). Only createSkill is needed.
|
|
53
|
+
const adapter = useMemo<SkillsAdapter>(
|
|
54
|
+
() => ({
|
|
55
|
+
createSkill: async (input: SkillCreateInput): Promise<Skill> => {
|
|
56
|
+
const res = await client.createSkill({
|
|
57
|
+
name: input.name,
|
|
58
|
+
description: input.description,
|
|
59
|
+
content: input.body,
|
|
60
|
+
allowedTools: (input.frontmatter as { allowedTools?: string[] })?.allowedTools,
|
|
61
|
+
});
|
|
62
|
+
return { id: res.name, name: res.name, description: input.description, installed: true } as Skill;
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
[client],
|
|
66
|
+
);
|
|
108
67
|
|
|
109
68
|
const install = useMutation({
|
|
110
69
|
mutationFn: (src: string) => client.installSkills(src),
|
|
@@ -114,6 +73,13 @@ function WizardDialog() {
|
|
|
114
73
|
},
|
|
115
74
|
});
|
|
116
75
|
|
|
76
|
+
const reset = () => {
|
|
77
|
+
setSource(null);
|
|
78
|
+
setUrl("");
|
|
79
|
+
setDone(null);
|
|
80
|
+
install.reset();
|
|
81
|
+
};
|
|
82
|
+
|
|
117
83
|
const afterCreate = () => {
|
|
118
84
|
qc.invalidateQueries({ queryKey: ["skills", id] });
|
|
119
85
|
setDone("created");
|
|
@@ -134,7 +100,6 @@ function WizardDialog() {
|
|
|
134
100
|
</DialogDescription>
|
|
135
101
|
</DialogHeader>
|
|
136
102
|
|
|
137
|
-
{/* ── Result ─────────────────────────────────────────── */}
|
|
138
103
|
{done ? (
|
|
139
104
|
<div className="flex flex-col items-center gap-3 py-8 text-center">
|
|
140
105
|
<div className="grid h-10 w-10 place-items-center rounded-full bg-emerald-500/15 text-emerald-500">
|
|
@@ -152,14 +117,7 @@ function WizardDialog() {
|
|
|
152
117
|
</div>
|
|
153
118
|
</div>
|
|
154
119
|
) : !source ? (
|
|
155
|
-
/* ── Step 1: choose a source ──────────────────────── */
|
|
156
120
|
<div className="grid gap-2 pt-1">
|
|
157
|
-
<SourceCard
|
|
158
|
-
icon={<BookMarked className="h-4 w-4" />}
|
|
159
|
-
title="Browse the registry"
|
|
160
|
-
desc="34k+ skills from skills.sh — search and install."
|
|
161
|
-
onClick={() => setSource("registry")}
|
|
162
|
-
/>
|
|
163
121
|
<SourceCard
|
|
164
122
|
icon={<Link2 className="h-4 w-4" />}
|
|
165
123
|
title="From a URL"
|
|
@@ -175,49 +133,13 @@ function WizardDialog() {
|
|
|
175
133
|
</div>
|
|
176
134
|
) : (
|
|
177
135
|
<div className="flex flex-col gap-3 pt-1">
|
|
178
|
-
<button
|
|
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
|
+
>
|
|
179
140
|
<ArrowLeft className="h-3 w-3" /> Back
|
|
180
141
|
</button>
|
|
181
142
|
|
|
182
|
-
{/* ── Registry ──────────────────────────────────── */}
|
|
183
|
-
{source === "registry" && (
|
|
184
|
-
<>
|
|
185
|
-
<div className="flex items-center gap-2 border border-border px-2.5">
|
|
186
|
-
<Search className="h-3.5 w-3.5 text-muted-foreground" />
|
|
187
|
-
<input
|
|
188
|
-
value={search}
|
|
189
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
190
|
-
placeholder="Search skills…"
|
|
191
|
-
className="w-full bg-transparent py-2 text-sm outline-none"
|
|
192
|
-
/>
|
|
193
|
-
</div>
|
|
194
|
-
{isFetching ? (
|
|
195
|
-
<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>
|
|
196
|
-
) : regError ? (
|
|
197
|
-
<p className="py-6 text-center text-xs text-muted-foreground">Registry unavailable. Try the URL option.</p>
|
|
198
|
-
) : (
|
|
199
|
-
<ul className="flex max-h-[40vh] flex-col gap-1 overflow-y-auto">
|
|
200
|
-
{registry.map((s) => (
|
|
201
|
-
<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">
|
|
202
|
-
<div className="min-w-0">
|
|
203
|
-
<div className="truncate text-sm font-medium">{s.name}</div>
|
|
204
|
-
{s.description && <div className="truncate text-[11px] text-muted-foreground">{s.description}</div>}
|
|
205
|
-
</div>
|
|
206
|
-
<button
|
|
207
|
-
disabled={install.isPending}
|
|
208
|
-
onClick={() => install.mutate(sourceFor(s))}
|
|
209
|
-
className="shrink-0 border border-foreground bg-foreground px-2.5 py-1 text-[11px] text-background hover:bg-foreground/90 disabled:opacity-50"
|
|
210
|
-
>
|
|
211
|
-
{install.isPending ? <Loader2 className="h-3 w-3 animate-spin" /> : "Install"}
|
|
212
|
-
</button>
|
|
213
|
-
</li>
|
|
214
|
-
))}
|
|
215
|
-
</ul>
|
|
216
|
-
)}
|
|
217
|
-
</>
|
|
218
|
-
)}
|
|
219
|
-
|
|
220
|
-
{/* ── URL ────────────────────────────────────────── */}
|
|
221
143
|
{source === "url" && (
|
|
222
144
|
<form
|
|
223
145
|
onSubmit={(e) => { e.preventDefault(); if (url.trim()) install.mutate(url.trim()); }}
|
|
@@ -242,13 +164,10 @@ function WizardDialog() {
|
|
|
242
164
|
</form>
|
|
243
165
|
)}
|
|
244
166
|
|
|
245
|
-
{/* ── Paste / create (dogfood lumea-agents UI) ──── */}
|
|
246
167
|
{source === "paste" && (
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{install.isError && source === "registry" && (
|
|
251
|
-
<p className="text-xs text-destructive">{(install.error as Error).message}</p>
|
|
168
|
+
<SkillsProvider adapter={adapter}>
|
|
169
|
+
<SkillCreateForm onCreated={afterCreate} onCancel={() => setSource(null)} />
|
|
170
|
+
</SkillsProvider>
|
|
252
171
|
)}
|
|
253
172
|
</div>
|
|
254
173
|
)}
|
package/index.ts
CHANGED
|
@@ -12,3 +12,6 @@ export type { PolpoClientFactory } from "./lib/polpo-client";
|
|
|
12
12
|
|
|
13
13
|
export { default as AgentsView } from "./app/(dashboard)/projects/[id]/agents/view";
|
|
14
14
|
export type { Team } from "./app/(dashboard)/projects/[id]/agents/view";
|
|
15
|
+
|
|
16
|
+
export { default as SkillsView } from "./app/(dashboard)/projects/[id]/skills/view";
|
|
17
|
+
export type { SkillInfo } from "./app/(dashboard)/projects/[id]/skills/view";
|