@vertesia/tools-admin-ui 1.2.0 → 1.4.0-dev.20260615.042549Z

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 (77) hide show
  1. package/LICENSE +198 -10
  2. package/lib/AdminApp.d.ts +1 -1
  3. package/lib/AdminApp.d.ts.map +1 -1
  4. package/lib/components/AdminTopBar.d.ts +1 -1
  5. package/lib/components/AdminTopBar.d.ts.map +1 -1
  6. package/lib/components/CollectionCard.d.ts +1 -1
  7. package/lib/components/CollectionCard.d.ts.map +1 -1
  8. package/lib/components/DetailPage.d.ts +1 -1
  9. package/lib/components/DetailPage.d.ts.map +1 -1
  10. package/lib/components/EndpointPanel.d.ts +1 -1
  11. package/lib/components/EndpointPanel.d.ts.map +1 -1
  12. package/lib/components/HeroSection.d.ts +1 -1
  13. package/lib/components/HeroSection.d.ts.map +1 -1
  14. package/lib/components/ResourceCard.d.ts +1 -1
  15. package/lib/components/ResourceCard.d.ts.map +1 -1
  16. package/lib/components/ResourceSection.d.ts +1 -1
  17. package/lib/components/ResourceSection.d.ts.map +1 -1
  18. package/lib/components/SearchBar.d.ts +1 -1
  19. package/lib/components/SearchBar.d.ts.map +1 -1
  20. package/lib/components/SummaryBadge.d.ts +1 -1
  21. package/lib/components/SummaryBadge.d.ts.map +1 -1
  22. package/lib/hooks.d.ts +2 -2
  23. package/lib/hooks.d.ts.map +1 -1
  24. package/lib/index.d.ts +4 -4
  25. package/lib/index.d.ts.map +1 -1
  26. package/lib/pages/ActivityCollection.d.ts +1 -1
  27. package/lib/pages/ActivityCollection.d.ts.map +1 -1
  28. package/lib/pages/HomePage.d.ts +1 -1
  29. package/lib/pages/HomePage.d.ts.map +1 -1
  30. package/lib/pages/InteractionCollection.d.ts +1 -1
  31. package/lib/pages/InteractionCollection.d.ts.map +1 -1
  32. package/lib/pages/InteractionDetail.d.ts +1 -1
  33. package/lib/pages/InteractionDetail.d.ts.map +1 -1
  34. package/lib/pages/SkillCollection.d.ts +1 -1
  35. package/lib/pages/SkillCollection.d.ts.map +1 -1
  36. package/lib/pages/SkillDetail.d.ts +1 -1
  37. package/lib/pages/SkillDetail.d.ts.map +1 -1
  38. package/lib/pages/TemplateCollection.d.ts +1 -1
  39. package/lib/pages/TemplateCollection.d.ts.map +1 -1
  40. package/lib/pages/TemplateDetail.d.ts +1 -1
  41. package/lib/pages/TemplateDetail.d.ts.map +1 -1
  42. package/lib/pages/ToolCollection.d.ts +1 -1
  43. package/lib/pages/ToolCollection.d.ts.map +1 -1
  44. package/lib/pages/TypeCollection.d.ts +1 -1
  45. package/lib/pages/TypeCollection.d.ts.map +1 -1
  46. package/lib/pages/TypeDetail.d.ts +1 -1
  47. package/lib/pages/TypeDetail.d.ts.map +1 -1
  48. package/lib/tools-admin-ui.js +1484 -969
  49. package/lib/tools-admin-ui.js.map +1 -1
  50. package/lib/types.d.ts.map +1 -1
  51. package/package.json +16 -14
  52. package/src/AdminApp.tsx +14 -15
  53. package/src/components/AdminTopBar.tsx +7 -7
  54. package/src/components/CollectionCard.tsx +3 -1
  55. package/src/components/DetailPage.tsx +10 -4
  56. package/src/components/EndpointPanel.tsx +2 -9
  57. package/src/components/HeroSection.tsx +12 -3
  58. package/src/components/ResourceCard.tsx +8 -6
  59. package/src/components/ResourceSection.tsx +1 -1
  60. package/src/components/SearchBar.tsx +1 -5
  61. package/src/components/SummaryBadge.tsx +2 -1
  62. package/src/dev/env.ts +8 -8
  63. package/src/dev/main.tsx +20 -15
  64. package/src/hooks.ts +19 -12
  65. package/src/index.ts +4 -4
  66. package/src/pages/ActivityCollection.tsx +30 -12
  67. package/src/pages/HomePage.tsx +65 -57
  68. package/src/pages/InteractionCollection.tsx +26 -11
  69. package/src/pages/InteractionDetail.tsx +28 -13
  70. package/src/pages/SkillCollection.tsx +38 -18
  71. package/src/pages/SkillDetail.tsx +37 -13
  72. package/src/pages/TemplateCollection.tsx +28 -17
  73. package/src/pages/TemplateDetail.tsx +13 -7
  74. package/src/pages/ToolCollection.tsx +21 -10
  75. package/src/pages/TypeCollection.tsx +26 -17
  76. package/src/pages/TypeDetail.tsx +12 -6
  77. package/src/types.ts +16 -35
