@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 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
- () => initialSkills.flatMap(
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
- [initialSkills]
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.jsx(
3665
- FileTreeEditor2,
3666
- {
3667
- initialFiles,
3668
- onSave: handleSave,
3669
- title: "Skills",
3670
- saveLabel: "Save Skills",
3671
- addFolderLabel: "Skill",
3672
- newFileTemplate: {
3673
- filename: "SKILL.md",
3674
- content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
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
- () => initialSkills.flatMap(
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
- [initialSkills]
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__ */ jsx(
3641
- FileTreeEditor2,
3642
- {
3643
- initialFiles,
3644
- onSave: handleSave,
3645
- title: "Skills",
3646
- saveLabel: "Save Skills",
3647
- addFolderLabel: "Skill",
3648
- newFileTemplate: {
3649
- filename: "SKILL.md",
3650
- content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getcatalystiq/agent-plane-ui",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Embeddable React component library for AgentPlane",
5
5
  "type": "module",
6
6
  "exports": {