@getcatalystiq/agent-plane-ui 0.1.28 → 0.1.29
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/dist/index.cjs +204 -15
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +204 -15
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3630,17 +3630,193 @@ function FileTreeEditor2({
|
|
|
3630
3630
|
] })
|
|
3631
3631
|
] });
|
|
3632
3632
|
}
|
|
3633
|
+
function TabButton({ label, active, onClick }) {
|
|
3634
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3635
|
+
"button",
|
|
3636
|
+
{
|
|
3637
|
+
onClick,
|
|
3638
|
+
className: `relative pb-2 text-sm font-medium transition-colors ${active ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
3639
|
+
children: [
|
|
3640
|
+
label,
|
|
3641
|
+
active && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-x-0 bottom-0 h-0.5 bg-foreground rounded-full" })
|
|
3642
|
+
]
|
|
3643
|
+
}
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
function ImportSkillDialog({ open, onOpenChange, onImported, existingFolders }) {
|
|
3647
|
+
const client = chunkXXF4U7WL_cjs.useAgentPlaneClient();
|
|
3648
|
+
const [tab, setTab] = React.useState("all");
|
|
3649
|
+
const [entries, setEntries] = React.useState([]);
|
|
3650
|
+
const [loading, setLoading] = React.useState(false);
|
|
3651
|
+
const [error, setError] = React.useState("");
|
|
3652
|
+
const [search, setSearch] = React.useState("");
|
|
3653
|
+
const [selected, setSelected] = React.useState(null);
|
|
3654
|
+
const [preview, setPreview] = React.useState("");
|
|
3655
|
+
const [previewLoading, setPreviewLoading] = React.useState(false);
|
|
3656
|
+
const [importing, setImporting] = React.useState(false);
|
|
3657
|
+
const [importError, setImportError] = React.useState("");
|
|
3658
|
+
const [url, setUrl] = React.useState("");
|
|
3659
|
+
const [urlImporting, setUrlImporting] = React.useState(false);
|
|
3660
|
+
React.useEffect(() => {
|
|
3661
|
+
if (!open) return;
|
|
3662
|
+
setLoading(true);
|
|
3663
|
+
setError("");
|
|
3664
|
+
setSelected(null);
|
|
3665
|
+
setPreview("");
|
|
3666
|
+
client.skillsDirectory.list(tab).then((data) => setEntries(data)).catch((err) => setError(err instanceof Error ? err.message : "Failed to load skills")).finally(() => setLoading(false));
|
|
3667
|
+
}, [tab, open, client]);
|
|
3668
|
+
const filtered = React.useMemo(() => {
|
|
3669
|
+
if (!search.trim()) return entries;
|
|
3670
|
+
const q = search.toLowerCase();
|
|
3671
|
+
return entries.filter(
|
|
3672
|
+
(e) => e.name.toLowerCase().includes(q) || e.owner.toLowerCase().includes(q) || e.repo.toLowerCase().includes(q)
|
|
3673
|
+
);
|
|
3674
|
+
}, [entries, search]);
|
|
3675
|
+
async function handleSelect(entry) {
|
|
3676
|
+
setSelected(entry);
|
|
3677
|
+
setPreviewLoading(true);
|
|
3678
|
+
setPreview("");
|
|
3679
|
+
setImportError("");
|
|
3680
|
+
try {
|
|
3681
|
+
const content = await client.skillsDirectory.preview(entry.owner, entry.repo, entry.skill);
|
|
3682
|
+
setPreview(content);
|
|
3683
|
+
} catch (err) {
|
|
3684
|
+
setPreview(`Failed to load preview: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
3685
|
+
} finally {
|
|
3686
|
+
setPreviewLoading(false);
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
async function handleImport() {
|
|
3690
|
+
if (!selected) return;
|
|
3691
|
+
setImporting(true);
|
|
3692
|
+
setImportError("");
|
|
3693
|
+
try {
|
|
3694
|
+
const result = await client.skillsDirectory.import({
|
|
3695
|
+
owner: selected.owner,
|
|
3696
|
+
repo: selected.repo,
|
|
3697
|
+
skill_name: selected.skill
|
|
3698
|
+
});
|
|
3699
|
+
if (existingFolders.includes(result.folder)) {
|
|
3700
|
+
setImportError(`Skill folder "${result.folder}" already exists. Remove it first or rename.`);
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
if (result.warnings.length > 0) {
|
|
3704
|
+
setImportError(`Imported with warnings: ${result.warnings.join("; ")}`);
|
|
3705
|
+
}
|
|
3706
|
+
onImported({ folder: result.folder, files: result.files });
|
|
3707
|
+
onOpenChange(false);
|
|
3708
|
+
resetState();
|
|
3709
|
+
} catch (err) {
|
|
3710
|
+
setImportError(err instanceof Error ? err.message : "Failed to import skill");
|
|
3711
|
+
} finally {
|
|
3712
|
+
setImporting(false);
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
async function handleUrlImport() {
|
|
3716
|
+
if (!url.trim()) return;
|
|
3717
|
+
setUrlImporting(true);
|
|
3718
|
+
setImportError("");
|
|
3719
|
+
try {
|
|
3720
|
+
const result = await client.skillsDirectory.import({ url: url.trim() });
|
|
3721
|
+
if (existingFolders.includes(result.folder)) {
|
|
3722
|
+
setImportError(`Skill folder "${result.folder}" already exists. Remove it first or rename.`);
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
onImported({ folder: result.folder, files: result.files });
|
|
3726
|
+
onOpenChange(false);
|
|
3727
|
+
resetState();
|
|
3728
|
+
} catch (err) {
|
|
3729
|
+
setImportError(err instanceof Error ? err.message : "Failed to import skill");
|
|
3730
|
+
} finally {
|
|
3731
|
+
setUrlImporting(false);
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
function resetState() {
|
|
3735
|
+
setTab("all");
|
|
3736
|
+
setSearch("");
|
|
3737
|
+
setSelected(null);
|
|
3738
|
+
setPreview("");
|
|
3739
|
+
setUrl("");
|
|
3740
|
+
setError("");
|
|
3741
|
+
setImportError("");
|
|
3742
|
+
}
|
|
3743
|
+
return /* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Dialog, { open, onOpenChange: (v) => {
|
|
3744
|
+
onOpenChange(v);
|
|
3745
|
+
if (!v) resetState();
|
|
3746
|
+
}, children: /* @__PURE__ */ jsxRuntime.jsxs(chunkXXF4U7WL_cjs.DialogContent, { className: "max-w-2xl max-h-[80vh] flex flex-col", children: [
|
|
3747
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkXXF4U7WL_cjs.DialogHeader, { children: [
|
|
3748
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.DialogTitle, { children: "Import from skills.sh" }),
|
|
3749
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.DialogDescription, { children: "Browse the open skills directory and import skills into this agent." })
|
|
3750
|
+
] }),
|
|
3751
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkXXF4U7WL_cjs.DialogBody, { className: "flex-1 overflow-hidden flex flex-col gap-4", children: [
|
|
3752
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
|
|
3753
|
+
/* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "All Time", active: tab === "all", onClick: () => setTab("all") }),
|
|
3754
|
+
/* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Trending", active: tab === "trending", onClick: () => setTab("trending") }),
|
|
3755
|
+
/* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Hot", active: tab === "hot", onClick: () => setTab("hot") })
|
|
3756
|
+
] }),
|
|
3757
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Input, { placeholder: "Search skills...", value: search, onChange: (e) => setSearch(e.target.value) }),
|
|
3758
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: error }),
|
|
3759
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-hidden flex gap-4 min-h-0", children: [
|
|
3760
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1/2 overflow-y-auto border border-muted-foreground/25 rounded-lg", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-sm text-muted-foreground", children: "Loading skills..." }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-sm text-muted-foreground", children: search ? "No skills match your search" : "No skills found" }) : filtered.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3761
|
+
"button",
|
|
3762
|
+
{
|
|
3763
|
+
onClick: () => handleSelect(entry),
|
|
3764
|
+
className: `w-full text-left px-3 py-2 border-b border-muted-foreground/10 hover:bg-muted/50 transition-colors ${selected?.skill === entry.skill && selected?.owner === entry.owner ? "bg-muted/50" : ""}`,
|
|
3765
|
+
children: [
|
|
3766
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-sm truncate", children: entry.name }),
|
|
3767
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3768
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground truncate", children: [
|
|
3769
|
+
entry.owner,
|
|
3770
|
+
"/",
|
|
3771
|
+
entry.repo
|
|
3772
|
+
] }),
|
|
3773
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground font-mono ml-2 shrink-0", children: entry.installs })
|
|
3774
|
+
] })
|
|
3775
|
+
]
|
|
3776
|
+
},
|
|
3777
|
+
`${entry.owner}/${entry.repo}/${entry.skill}`
|
|
3778
|
+
)) }),
|
|
3779
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1/2 overflow-y-auto border border-muted-foreground/25 rounded-lg p-3", children: !selected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-muted-foreground", children: "Select a skill to preview" }) : previewLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-muted-foreground", children: "Loading preview..." }) : /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs whitespace-pre-wrap break-words font-mono", children: preview }) })
|
|
3780
|
+
] }),
|
|
3781
|
+
importError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: importError }),
|
|
3782
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-muted-foreground/25 pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.FormField, { label: "Or paste a skills.sh URL", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3783
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3784
|
+
chunkXXF4U7WL_cjs.Input,
|
|
3785
|
+
{
|
|
3786
|
+
value: url,
|
|
3787
|
+
onChange: (e) => setUrl(e.target.value),
|
|
3788
|
+
placeholder: "skills.sh/owner/repo/skill",
|
|
3789
|
+
onKeyDown: (e) => e.key === "Enter" && handleUrlImport(),
|
|
3790
|
+
className: "flex-1"
|
|
3791
|
+
}
|
|
3792
|
+
),
|
|
3793
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Button, { size: "sm", variant: "outline", onClick: handleUrlImport, disabled: urlImporting || !url.trim(), children: urlImporting ? "Importing..." : "Import URL" })
|
|
3794
|
+
] }) }) })
|
|
3795
|
+
] }),
|
|
3796
|
+
/* @__PURE__ */ jsxRuntime.jsxs(chunkXXF4U7WL_cjs.DialogFooter, { children: [
|
|
3797
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
3798
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Button, { size: "sm", onClick: handleImport, disabled: !selected || importing || previewLoading, children: importing ? "Importing..." : "Import Selected" })
|
|
3799
|
+
] })
|
|
3800
|
+
] }) });
|
|
3801
|
+
}
|
|
3633
3802
|
function AgentSkillManager({ agentId, initialSkills, onSaved }) {
|
|
3634
3803
|
const client = chunkXXF4U7WL_cjs.useAgentPlaneClient();
|
|
3804
|
+
const [importOpen, setImportOpen] = React.useState(false);
|
|
3805
|
+
const [extraSkills, setExtraSkills] = React.useState([]);
|
|
3806
|
+
const allSkills = React.useMemo(() => [...initialSkills, ...extraSkills], [initialSkills, extraSkills]);
|
|
3635
3807
|
const initialFiles = React.useMemo(
|
|
3636
|
-
() =>
|
|
3808
|
+
() => allSkills.flatMap(
|
|
3637
3809
|
(s) => s.files.map((f) => ({
|
|
3638
3810
|
path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
|
|
3639
3811
|
content: f.content
|
|
3640
3812
|
}))
|
|
3641
3813
|
),
|
|
3642
|
-
[
|
|
3814
|
+
[allSkills]
|
|
3643
3815
|
);
|
|
3816
|
+
const existingFolders = React.useMemo(() => allSkills.map((s) => s.folder), [allSkills]);
|
|
3817
|
+
const handleImported = React.useCallback((skill) => {
|
|
3818
|
+
setExtraSkills((prev) => [...prev, skill]);
|
|
3819
|
+
}, []);
|
|
3644
3820
|
const handleSave = React.useCallback(async (files) => {
|
|
3645
3821
|
const folderMap = /* @__PURE__ */ new Map();
|
|
3646
3822
|
for (const file of files) {
|
|
@@ -3659,22 +3835,35 @@ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
|
|
|
3659
3835
|
}
|
|
3660
3836
|
const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
|
|
3661
3837
|
await client.agents.update(agentId, { skills });
|
|
3838
|
+
setExtraSkills([]);
|
|
3662
3839
|
onSaved?.();
|
|
3663
3840
|
}, [agentId, client, onSaved]);
|
|
3664
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3841
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3842
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(chunkXXF4U7WL_cjs.Button, { size: "sm", variant: "outline", onClick: () => setImportOpen(true), children: "Import from skills.sh" }) }),
|
|
3843
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3844
|
+
FileTreeEditor2,
|
|
3845
|
+
{
|
|
3846
|
+
initialFiles,
|
|
3847
|
+
onSave: handleSave,
|
|
3848
|
+
title: "Skills",
|
|
3849
|
+
saveLabel: "Save Skills",
|
|
3850
|
+
addFolderLabel: "Skill",
|
|
3851
|
+
newFileTemplate: {
|
|
3852
|
+
filename: "SKILL.md",
|
|
3853
|
+
content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
|
|
3854
|
+
}
|
|
3675
3855
|
}
|
|
3676
|
-
|
|
3677
|
-
|
|
3856
|
+
),
|
|
3857
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3858
|
+
ImportSkillDialog,
|
|
3859
|
+
{
|
|
3860
|
+
open: importOpen,
|
|
3861
|
+
onOpenChange: setImportOpen,
|
|
3862
|
+
onImported: handleImported,
|
|
3863
|
+
existingFolders
|
|
3864
|
+
}
|
|
3865
|
+
)
|
|
3866
|
+
] });
|
|
3678
3867
|
}
|
|
3679
3868
|
function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
|
|
3680
3869
|
const client = chunkXXF4U7WL_cjs.useAgentPlaneClient();
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,22 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
|
|
|
7
7
|
import { VariantProps } from 'class-variance-authority';
|
|
8
8
|
import { DailyAgentStat } from './charts.cjs';
|
|
9
9
|
|
|
10
|
+
/** Skills directory types. */
|
|
11
|
+
interface SkillDirectoryEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
skill: string;
|
|
16
|
+
installs: string;
|
|
17
|
+
}
|
|
18
|
+
interface ImportedSkillResult {
|
|
19
|
+
folder: string;
|
|
20
|
+
files: Array<{
|
|
21
|
+
path: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}>;
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
10
26
|
/** Minimal stream event types used by the playground UI. */
|
|
11
27
|
interface PlaygroundTextDeltaEvent {
|
|
12
28
|
type: "text_delta";
|
|
@@ -155,6 +171,17 @@ interface AgentPlaneClient {
|
|
|
155
171
|
toolkits(): Promise<unknown[]>;
|
|
156
172
|
tools(toolkit: string): Promise<unknown[]>;
|
|
157
173
|
};
|
|
174
|
+
skillsDirectory: {
|
|
175
|
+
list(tab?: "all" | "trending" | "hot"): Promise<SkillDirectoryEntry[]>;
|
|
176
|
+
preview(owner: string, repo: string, skill: string): Promise<string>;
|
|
177
|
+
import(params: {
|
|
178
|
+
owner: string;
|
|
179
|
+
repo: string;
|
|
180
|
+
skill_name: string;
|
|
181
|
+
} | {
|
|
182
|
+
url: string;
|
|
183
|
+
}): Promise<ImportedSkillResult>;
|
|
184
|
+
};
|
|
158
185
|
pluginMarketplaces: {
|
|
159
186
|
list(): Promise<unknown[]>;
|
|
160
187
|
get(marketplaceId: string): Promise<unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,22 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
|
|
|
7
7
|
import { VariantProps } from 'class-variance-authority';
|
|
8
8
|
import { DailyAgentStat } from './charts.js';
|
|
9
9
|
|
|
10
|
+
/** Skills directory types. */
|
|
11
|
+
interface SkillDirectoryEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
skill: string;
|
|
16
|
+
installs: string;
|
|
17
|
+
}
|
|
18
|
+
interface ImportedSkillResult {
|
|
19
|
+
folder: string;
|
|
20
|
+
files: Array<{
|
|
21
|
+
path: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}>;
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
10
26
|
/** Minimal stream event types used by the playground UI. */
|
|
11
27
|
interface PlaygroundTextDeltaEvent {
|
|
12
28
|
type: "text_delta";
|
|
@@ -155,6 +171,17 @@ interface AgentPlaneClient {
|
|
|
155
171
|
toolkits(): Promise<unknown[]>;
|
|
156
172
|
tools(toolkit: string): Promise<unknown[]>;
|
|
157
173
|
};
|
|
174
|
+
skillsDirectory: {
|
|
175
|
+
list(tab?: "all" | "trending" | "hot"): Promise<SkillDirectoryEntry[]>;
|
|
176
|
+
preview(owner: string, repo: string, skill: string): Promise<string>;
|
|
177
|
+
import(params: {
|
|
178
|
+
owner: string;
|
|
179
|
+
repo: string;
|
|
180
|
+
skill_name: string;
|
|
181
|
+
} | {
|
|
182
|
+
url: string;
|
|
183
|
+
}): Promise<ImportedSkillResult>;
|
|
184
|
+
};
|
|
158
185
|
pluginMarketplaces: {
|
|
159
186
|
list(): Promise<unknown[]>;
|
|
160
187
|
get(marketplaceId: string): Promise<unknown>;
|
package/dist/index.js
CHANGED
|
@@ -3606,17 +3606,193 @@ function FileTreeEditor2({
|
|
|
3606
3606
|
] })
|
|
3607
3607
|
] });
|
|
3608
3608
|
}
|
|
3609
|
+
function TabButton({ label, active, onClick }) {
|
|
3610
|
+
return /* @__PURE__ */ jsxs(
|
|
3611
|
+
"button",
|
|
3612
|
+
{
|
|
3613
|
+
onClick,
|
|
3614
|
+
className: `relative pb-2 text-sm font-medium transition-colors ${active ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
3615
|
+
children: [
|
|
3616
|
+
label,
|
|
3617
|
+
active && /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 h-0.5 bg-foreground rounded-full" })
|
|
3618
|
+
]
|
|
3619
|
+
}
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
function ImportSkillDialog({ open, onOpenChange, onImported, existingFolders }) {
|
|
3623
|
+
const client = useAgentPlaneClient();
|
|
3624
|
+
const [tab, setTab] = useState("all");
|
|
3625
|
+
const [entries, setEntries] = useState([]);
|
|
3626
|
+
const [loading, setLoading] = useState(false);
|
|
3627
|
+
const [error, setError] = useState("");
|
|
3628
|
+
const [search, setSearch] = useState("");
|
|
3629
|
+
const [selected, setSelected] = useState(null);
|
|
3630
|
+
const [preview, setPreview] = useState("");
|
|
3631
|
+
const [previewLoading, setPreviewLoading] = useState(false);
|
|
3632
|
+
const [importing, setImporting] = useState(false);
|
|
3633
|
+
const [importError, setImportError] = useState("");
|
|
3634
|
+
const [url, setUrl] = useState("");
|
|
3635
|
+
const [urlImporting, setUrlImporting] = useState(false);
|
|
3636
|
+
useEffect(() => {
|
|
3637
|
+
if (!open) return;
|
|
3638
|
+
setLoading(true);
|
|
3639
|
+
setError("");
|
|
3640
|
+
setSelected(null);
|
|
3641
|
+
setPreview("");
|
|
3642
|
+
client.skillsDirectory.list(tab).then((data) => setEntries(data)).catch((err) => setError(err instanceof Error ? err.message : "Failed to load skills")).finally(() => setLoading(false));
|
|
3643
|
+
}, [tab, open, client]);
|
|
3644
|
+
const filtered = useMemo(() => {
|
|
3645
|
+
if (!search.trim()) return entries;
|
|
3646
|
+
const q = search.toLowerCase();
|
|
3647
|
+
return entries.filter(
|
|
3648
|
+
(e) => e.name.toLowerCase().includes(q) || e.owner.toLowerCase().includes(q) || e.repo.toLowerCase().includes(q)
|
|
3649
|
+
);
|
|
3650
|
+
}, [entries, search]);
|
|
3651
|
+
async function handleSelect(entry) {
|
|
3652
|
+
setSelected(entry);
|
|
3653
|
+
setPreviewLoading(true);
|
|
3654
|
+
setPreview("");
|
|
3655
|
+
setImportError("");
|
|
3656
|
+
try {
|
|
3657
|
+
const content = await client.skillsDirectory.preview(entry.owner, entry.repo, entry.skill);
|
|
3658
|
+
setPreview(content);
|
|
3659
|
+
} catch (err) {
|
|
3660
|
+
setPreview(`Failed to load preview: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
3661
|
+
} finally {
|
|
3662
|
+
setPreviewLoading(false);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
async function handleImport() {
|
|
3666
|
+
if (!selected) return;
|
|
3667
|
+
setImporting(true);
|
|
3668
|
+
setImportError("");
|
|
3669
|
+
try {
|
|
3670
|
+
const result = await client.skillsDirectory.import({
|
|
3671
|
+
owner: selected.owner,
|
|
3672
|
+
repo: selected.repo,
|
|
3673
|
+
skill_name: selected.skill
|
|
3674
|
+
});
|
|
3675
|
+
if (existingFolders.includes(result.folder)) {
|
|
3676
|
+
setImportError(`Skill folder "${result.folder}" already exists. Remove it first or rename.`);
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
if (result.warnings.length > 0) {
|
|
3680
|
+
setImportError(`Imported with warnings: ${result.warnings.join("; ")}`);
|
|
3681
|
+
}
|
|
3682
|
+
onImported({ folder: result.folder, files: result.files });
|
|
3683
|
+
onOpenChange(false);
|
|
3684
|
+
resetState();
|
|
3685
|
+
} catch (err) {
|
|
3686
|
+
setImportError(err instanceof Error ? err.message : "Failed to import skill");
|
|
3687
|
+
} finally {
|
|
3688
|
+
setImporting(false);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
async function handleUrlImport() {
|
|
3692
|
+
if (!url.trim()) return;
|
|
3693
|
+
setUrlImporting(true);
|
|
3694
|
+
setImportError("");
|
|
3695
|
+
try {
|
|
3696
|
+
const result = await client.skillsDirectory.import({ url: url.trim() });
|
|
3697
|
+
if (existingFolders.includes(result.folder)) {
|
|
3698
|
+
setImportError(`Skill folder "${result.folder}" already exists. Remove it first or rename.`);
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
onImported({ folder: result.folder, files: result.files });
|
|
3702
|
+
onOpenChange(false);
|
|
3703
|
+
resetState();
|
|
3704
|
+
} catch (err) {
|
|
3705
|
+
setImportError(err instanceof Error ? err.message : "Failed to import skill");
|
|
3706
|
+
} finally {
|
|
3707
|
+
setUrlImporting(false);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
function resetState() {
|
|
3711
|
+
setTab("all");
|
|
3712
|
+
setSearch("");
|
|
3713
|
+
setSelected(null);
|
|
3714
|
+
setPreview("");
|
|
3715
|
+
setUrl("");
|
|
3716
|
+
setError("");
|
|
3717
|
+
setImportError("");
|
|
3718
|
+
}
|
|
3719
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (v) => {
|
|
3720
|
+
onOpenChange(v);
|
|
3721
|
+
if (!v) resetState();
|
|
3722
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl max-h-[80vh] flex flex-col", children: [
|
|
3723
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
3724
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Import from skills.sh" }),
|
|
3725
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: "Browse the open skills directory and import skills into this agent." })
|
|
3726
|
+
] }),
|
|
3727
|
+
/* @__PURE__ */ jsxs(DialogBody, { className: "flex-1 overflow-hidden flex flex-col gap-4", children: [
|
|
3728
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
|
|
3729
|
+
/* @__PURE__ */ jsx(TabButton, { label: "All Time", active: tab === "all", onClick: () => setTab("all") }),
|
|
3730
|
+
/* @__PURE__ */ jsx(TabButton, { label: "Trending", active: tab === "trending", onClick: () => setTab("trending") }),
|
|
3731
|
+
/* @__PURE__ */ jsx(TabButton, { label: "Hot", active: tab === "hot", onClick: () => setTab("hot") })
|
|
3732
|
+
] }),
|
|
3733
|
+
/* @__PURE__ */ jsx(Input, { placeholder: "Search skills...", value: search, onChange: (e) => setSearch(e.target.value) }),
|
|
3734
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
|
|
3735
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex gap-4 min-h-0", children: [
|
|
3736
|
+
/* @__PURE__ */ jsx("div", { className: "w-1/2 overflow-y-auto border border-muted-foreground/25 rounded-lg", children: loading ? /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-muted-foreground", children: "Loading skills..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-muted-foreground", children: search ? "No skills match your search" : "No skills found" }) : filtered.map((entry) => /* @__PURE__ */ jsxs(
|
|
3737
|
+
"button",
|
|
3738
|
+
{
|
|
3739
|
+
onClick: () => handleSelect(entry),
|
|
3740
|
+
className: `w-full text-left px-3 py-2 border-b border-muted-foreground/10 hover:bg-muted/50 transition-colors ${selected?.skill === entry.skill && selected?.owner === entry.owner ? "bg-muted/50" : ""}`,
|
|
3741
|
+
children: [
|
|
3742
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm truncate", children: entry.name }),
|
|
3743
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3744
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground truncate", children: [
|
|
3745
|
+
entry.owner,
|
|
3746
|
+
"/",
|
|
3747
|
+
entry.repo
|
|
3748
|
+
] }),
|
|
3749
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-mono ml-2 shrink-0", children: entry.installs })
|
|
3750
|
+
] })
|
|
3751
|
+
]
|
|
3752
|
+
},
|
|
3753
|
+
`${entry.owner}/${entry.repo}/${entry.skill}`
|
|
3754
|
+
)) }),
|
|
3755
|
+
/* @__PURE__ */ jsx("div", { className: "w-1/2 overflow-y-auto border border-muted-foreground/25 rounded-lg p-3", children: !selected ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Select a skill to preview" }) : previewLoading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading preview..." }) : /* @__PURE__ */ jsx("pre", { className: "text-xs whitespace-pre-wrap break-words font-mono", children: preview }) })
|
|
3756
|
+
] }),
|
|
3757
|
+
importError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: importError }),
|
|
3758
|
+
/* @__PURE__ */ jsx("div", { className: "border-t border-muted-foreground/25 pt-3", children: /* @__PURE__ */ jsx(FormField, { label: "Or paste a skills.sh URL", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3759
|
+
/* @__PURE__ */ jsx(
|
|
3760
|
+
Input,
|
|
3761
|
+
{
|
|
3762
|
+
value: url,
|
|
3763
|
+
onChange: (e) => setUrl(e.target.value),
|
|
3764
|
+
placeholder: "skills.sh/owner/repo/skill",
|
|
3765
|
+
onKeyDown: (e) => e.key === "Enter" && handleUrlImport(),
|
|
3766
|
+
className: "flex-1"
|
|
3767
|
+
}
|
|
3768
|
+
),
|
|
3769
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: handleUrlImport, disabled: urlImporting || !url.trim(), children: urlImporting ? "Importing..." : "Import URL" })
|
|
3770
|
+
] }) }) })
|
|
3771
|
+
] }),
|
|
3772
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
3773
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
3774
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleImport, disabled: !selected || importing || previewLoading, children: importing ? "Importing..." : "Import Selected" })
|
|
3775
|
+
] })
|
|
3776
|
+
] }) });
|
|
3777
|
+
}
|
|
3609
3778
|
function AgentSkillManager({ agentId, initialSkills, onSaved }) {
|
|
3610
3779
|
const client = useAgentPlaneClient();
|
|
3780
|
+
const [importOpen, setImportOpen] = useState(false);
|
|
3781
|
+
const [extraSkills, setExtraSkills] = useState([]);
|
|
3782
|
+
const allSkills = useMemo(() => [...initialSkills, ...extraSkills], [initialSkills, extraSkills]);
|
|
3611
3783
|
const initialFiles = useMemo(
|
|
3612
|
-
() =>
|
|
3784
|
+
() => allSkills.flatMap(
|
|
3613
3785
|
(s) => s.files.map((f) => ({
|
|
3614
3786
|
path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
|
|
3615
3787
|
content: f.content
|
|
3616
3788
|
}))
|
|
3617
3789
|
),
|
|
3618
|
-
[
|
|
3790
|
+
[allSkills]
|
|
3619
3791
|
);
|
|
3792
|
+
const existingFolders = useMemo(() => allSkills.map((s) => s.folder), [allSkills]);
|
|
3793
|
+
const handleImported = useCallback((skill) => {
|
|
3794
|
+
setExtraSkills((prev) => [...prev, skill]);
|
|
3795
|
+
}, []);
|
|
3620
3796
|
const handleSave = useCallback(async (files) => {
|
|
3621
3797
|
const folderMap = /* @__PURE__ */ new Map();
|
|
3622
3798
|
for (const file of files) {
|
|
@@ -3635,22 +3811,35 @@ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
|
|
|
3635
3811
|
}
|
|
3636
3812
|
const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
|
|
3637
3813
|
await client.agents.update(agentId, { skills });
|
|
3814
|
+
setExtraSkills([]);
|
|
3638
3815
|
onSaved?.();
|
|
3639
3816
|
}, [agentId, client, onSaved]);
|
|
3640
|
-
return /* @__PURE__ */
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3817
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
3818
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end mb-4", children: /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: () => setImportOpen(true), children: "Import from skills.sh" }) }),
|
|
3819
|
+
/* @__PURE__ */ jsx(
|
|
3820
|
+
FileTreeEditor2,
|
|
3821
|
+
{
|
|
3822
|
+
initialFiles,
|
|
3823
|
+
onSave: handleSave,
|
|
3824
|
+
title: "Skills",
|
|
3825
|
+
saveLabel: "Save Skills",
|
|
3826
|
+
addFolderLabel: "Skill",
|
|
3827
|
+
newFileTemplate: {
|
|
3828
|
+
filename: "SKILL.md",
|
|
3829
|
+
content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
|
|
3830
|
+
}
|
|
3651
3831
|
}
|
|
3652
|
-
|
|
3653
|
-
|
|
3832
|
+
),
|
|
3833
|
+
/* @__PURE__ */ jsx(
|
|
3834
|
+
ImportSkillDialog,
|
|
3835
|
+
{
|
|
3836
|
+
open: importOpen,
|
|
3837
|
+
onOpenChange: setImportOpen,
|
|
3838
|
+
onImported: handleImported,
|
|
3839
|
+
existingFolders
|
|
3840
|
+
}
|
|
3841
|
+
)
|
|
3842
|
+
] });
|
|
3654
3843
|
}
|
|
3655
3844
|
function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
|
|
3656
3845
|
const client = useAgentPlaneClient();
|