@@ -13,7 +13,11 @@ const sections: { type: ResourceType; title: string; subtitle: string }[] = [
13
13
  { type: 'skill', title: 'Skills', subtitle: 'Reusable instructions and scripts packaged as tools.' },
14
14
  { type: 'interaction', title: 'Interactions', subtitle: 'Conversation blueprints surfaced in the Vertesia UI.' },
15
15
  { type: 'type', title: 'Content Types', subtitle: 'Schema definitions for structured content in the data store.' },
16
- { type: 'template', title: 'Rendering Templates', subtitle: 'Document and presentation templates for content generation.' },
16
+ {
17
+ type: 'template',
18
+ title: 'Rendering Templates',
19
+ subtitle: 'Document and presentation templates for content generation.',
20
+ },
17
21
  { type: 'mcp', title: 'MCP Providers', subtitle: 'Remote MCP servers available through this tools server.' },
18
22
  ];
19
23
 
@@ -21,10 +25,7 @@ export function HomePage() {
21
25
  const { serverInfo, collections, resources } = useAdminContext();
22
26
  const [search, setSearch] = useState('');
23
27
 
24
- const filtered = useMemo(() =>
25
- filterResources(resources, search),
26
- [resources, search]
27
- );
28
+ const filtered = useMemo(() => filterResources(resources, search), [resources, search]);
28
29
 
29
30
  const isSearching = search.trim().length > 0;
30
31
 
@@ -44,60 +45,67 @@ export function HomePage() {
44
45
  totalCount={resources.length}
45
46
  />
46
47
 
47
- {isSearching ? (
48
- sections.map((section, i) => {
49
- const sectionItems = filtered.filter(r => r.type === section.type);
50
- return (
51
- <ResourceSection
52
- key={section.type}
53
- title={section.title}
54
- subtitle={section.subtitle}
55
- resources={sectionItems}
56
- showDivider={i > 0}
57
- />
58
- );
59
- })
60
- ) : (
61
- sections.map((section, i) => {
62
- const sectionCollections = collections.filter(c => c.type === section.type);
63
- const mcpResources = section.type === 'mcp'
64
- ? resources.filter(r => r.type === 'mcp')
65
- : [];
48
+ {isSearching
49
+ ? sections.map((section, i) => {
50
+ const sectionItems = filtered.filter((r) => r.type === section.type);
51
+ return (
52
+ <ResourceSection
53
+ key={section.type}
54
+ title={section.title}
55
+ subtitle={section.subtitle}
56
+ resources={sectionItems}
57
+ showDivider={i > 0}
58
+ />
59
+ );
60
+ })
61
+ : sections.map((section, i) => {
62
+ const sectionCollections = collections.filter((c) => c.type === section.type);
63
+ const mcpResources = section.type === 'mcp' ? resources.filter((r) => r.type === 'mcp') : [];
66
64
 
67
- if (sectionCollections.length === 0 && mcpResources.length === 0) return null;
65
+ if (sectionCollections.length === 0 && mcpResources.length === 0) return null;
68
66
 
69
- return (
70
- <section key={section.type}>
71
- {i > 0 && <Separator className="my-8" />}
72
- <div>
73
- <h2 className="text-xl font-semibold text-foreground">
74
- {section.title}
75
- <span className="ml-2 text-sm font-normal text-muted-foreground">
76
- ({sectionCollections.length}{sectionCollections.length === 1
77
- ? ' collection' : ' collections'})
78
- </span>
79
- </h2>
80
- <p className="mb-4 text-sm text-muted-foreground">{section.subtitle}</p>
81
- </div>
82
- <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
83
- {sectionCollections.map(col => (
84
- <CollectionCard key={`${col.type}:${col.name}`} collection={col} />
85
- ))}
86
- {mcpResources.map(r => (
87
- <div key={r.name} className="rounded-xl border border-border bg-card p-5 shadow-sm">
88
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.mcp}`}>
89
- mcp
90
- </span>
91
- <div className="font-semibold text-card-foreground">{r.title}</div>
92
- <div className="mt-1 text-sm text-muted-foreground">{r.description || 'No description'}</div>
93
- {r.url && <div className="mt-2 truncate font-mono text-xs text-muted-foreground">{r.url}</div>}
94
- </div>
95
- ))}
96
- </div>
97
- </section>
98
- );
99
- })
100
- )}
67
+ return (
68
+ <section key={section.type}>
69
+ {i > 0 && <Separator className="my-8" />}
70
+ <div>
71
+ <h2 className="text-xl font-semibold text-foreground">
72
+ {section.title}
73
+ <span className="ml-2 text-sm font-normal text-muted-foreground">
74
+ ({sectionCollections.length}
75
+ {sectionCollections.length === 1 ? ' collection' : ' collections'})
76
+ </span>
77
+ </h2>
78
+ <p className="mb-4 text-sm text-muted-foreground">{section.subtitle}</p>
79
+ </div>
80
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
81
+ {sectionCollections.map((col) => (
82
+ <CollectionCard key={`${col.type}:${col.name}`} collection={col} />
83
+ ))}
84
+ {mcpResources.map((r) => (
85
+ <div
86
+ key={r.name}
87
+ className="rounded-xl border border-border bg-card p-5 shadow-sm"
88
+ >
89
+ <span
90
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.mcp}`}
91
+ >
92
+ mcp
93
+ </span>
94
+ <div className="font-semibold text-card-foreground">{r.title}</div>
95
+ <div className="mt-1 text-sm text-muted-foreground">
96
+ {r.description || 'No description'}
97
+ </div>
98
+ {r.url && (
99
+ <div className="mt-2 truncate font-mono text-xs text-muted-foreground">
100
+ {r.url}
101
+ </div>
102
+ )}
103
+ </div>
104
+ ))}
105
+ </div>
106
+ </section>
107
+ );
108
+ })}
101
109
  </div>
102
110
  );
103
111
  }
@@ -11,11 +11,12 @@ export function InteractionCollection() {
11
11
  const { baseUrl } = useAdminContext();
12
12
 
13
13
  const { data: interactions, error } = useFetch<CatalogInteractionRef[]>(
14
- () => fetch(`${baseUrl}/interactions/${collection}`).then(r => {
15
- if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
16
- return r.json();
17
- }),
18
- [baseUrl, collection]
14
+ () =>
15
+ fetch(`${baseUrl}/interactions/${collection}`).then((r) => {
16
+ if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
17
+ return r.json();
18
+ }),
19
+ [baseUrl, collection],
19
20
  );
20
21
 
21
22
  if (error) {
@@ -23,7 +24,11 @@ export function InteractionCollection() {
23
24
  }
24
25
 
25
26
  if (!interactions) {
26
- return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
27
+ return (
28
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
29
+ <Spinner />
30
+ </div>
31
+ );
27
32
  }
28
33
 
29
34
  return (
@@ -33,18 +38,28 @@ export function InteractionCollection() {
33
38
  description={`${interactions.length} interaction${interactions.length !== 1 ? 's' : ''} in this collection.`}
34
39
  >
35
40
  <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
36
- {interactions.map(inter => (
37
- <NavLink key={inter.id} href={`/interactions/${collection}/${inter.name}`} className="block no-underline">
41
+ {interactions.map((inter) => (
42
+ <NavLink
43
+ key={inter.id}
44
+ href={`/interactions/${collection}/${inter.name}`}
45
+ className="block no-underline"
46
+ >
38
47
  <Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
39
48
  <CardContent className="p-5">
40
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.interaction}`}>
49
+ <span
50
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.interaction}`}
51
+ >
41
52
  interaction
42
53
  </span>
43
54
  <div className="font-semibold text-card-foreground">{inter.title || inter.name}</div>
44
- <div className="mt-1 text-sm text-muted-foreground">{inter.description || 'No description'}</div>
55
+ <div className="mt-1 text-sm text-muted-foreground">
56
+ {inter.description || 'No description'}
57
+ </div>
45
58
  {inter.tags && inter.tags.length > 0 && (
46
59
  <div className="mt-2 flex flex-wrap gap-1">
47
- {inter.tags.map(tag => <Badge key={tag}>{tag}</Badge>)}
60
+ {inter.tags.map((tag) => (
61
+ <Badge key={tag}>{tag}</Badge>
62
+ ))}
48
63
  </div>
49
64
  )}
50
65
  </CardContent>
@@ -17,15 +17,21 @@ export function InteractionDetail() {
17
17
  const { baseUrl } = useAdminContext();
18
18
 
19
19
  const { data: interaction, error } = useFetch<InteractionResponse>(
20
- () => client.getRawJWT().then(token => fetch(`${baseUrl}/interactions/${collection}/${name}`, {
21
- headers: {
22
- Authorization: `Bearer ${token}`,
23
- },
24
- })).then(r => {
25
- if (!r.ok) throw new Error(`Failed to load interaction: ${r.statusText}`);
26
- return r.json();
27
- }),
28
- [baseUrl, collection, name]
20
+ () =>
21
+ client
22
+ .getRawJWT()
23
+ .then((token) =>
24
+ fetch(`${baseUrl}/interactions/${collection}/${name}`, {
25
+ headers: {
26
+ Authorization: `Bearer ${token}`,
27
+ },
28
+ }),
29
+ )
30
+ .then((r) => {
31
+ if (!r.ok) throw new Error(`Failed to load interaction: ${r.statusText}`);
32
+ return r.json();
33
+ }),
34
+ [baseUrl, collection, name],
29
35
  );
30
36
 
31
37
  if (error) {
@@ -33,11 +39,16 @@ export function InteractionDetail() {
33
39
  }
34
40
 
35
41
  if (!interaction) {
36
- return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
42
+ return (
43
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
44
+ <Spinner />
45
+ </div>
46
+ );
37
47
  }
38
48
 
39
49
  const { agent_runner_options } = interaction;
40
- const hasAgentFlags = agent_runner_options &&
50
+ const hasAgentFlags =
51
+ agent_runner_options &&
41
52
  (agent_runner_options.is_agent || agent_runner_options.is_tool || agent_runner_options.is_skill);
42
53
 
43
54
  return (
@@ -55,14 +66,18 @@ export function InteractionDetail() {
55
66
  <Card key={`${prompt.role}-${prompt.name ?? ''}`} className="mb-3">
56
67
  <CardContent className="p-4">
57
68
  <div className="mb-2 flex items-center gap-2">
58
- <span className={`rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${ROLE_VARIANTS[prompt.role] ?? ''}`}>
69
+ <span
70
+ className={`rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${ROLE_VARIANTS[prompt.role] ?? ''}`}
71
+ >
59
72
  {prompt.role}
60
73
  </span>
61
74
  {prompt.name && (
62
75
  <span className="text-sm italic text-muted-foreground">{prompt.name}</span>
63
76
  )}
64
77
  </div>
65
- <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">{prompt.content}</pre>
78
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
79
+ {prompt.content}
80
+ </pre>
66
81
  </CardContent>
67
82
  </Card>
68
83
  ))}
@@ -9,7 +9,7 @@ import { TYPE_VARIANTS } from '../components/typeVariants.js';
9
9
  interface SkillToolDef {
10
10
  name: string;
11
11
  description?: string;
12
- related_tools?: string[];
12
+ tools?: string[];
13
13
  }
14
14
 
15
15
  interface SkillCollectionResponse {
@@ -38,16 +38,17 @@ export function SkillCollection() {
38
38
  const { baseUrl } = useAdminContext();
39
39
 
40
40
  const { data, error } = useFetch<SkillCollectionResponse>(
41
- () => fetch(`${baseUrl}/skills/${collection}`).then(r => {
42
- if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
43
- return r.json();
44
- }),
45
- [baseUrl, collection]
41
+ () =>
42
+ fetch(`${baseUrl}/skills/${collection}`).then((r) => {
43
+ if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
44
+ return r.json();
45
+ }),
46
+ [baseUrl, collection],
46
47
  );
47
48
 
48
49
  const { data: widgetsData } = useFetch<WidgetsResponse>(
49
- () => fetch(`${baseUrl}/package?scope=widgets`).then(r => r.ok ? r.json() : { widgets: {} }),
50
- [baseUrl]
50
+ () => fetch(`${baseUrl}/package?scope=widgets`).then((r) => (r.ok ? r.json() : { widgets: {} })),
51
+ [baseUrl],
51
52
  );
52
53
 
53
54
  const collectionWidgets = useMemo(() => {
@@ -57,20 +58,29 @@ export function SkillCollection() {
57
58
  .map(([name, w]) => ({ name, ...w }));
58
59
  }, [widgetsData, collection]);
59
60
 
60
- if (error) return <div className="p-6 text-destructive">Failed to load skill collection &ldquo;{collection}&rdquo;.</div>;
61
- if (!data) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
61
+ if (error)
62
+ return <div className="p-6 text-destructive">Failed to load skill collection &ldquo;{collection}&rdquo;.</div>;
63
+ if (!data)
64
+ return (
65
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
66
+ <Spinner />
67
+ </div>
68
+ );
62
69
 
63
70
  return (
64
71
  <DetailPage
65
72
  type="skill"
66
73
  title={data.title || collection}
67
- description={data.description || `${data.tools.length} skill${data.tools.length !== 1 ? 's' : ''} in this collection.`}
74
+ description={
75
+ data.description ||
76
+ `${data.tools.length} skill${data.tools.length !== 1 ? 's' : ''} in this collection.`
77
+ }
68
78
  >
69
79
  {collectionWidgets.length > 0 && (
70
80
  <div className="mb-8">
71
81
  <h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
72
82
  <div className="flex flex-wrap gap-2">
73
- {collectionWidgets.map(w => (
83
+ {collectionWidgets.map((w) => (
74
84
  <Badge key={w.name} variant="success">
75
85
  {w.name}
76
86
  <span className="ml-2 font-mono text-xs opacity-70">(skill: {w.skill})</span>
@@ -81,20 +91,30 @@ export function SkillCollection() {
81
91
  )}
82
92
 
83
93
  <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
84
- {data.tools.map(skill => {
94
+ {data.tools.map((skill) => {
85
95
  const displayName = skillDisplayName(skill.name);
86
96
  return (
87
- <NavLink key={skill.name} href={`/skills/${collection}/${displayName}`} className="block no-underline">
97
+ <NavLink
98
+ key={skill.name}
99
+ href={`/skills/${collection}/${displayName}`}
100
+ className="block no-underline"
101
+ >
88
102
  <Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
89
103
  <CardContent className="p-5">
90
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.skill}`}>
104
+ <span
105
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.skill}`}
106
+ >
91
107
  skill
92
108
  </span>
93
109
  <div className="font-semibold text-card-foreground">{displayName}</div>
94
- <div className="mt-1 text-sm text-muted-foreground">{skill.description || 'No description'}</div>
95
- {skill.related_tools && skill.related_tools.length > 0 && (
110
+ <div className="mt-1 text-sm text-muted-foreground">
111
+ {skill.description || 'No description'}
112
+ </div>
113
+ {skill.tools && skill.tools.length > 0 && (
96
114
  <div className="mt-2 flex flex-wrap gap-1">
97
- {skill.related_tools.map(t => <Badge key={t}>{t}</Badge>)}
115
+ {skill.tools.map((t) => (
116
+ <Badge key={t}>{t}</Badge>
117
+ ))}
98
118
  </div>
99
119
  )}
100
120
  </CardContent>
@@ -11,7 +11,7 @@ interface SkillDefinitionResponse {
11
11
  instructions: string;
12
12
  content_type: 'md' | 'jst';
13
13
  input_schema?: Record<string, unknown>;
14
- related_tools?: string[];
14
+ tools?: string[];
15
15
  execution?: {
16
16
  language: string;
17
17
  packages?: string[];
@@ -27,15 +27,21 @@ export function SkillDetail() {
27
27
  const { baseUrl } = useAdminContext();
28
28
 
29
29
  const { data: skill, error } = useFetch<SkillDefinitionResponse>(
30
- () => fetch(`${baseUrl}/skills/${collection}/${name}`).then(r => {
31
- if (!r.ok) throw new Error(`Failed to load skill: ${r.statusText}`);
32
- return r.json();
33
- }),
34
- [baseUrl, collection, name]
30
+ () =>
31
+ fetch(`${baseUrl}/skills/${collection}/${name}`).then((r) => {
32
+ if (!r.ok) throw new Error(`Failed to load skill: ${r.statusText}`);
33
+ return r.json();
34
+ }),
35
+ [baseUrl, collection, name],
35
36
  );
36
37
 
37
38
  if (error) return <div className="p-6 text-destructive">Failed to load skill &ldquo;{name}&rdquo;.</div>;
38
- if (!skill) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
39
+ if (!skill)
40
+ return (
41
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
42
+ <Spinner />
43
+ </div>
44
+ );
39
45
 
40
46
  return (
41
47
  <DetailPage
@@ -48,7 +54,11 @@ export function SkillDetail() {
48
54
  <div className="mb-8">
49
55
  <h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
50
56
  <div className="flex flex-wrap gap-2">
51
- {skill.widgets.map(w => <Badge key={w} variant="success">{w}</Badge>)}
57
+ {skill.widgets.map((w) => (
58
+ <Badge key={w} variant="success">
59
+ {w}
60
+ </Badge>
61
+ ))}
52
62
  </div>
53
63
  </div>
54
64
  )}
@@ -57,16 +67,24 @@ export function SkillDetail() {
57
67
  <div className="mb-8">
58
68
  <h2 className="mb-3 text-lg font-semibold text-foreground">Scripts</h2>
59
69
  <div className="flex flex-wrap gap-2">
60
- {skill.scripts.map(s => <Badge key={s} variant="success">{s}</Badge>)}
70
+ {skill.scripts.map((s) => (
71
+ <Badge key={s} variant="success">
72
+ {s}
73
+ </Badge>
74
+ ))}
61
75
  </div>
62
76
  </div>
63
77
  )}
64
78
 
65
- {skill.related_tools && skill.related_tools.length > 0 && (
79
+ {skill.tools && skill.tools.length > 0 && (
66
80
  <div className="mb-8">
67
81
  <h2 className="mb-3 text-lg font-semibold text-foreground">Related Tools</h2>
68
82
  <div className="flex flex-wrap gap-2">
69
- {skill.related_tools.map(t => <Badge key={t} variant="success">{t}</Badge>)}
83
+ {skill.tools.map((t) => (
84
+ <Badge key={t} variant="success">
85
+ {t}
86
+ </Badge>
87
+ ))}
70
88
  </div>
71
89
  </div>
72
90
  )}
@@ -76,7 +94,11 @@ export function SkillDetail() {
76
94
  <h2 className="mb-3 text-lg font-semibold text-foreground">Execution</h2>
77
95
  <div className="flex flex-wrap gap-2">
78
96
  <Badge variant="success">{skill.execution.language}</Badge>
79
- {skill.execution.packages?.map(p => <Badge key={p} variant="success">{p}</Badge>)}
97
+ {skill.execution.packages?.map((p) => (
98
+ <Badge key={p} variant="success">
99
+ {p}
100
+ </Badge>
101
+ ))}
80
102
  </div>
81
103
  </div>
82
104
  )}
@@ -86,7 +108,9 @@ export function SkillDetail() {
86
108
  Instructions
87
109
  {skill.content_type === 'jst' && <Badge className="ml-2">JST template</Badge>}
88
110
  </h2>
89
- <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">{skill.instructions}</pre>
111
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
112
+ {skill.instructions}
113
+ </pre>
90
114
  </div>
91
115
 
92
116
  {skill.input_schema && (
@@ -11,15 +11,24 @@ export function TemplateCollection() {
11
11
  const { baseUrl } = useAdminContext();
12
12
 
13
13
  const { data: templates, error } = useFetch<RenderingTemplateDefinitionRef[]>(
14
- () => fetch(`${baseUrl}/templates/${collection}`).then(r => {
15
- if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
16
- return r.json();
17
- }),
18
- [baseUrl, collection]
14
+ () =>
15
+ fetch(`${baseUrl}/templates/${collection}`).then((r) => {
16
+ if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
17
+ return r.json();
18
+ }),
19
+ [baseUrl, collection],
19
20
  );
20
21
 
21
- if (error) return <div className="p-6 text-destructive">Failed to load template collection &ldquo;{collection}&rdquo;.</div>;
22
- if (!templates) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
22
+ if (error)
23
+ return (
24
+ <div className="p-6 text-destructive">Failed to load template collection &ldquo;{collection}&rdquo;.</div>
25
+ );
26
+ if (!templates)
27
+ return (
28
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
29
+ <Spinner />
30
+ </div>
31
+ );
23
32
 
24
33
  return (
25
34
  <DetailPage
@@ -28,23 +37,25 @@ export function TemplateCollection() {
28
37
  description={`${templates.length} template${templates.length !== 1 ? 's' : ''} in this collection.`}
29
38
  >
30
39
  <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
31
- {templates.map(tmpl => (
32
- <NavLink
33
- key={tmpl.name}
34
- href={`/templates/${collection}/${tmpl.name}`}
35
- className="no-underline"
36
- >
40
+ {templates.map((tmpl) => (
41
+ <NavLink key={tmpl.name} href={`/templates/${collection}/${tmpl.name}`} className="no-underline">
37
42
  <Card className="h-full transition-all hover:-translate-y-0.5 hover:shadow-md">
38
43
  <CardContent className="p-5">
39
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.template}`}>
44
+ <span
45
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.template}`}
46
+ >
40
47
  {tmpl.type || 'template'}
41
48
  </span>
42
49
  <div className="font-semibold text-card-foreground">{tmpl.title || tmpl.name}</div>
43
- <div className="mt-1 text-sm text-muted-foreground">{tmpl.description || 'No description'}</div>
50
+ <div className="mt-1 text-sm text-muted-foreground">
51
+ {tmpl.description || 'No description'}
52
+ </div>
44
53
  {tmpl.tags && tmpl.tags.length > 0 && (
45
54
  <div className="mt-3 flex flex-wrap gap-1.5">
46
- {tmpl.tags.map(tag => (
47
- <Badge key={tag} variant="default">{tag}</Badge>
55
+ {tmpl.tags.map((tag) => (
56
+ <Badge key={tag} variant="default">
57
+ {tag}
58
+ </Badge>
48
59
  ))}
49
60
  </div>
50
61
  )}
@@ -21,15 +21,21 @@ export function TemplateDetail() {
21
21
  const { baseUrl } = useAdminContext();
22
22
 
23
23
  const { data: template, error } = useFetch<TemplateDefinitionResponse>(
24
- () => fetch(`${baseUrl}/templates/${collection}/${name}`).then(r => {
25
- if (!r.ok) throw new Error(`Failed to load template: ${r.statusText}`);
26
- return r.json();
27
- }),
28
- [baseUrl, collection, name]
24
+ () =>
25
+ fetch(`${baseUrl}/templates/${collection}/${name}`).then((r) => {
26
+ if (!r.ok) throw new Error(`Failed to load template: ${r.statusText}`);
27
+ return r.json();
28
+ }),
29
+ [baseUrl, collection, name],
29
30
  );
30
31
 
31
32
  if (error) return <div className="p-6 text-destructive">Failed to load template &ldquo;{name}&rdquo;.</div>;
32
- if (!template) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
33
+ if (!template)
34
+ return (
35
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
36
+ <Spinner />
37
+ </div>
38
+ );
33
39
 
34
40
  return (
35
41
  <DetailPage
@@ -49,7 +55,7 @@ export function TemplateDetail() {
49
55
  <div className="mb-8">
50
56
  <h2 className="mb-3 text-lg font-semibold text-foreground">Assets</h2>
51
57
  <div className="flex flex-wrap gap-2">
52
- {template.assets.map(asset => (
58
+ {template.assets.map((asset) => (
53
59
  <Badge key={asset} variant="success">
54
60
  {asset.split('/').pop()}
55
61
  </Badge>