@vertesia/tools-sdk 0.81.1 → 1.0.0-dev.20260203.130115Z

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 (101) hide show
  1. package/package.json +9 -8
  2. package/src/ContentTypesCollection.ts +51 -0
  3. package/src/InteractionCollection.ts +1 -94
  4. package/src/SkillCollection.ts +88 -15
  5. package/src/ToolCollection.ts +65 -17
  6. package/src/ToolRegistry.ts +101 -9
  7. package/src/auth.ts +37 -6
  8. package/src/index.ts +7 -3
  9. package/src/server/app-package.ts +102 -0
  10. package/src/server/conyent-types.ts +71 -0
  11. package/src/server/interactions.ts +100 -0
  12. package/src/server/mcp.ts +51 -0
  13. package/src/server/site.ts +53 -0
  14. package/src/server/skills.ts +133 -0
  15. package/src/server/tools.ts +128 -0
  16. package/src/server/types.ts +87 -0
  17. package/src/server/widgets.ts +38 -0
  18. package/src/server.ts +80 -359
  19. package/src/site/styles.ts +71 -0
  20. package/src/site/templates.ts +215 -119
  21. package/src/types.ts +22 -18
  22. package/src/utils.ts +20 -0
  23. package/lib/cjs/InteractionCollection.js +0 -164
  24. package/lib/cjs/InteractionCollection.js.map +0 -1
  25. package/lib/cjs/SkillCollection.js +0 -318
  26. package/lib/cjs/SkillCollection.js.map +0 -1
  27. package/lib/cjs/ToolCollection.js +0 -192
  28. package/lib/cjs/ToolCollection.js.map +0 -1
  29. package/lib/cjs/ToolRegistry.js +0 -44
  30. package/lib/cjs/ToolRegistry.js.map +0 -1
  31. package/lib/cjs/auth.js +0 -89
  32. package/lib/cjs/auth.js.map +0 -1
  33. package/lib/cjs/build/validate.js +0 -7
  34. package/lib/cjs/build/validate.js.map +0 -1
  35. package/lib/cjs/copy-assets.js +0 -84
  36. package/lib/cjs/copy-assets.js.map +0 -1
  37. package/lib/cjs/index.js +0 -30
  38. package/lib/cjs/index.js.map +0 -1
  39. package/lib/cjs/package.json +0 -3
  40. package/lib/cjs/server.js +0 -327
  41. package/lib/cjs/server.js.map +0 -1
  42. package/lib/cjs/site/styles.js +0 -621
  43. package/lib/cjs/site/styles.js.map +0 -1
  44. package/lib/cjs/site/templates.js +0 -932
  45. package/lib/cjs/site/templates.js.map +0 -1
  46. package/lib/cjs/types.js +0 -3
  47. package/lib/cjs/types.js.map +0 -1
  48. package/lib/cjs/utils.js +0 -7
  49. package/lib/cjs/utils.js.map +0 -1
  50. package/lib/esm/InteractionCollection.js +0 -125
  51. package/lib/esm/InteractionCollection.js.map +0 -1
  52. package/lib/esm/SkillCollection.js +0 -311
  53. package/lib/esm/SkillCollection.js.map +0 -1
  54. package/lib/esm/ToolCollection.js +0 -154
  55. package/lib/esm/ToolCollection.js.map +0 -1
  56. package/lib/esm/ToolRegistry.js +0 -39
  57. package/lib/esm/ToolRegistry.js.map +0 -1
  58. package/lib/esm/auth.js +0 -82
  59. package/lib/esm/auth.js.map +0 -1
  60. package/lib/esm/build/validate.js +0 -4
  61. package/lib/esm/build/validate.js.map +0 -1
  62. package/lib/esm/copy-assets.js +0 -81
  63. package/lib/esm/copy-assets.js.map +0 -1
  64. package/lib/esm/index.js +0 -10
  65. package/lib/esm/index.js.map +0 -1
  66. package/lib/esm/server.js +0 -323
  67. package/lib/esm/server.js.map +0 -1
  68. package/lib/esm/site/styles.js +0 -618
  69. package/lib/esm/site/styles.js.map +0 -1
  70. package/lib/esm/site/templates.js +0 -920
  71. package/lib/esm/site/templates.js.map +0 -1
  72. package/lib/esm/types.js +0 -2
  73. package/lib/esm/types.js.map +0 -1
  74. package/lib/esm/utils.js +0 -4
  75. package/lib/esm/utils.js.map +0 -1
  76. package/lib/types/InteractionCollection.d.ts +0 -48
  77. package/lib/types/InteractionCollection.d.ts.map +0 -1
  78. package/lib/types/SkillCollection.d.ts +0 -111
  79. package/lib/types/SkillCollection.d.ts.map +0 -1
  80. package/lib/types/ToolCollection.d.ts +0 -61
  81. package/lib/types/ToolCollection.d.ts.map +0 -1
  82. package/lib/types/ToolRegistry.d.ts +0 -15
  83. package/lib/types/ToolRegistry.d.ts.map +0 -1
  84. package/lib/types/auth.d.ts +0 -20
  85. package/lib/types/auth.d.ts.map +0 -1
  86. package/lib/types/build/validate.d.ts +0 -2
  87. package/lib/types/build/validate.d.ts.map +0 -1
  88. package/lib/types/copy-assets.d.ts +0 -14
  89. package/lib/types/copy-assets.d.ts.map +0 -1
  90. package/lib/types/index.d.ts +0 -10
  91. package/lib/types/index.d.ts.map +0 -1
  92. package/lib/types/server.d.ts +0 -72
  93. package/lib/types/server.d.ts.map +0 -1
  94. package/lib/types/site/styles.d.ts +0 -5
  95. package/lib/types/site/styles.d.ts.map +0 -1
  96. package/lib/types/site/templates.d.ts +0 -54
  97. package/lib/types/site/templates.d.ts.map +0 -1
  98. package/lib/types/types.d.ts +0 -262
  99. package/lib/types/types.d.ts.map +0 -1
  100. package/lib/types/utils.d.ts +0 -2
  101. package/lib/types/utils.d.ts.map +0 -1
@@ -1,12 +1,15 @@
1
1
  import type { InteractionCollection } from "../InteractionCollection.js";
2
+ import { ToolServerConfig } from "../server/types.js";
2
3
  import type { SkillCollection } from "../SkillCollection.js";
3
4
  import type { ToolCollection } from "../ToolCollection.js";
5
+ import type { ContentTypesCollection } from "../ContentTypesCollection.js";
4
6
  import type { ICollection, SkillDefinition, Tool } from "../types.js";
7
+ import { join } from "../utils.js";
5
8
  import { baseStyles } from "./styles.js";
6
9
 
7
10
  type MCPProviderMeta = {
8
- name: string;
9
- description?: string;
11
+ name: string;
12
+ description?: string;
10
13
  };
11
14
 
12
15
  /**
@@ -228,43 +231,6 @@ ${baseStyles}
228
231
  white-space: pre-wrap;
229
232
  }
230
233
 
231
- .endpoint-box {
232
- display: flex;
233
- align-items: center;
234
- gap: 0.75rem;
235
- background: #f3f4f6;
236
- padding: 0.75rem 1rem;
237
- border-radius: 8px;
238
- margin-top: 0.5rem;
239
- }
240
-
241
- .endpoint-box code {
242
- flex: 1;
243
- font-family: ui-monospace, monospace;
244
- font-size: 0.875rem;
245
- color: #1f2937;
246
- }
247
-
248
- .copy-btn {
249
- background: #e5e7eb;
250
- border: none;
251
- padding: 0.5rem;
252
- border-radius: 6px;
253
- cursor: pointer;
254
- color: #6b7280;
255
- transition: all 0.15s;
256
- }
257
-
258
- .copy-btn:hover {
259
- background: #d1d5db;
260
- color: #374151;
261
- }
262
-
263
- .copy-btn svg {
264
- width: 16px;
265
- height: 16px;
266
- }
267
-
268
234
  .empty-state {
269
235
  text-align: center;
270
236
  padding: 3rem;
@@ -345,24 +311,6 @@ ${baseStyles}
345
311
  color: #e5e7eb;
346
312
  }
347
313
 
348
- .endpoint-box {
349
- background: rgba(31, 41, 55, 0.95);
350
- }
351
-
352
- .endpoint-box code {
353
- color: #e5e7eb;
354
- }
355
-
356
- .copy-btn {
357
- background: rgba(55, 65, 81, 0.95);
358
- color: #e5e7eb;
359
- }
360
-
361
- .copy-btn:hover {
362
- background: rgba(75, 85, 99, 0.98);
363
- color: #f9fafb;
364
- }
365
-
366
314
  .empty-state {
367
315
  color: #9ca3af;
368
316
  }
@@ -439,7 +387,7 @@ export function toolCard(tool: Tool<Record<string, unknown>>): string {
439
387
  * Render an MCP provider card
440
388
  */
441
389
  export function mcpProviderCard(provider: MCPProviderMeta): string {
442
- return /*html*/`
390
+ return /*html*/`
443
391
  <a class="card" href="/api/mcp/${provider.name}">
444
392
  <div class="card-title">${provider.name}</div>
445
393
  <div class="card-desc">${provider.description || ''}</div>
@@ -482,9 +430,9 @@ export function toolDetailCard(tool: Tool<Record<string, unknown>>, collectionNa
482
430
  ${properties ? /*html*/`
483
431
  <div class="info-grid" style="margin-bottom: 1rem;">
484
432
  ${Object.entries(properties).map(([key, value]) => {
485
- const prop = value as Record<string, unknown>;
486
- const isRequired = required?.includes(key);
487
- return /*html*/`
433
+ const prop = value as Record<string, unknown>;
434
+ const isRequired = required?.includes(key);
435
+ return /*html*/`
488
436
  <div class="info-item">
489
437
  <div class="info-label">${key}${isRequired ? ' *' : ''}</div>
490
438
  <div class="info-value">
@@ -492,7 +440,7 @@ export function toolDetailCard(tool: Tool<Record<string, unknown>>, collectionNa
492
440
  ${prop.description ? `<br><span style="color: #6b7280; font-size: 0.85rem;">${prop.description}</span>` : ''}
493
441
  </div>
494
442
  </div>`;
495
- }).join('')}
443
+ }).join('')}
496
444
  </div>
497
445
  ` : ''}
498
446
  <details>
@@ -525,13 +473,34 @@ export function skillCard(skill: SkillDefinition): string {
525
473
  </div>`;
526
474
  }
527
475
 
476
+ function skillWidgetsTemplate(skillWidgets: string[] | undefined) {
477
+ if (!skillWidgets || skillWidgets.length === 0) {
478
+ return 'n/a';
479
+ }
480
+ return skillWidgets.map(w => `<div style='display: flex; align-items: center; gap: 0.5rem; width:100%;justify-content: space-between;'><span>${w}</span>
481
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/widgets/${w}.js')" title="Copy endpoint URL">
482
+ ${copyIcon}
483
+ </button>
484
+ </div>`).join('');
485
+ }
486
+
487
+ function renderSkillUrl(skill: SkillDefinition, collectionName: string): string {
488
+ const skillPath = `/api/skills/${collectionName}/${skill.name}`;
489
+ return /*html*/`<div class="script-item" style='display: flex; align-items: center; gap: 0.5rem; width:100%;justify-content: space-between;'><span class="script-name">${skillPath}</span>
490
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '${skillPath}')" title="Copy endpoint URL">
491
+ ${copyIcon}
492
+ </button>
493
+ </div>`;
494
+ }
495
+
528
496
  /**
529
497
  * Render a detailed skill card
530
498
  */
531
- export function skillDetailCard(skill: SkillDefinition): string {
499
+ export function skillDetailCard(skill: SkillDefinition, collection: SkillCollection): string {
532
500
  const hasKeywords = skill.context_triggers?.keywords?.length;
533
501
  const hasPackages = skill.execution?.packages?.length;
534
502
  const hasScripts = skill.scripts?.length;
503
+ const hasRelatedTools = skill.related_tools?.length;
535
504
 
536
505
  return /*html*/`
537
506
  <div class="detail-card">
@@ -539,10 +508,12 @@ export function skillDetailCard(skill: SkillDefinition): string {
539
508
  <div>
540
509
  <h3 class="detail-title">${skill.name}</h3>
541
510
  <p class="detail-desc">${skill.description || 'No description'}</p>
511
+ ${renderSkillUrl(skill, collection.name)}
542
512
  </div>
543
513
  <div class="detail-badges">
544
514
  <span class="badge skill-type-badge">Skill</span>
545
515
  ${skill.execution?.language ? `<span class="badge ${skill.execution.language}">${skill.execution.language}</span>` : ''}
516
+ ${hasRelatedTools ? `<span class="badge" style="background: #8b5cf6; color: white;">Unlocks ${skill.related_tools?.length} tool${skill.related_tools?.length !== 1 ? 's' : ''}</span>` : ''}
546
517
  </div>
547
518
  </div>
548
519
  <div class="detail-body">
@@ -557,7 +528,21 @@ export function skillDetailCard(skill: SkillDefinition): string {
557
528
  <div class="info-value"><code>${skill.execution.language}</code></div>
558
529
  </div>
559
530
  ` : ''}
531
+ <div class="info-item">
532
+ <div class="info-label">Widgets</div>
533
+ <div class="info-value">${skillWidgetsTemplate(skill.widgets)}</div>
534
+ </div>
535
+ </div>
536
+
537
+ ${hasRelatedTools ? /*html*/`
538
+ <div class="detail-section">
539
+ <h4 class="detail-section-title">Unlocks Tools</h4>
540
+ <p style="color: #6b7280; font-size: 0.85rem; margin: 0 0 0.75rem 0;">These tools become available when this skill is activated:</p>
541
+ <div class="package-list">
542
+ ${skill.related_tools?.map(tool => `<span class="package-tag" style="background: #ede9fe; color: #6d28d9;">${tool}</span>`).join('')}
543
+ </div>
560
544
  </div>
545
+ ` : ''}
561
546
 
562
547
  ${hasKeywords ? /*html*/`
563
548
  <div class="detail-section">
@@ -584,7 +569,7 @@ export function skillDetailCard(skill: SkillDefinition): string {
584
569
  ${skill.scripts?.map(script => /*html*/`
585
570
  <div class="script-item">
586
571
  ${fileIcon}
587
- <span class="script-name">${script.name}</span>
572
+ <span class="script-name">${join("/scripts", script)}</span>
588
573
  </div>
589
574
  `).join('')}
590
575
  </div>
@@ -624,6 +609,47 @@ function getInitials(title: string): string {
624
609
  return initials || "TS";
625
610
  }
626
611
 
612
+ function renderUILinks() {
613
+ const copyIconSvg = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
614
+
615
+ return /*html*/`
616
+ <div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(156, 163, 175, 0.2); display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;">
617
+ <a target="_blank" href="/ui/" class="plugin-link-primary" style="display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background: #3b82f6; color: white; text-decoration: none; border-radius: 6px; font-size: 0.875rem; font-weight: 500; transition: background 0.15s;">
618
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
619
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
620
+ <line x1="9" y1="3" x2="9" y2="21"></line>
621
+ </svg>
622
+ UI Plugin Dev
623
+ </a>
624
+ <div style="display: inline-flex; align-items: center; gap: 0.5rem;">
625
+ <a href="/lib/plugin.js" class="plugin-link-secondary" style="display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background: #10b981; color: white; text-decoration: none; border-radius: 6px; font-size: 0.875rem; font-weight: 500; transition: background 0.15s;">
626
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
627
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
628
+ <polyline points="7 10 12 15 17 10"></polyline>
629
+ <line x1="12" y1="15" x2="12" y2="3"></line>
630
+ </svg>
631
+ Plugin Bundle
632
+ </a>
633
+ <button id="copy-plugin-btn" class="copy-btn" onclick="copyPluginUrl(this)" title="Copy plugin URL" style="background: #e5e7eb; border: none; padding: 0.5rem; border-radius: 6px; cursor: pointer; color: #6b7280; transition: all 0.15s; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px;">
634
+ ${copyIconSvg}
635
+ </button>
636
+ </div>
637
+ </div>
638
+ <script>
639
+ function copyPluginUrl(btn) {
640
+ navigator.clipboard.writeText(window.location.origin + '/lib/plugin.js');
641
+ const originalHtml = btn.innerHTML;
642
+ btn.innerHTML = '✓';
643
+ btn.style.color = '#10b981';
644
+ setTimeout(function() {
645
+ btn.innerHTML = originalHtml;
646
+ btn.style.color = '#6b7280';
647
+ }, 1500);
648
+ }
649
+ </script>
650
+ `;
651
+ }
652
+
627
653
  /**
628
654
  * Render the main index page
629
655
  *
@@ -632,25 +658,18 @@ function getInitials(title: string): string {
632
658
  * - If an array is passed, it is treated as MCP providers and the fifth argument (if any) is the title.
633
659
  */
634
660
  export function indexPage(
635
- tools: ToolCollection[],
636
- skills: SkillCollection[],
637
- interactions: InteractionCollection[],
638
- mcpProvidersOrTitle?: MCPProviderMeta[] | string,
639
- titleParam?: string
661
+ config: ToolServerConfig
640
662
  ): string {
641
- let mcpProviders: MCPProviderMeta[] = [];
642
- let title = "Tools Server";
663
+ const {
664
+ title = 'Tools Server',
665
+ tools = [],
666
+ interactions = [],
667
+ skills = [],
668
+ types = [],
669
+ mcpProviders = [],
670
+ hideUILinks = false,
671
+ } = config;
643
672
 
644
- if (Array.isArray(mcpProvidersOrTitle)) {
645
- mcpProviders = mcpProvidersOrTitle;
646
- if (typeof titleParam === "string" && titleParam.length > 0) {
647
- title = titleParam;
648
- }
649
- } else if (typeof mcpProvidersOrTitle === "string" && mcpProvidersOrTitle.length > 0) {
650
- title = mcpProvidersOrTitle;
651
- } else if (typeof titleParam === "string" && titleParam.length > 0) {
652
- title = titleParam;
653
- }
654
673
 
655
674
  return /*html*/`
656
675
  <!DOCTYPE html>
@@ -672,19 +691,28 @@ export function indexPage(
672
691
  <p class="hero-eyebrow">Tools Server</p>
673
692
  <h1 class="hero-title">${title}</h1>
674
693
  <p class="hero-tagline">
675
- Discover the tools, skills, and interactions exposed by this server.
694
+ Discover the tools, skills, interactions, and content types exposed by this server.
676
695
  </p>
677
696
  <div class="hero-summary">
678
697
  ${tools.length ? /*html*/`<span><dot></dot> ${tools.length} tool collection${tools.length !== 1 ? 's' : ''}</span>` : ''}
679
698
  ${skills.length ? /*html*/`<span><dot></dot> ${skills.length} skill collection${skills.length !== 1 ? 's' : ''}</span>` : ''}
680
699
  ${interactions.length ? /*html*/`<span><dot></dot> ${interactions.length} interaction collection${interactions.length !== 1 ? 's' : ''}</span>` : ''}
700
+ ${types.length ? /*html*/`<span><dot></dot> ${types.length} content type collection${types.length !== 1 ? 's' : ''}</span>` : ''}
681
701
  ${mcpProviders.length ? /*html*/`<span><dot></dot> ${mcpProviders.length} MCP provider${mcpProviders.length !== 1 ? 's' : ''}</span>` : ''}
682
702
  </div>
703
+ ${hideUILinks ? '' : renderUILinks()}
683
704
  </div>
684
705
  </div>
685
706
  <aside class="hero-panel">
686
707
  <div class="hero-panel-label">Base endpoint</div>
687
708
  <div class="hero-panel-endpoint"><code>/api</code></div>
709
+ <div class="hero-panel-label" style="margin-top: 1rem;">Package endpoint</div>
710
+ <div class="endpoint-box" style="margin-top: 0.5rem;">
711
+ <code id="package-endpoint-url">/api/package</code>
712
+ <button class="copy-btn" onclick="copyPackageUrl(this)" title="Copy package endpoint URL">
713
+ ${copyIcon}
714
+ </button>
715
+ </div>
688
716
  <p class="hero-panel-hint">
689
717
  Use <strong>POST /api/tools/&lt;collection&gt;</strong> or
690
718
  <strong>POST /api/skills/&lt;collection&gt;</strong> to call these from your apps or agents.
@@ -697,7 +725,7 @@ export function indexPage(
697
725
  type="search"
698
726
  id="collection-search"
699
727
  class="search-input"
700
- placeholder="Search tools, skills, interactions..."
728
+ placeholder="Search tools, skills, interactions, types..."
701
729
  aria-label="Search collections"
702
730
  autocomplete="off"
703
731
  />
@@ -730,9 +758,9 @@ export function indexPage(
730
758
  </div>
731
759
  <div class="card-grid">
732
760
  ${skills.map(s => {
733
- const count = Array.from(s).length;
734
- return collectionCard(s, 'skills', `${count} skill${count !== 1 ? 's' : ''}`);
735
- }).join('')}
761
+ const count = Array.from(s).length;
762
+ return collectionCard(s, 'skills', `${count} skill${count !== 1 ? 's' : ''}`);
763
+ }).join('')}
736
764
  </div>
737
765
  </section>
738
766
  ` : ''}
@@ -750,6 +778,22 @@ export function indexPage(
750
778
  </section>
751
779
  ` : ''}
752
780
 
781
+ ${types.length > 0 ? /*html*/`
782
+ <section data-section="types">
783
+ <hr>
784
+ <div class="section-header">
785
+ <h2>Content Type Collections</h2>
786
+ <p class="section-subtitle">Schema definitions for structured content in the data store.</p>
787
+ </div>
788
+ <div class="card-grid">
789
+ ${types.map((t: ContentTypesCollection) => {
790
+ const count = t.getContentTypes().length;
791
+ return collectionCard(t, 'types', `${count} type${count !== 1 ? 's' : ''}`);
792
+ }).join('')}
793
+ </div>
794
+ </section>
795
+ ` : ''}
796
+
753
797
  ${mcpProviders.length > 0 ? /*html*/`
754
798
  <section data-section="mcp">
755
799
  <hr>
@@ -811,6 +855,18 @@ export function indexPage(
811
855
  update(input.value);
812
856
  });
813
857
  }());
858
+
859
+ function copyPackageUrl(btn) {
860
+ var url = window.location.origin + '/api/package';
861
+ navigator.clipboard.writeText(url);
862
+ var originalHtml = btn.innerHTML;
863
+ btn.innerHTML = '✓';
864
+ btn.style.color = '#10b981';
865
+ setTimeout(function() {
866
+ btn.innerHTML = originalHtml;
867
+ btn.style.color = '#6b7280';
868
+ }, 1500);
869
+ }
814
870
  </script>
815
871
  </body>
816
872
  </html>`;
@@ -852,9 +908,9 @@ export function toolCollectionPage(collection: ToolCollection): string {
852
908
  <h2>${toolsArray.length} Tool${toolsArray.length !== 1 ? 's' : ''}</h2>
853
909
 
854
910
  ${toolsArray.length > 0 ?
855
- toolsArray.map(tool => toolDetailCard(tool, collection.name)).join('') :
856
- '<div class="empty-state">No tools in this collection</div>'
857
- }
911
+ toolsArray.map(tool => toolDetailCard(tool, collection.name)).join('') :
912
+ '<div class="empty-state">No tools in this collection</div>'
913
+ }
858
914
  </body>
859
915
  </html>`;
860
916
  }
@@ -895,27 +951,18 @@ export function skillCollectionPage(collection: SkillCollection): string {
895
951
  <h2>${skillsArray.length} Skill${skillsArray.length !== 1 ? 's' : ''}</h2>
896
952
 
897
953
  ${skillsArray.length > 0 ?
898
- skillsArray.map(skill => skillDetailCard(skill)).join('') :
899
- '<div class="empty-state">No skills in this collection</div>'
900
- }
954
+ skillsArray.map(skill => skillDetailCard(skill, collection)).join('') :
955
+ '<div class="empty-state">No skills in this collection</div>'
956
+ }
901
957
  </body>
902
958
  </html>`;
903
959
  }
904
960
 
905
961
  /**
906
- * Render an interaction collection detail page
962
+ * Render a collection header with icon, title, description, and endpoint
907
963
  */
908
- export function interactionCollectionPage(collection: InteractionCollection): string {
964
+ function collectionDetailHeader(collection: ICollection, pathPrefix: string): string {
909
965
  return /*html*/`
910
- <!DOCTYPE html>
911
- <html lang="en">
912
- <head>
913
- <meta charset="UTF-8">
914
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
915
- <title>${collection.title || collection.name} - Interactions</title>
916
- <style>${detailStyles}</style>
917
- </head>
918
- <body>
919
966
  <nav class="nav">
920
967
  <a href="/">${backArrow} Back to all collections</a>
921
968
  </nav>
@@ -926,30 +973,79 @@ export function interactionCollectionPage(collection: InteractionCollection): st
926
973
  <h1>${collection.title || collection.name}</h1>
927
974
  <p style="color: #6b7280; margin: 0.25rem 0 0 0;">${collection.description || ''}</p>
928
975
  <div class="endpoint-box">
929
- <code>/api/interactions/${collection.name}</code>
930
- <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/api/interactions/${collection.name}')" title="Copy endpoint URL">
976
+ <code>/api/${pathPrefix}/${collection.name}</code>
977
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/api/${pathPrefix}/${collection.name}')" title="Copy endpoint URL">
931
978
  ${copyIcon}
932
979
  </button>
933
980
  </div>
934
981
  </div>
935
- </div>
982
+ </div>`;
983
+ }
984
+
985
+ /**
986
+ * Render a simple item card with name, description, and tags
987
+ */
988
+ function simpleItemCard(item: { name: string; description?: string; tags?: string[] }): string {
989
+ return /*html*/`
990
+ <div class="detail-card">
991
+ <div class="detail-header">
992
+ <div>
993
+ <h3 class="detail-title">${item.name}</h3>
994
+ <p class="detail-desc">${item.description || 'No description'}</p>
995
+ </div>
996
+ <div class="detail-badges">
997
+ ${item.tags?.map(tag => `<span class="badge">${tag}</span>`).join('') || ''}
998
+ </div>
999
+ </div>
1000
+ </div>`;
1001
+ }
1002
+
1003
+ /**
1004
+ * Render an interaction collection detail page
1005
+ */
1006
+ export function interactionCollectionPage(collection: InteractionCollection): string {
1007
+ return /*html*/`
1008
+ <!DOCTYPE html>
1009
+ <html lang="en">
1010
+ <head>
1011
+ <meta charset="UTF-8">
1012
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1013
+ <title>${collection.title || collection.name} - Interactions</title>
1014
+ <style>${detailStyles}</style>
1015
+ </head>
1016
+ <body>
1017
+ ${collectionDetailHeader(collection, 'interactions')}
936
1018
 
937
1019
  <h2>${collection.interactions.length} Interaction${collection.interactions.length !== 1 ? 's' : ''}</h2>
938
1020
 
939
1021
  <div class="item-list">
940
- ${collection.interactions.map(inter => /*html*/`
941
- <div class="detail-card">
942
- <div class="detail-header">
943
- <div>
944
- <h3 class="detail-title">${inter.name}</h3>
945
- <p class="detail-desc">${inter.description || 'No description'}</p>
946
- </div>
947
- <div class="detail-badges">
948
- ${inter.tags?.map(tag => `<span class="badge">${tag}</span>`).join('') || ''}
949
- </div>
950
- </div>
951
- </div>
952
- `).join('')}
1022
+ ${collection.interactions.map(inter => simpleItemCard(inter)).join('')}
1023
+ </div>
1024
+ </body>
1025
+ </html>`;
1026
+ }
1027
+
1028
+ /**
1029
+ * Render a content type collection detail page
1030
+ */
1031
+ export function contentTypeCollectionPage(collection: ContentTypesCollection): string {
1032
+ const typesArray = collection.getContentTypes();
1033
+ return /*html*/`
1034
+ <!DOCTYPE html>
1035
+ <html lang="en">
1036
+ <head>
1037
+ <meta charset="UTF-8">
1038
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1039
+ <title>${collection.title || collection.name} - Content Types</title>
1040
+ <style>${detailStyles}</style>
1041
+ </head>
1042
+ <body>
1043
+ ${collectionDetailHeader(collection, 'types')}
1044
+
1045
+ <h2>${typesArray.length} Content Type${typesArray.length !== 1 ? 's' : ''}</h2>
1046
+
1047
+ <div class="item-list">
1048
+ ${typesArray.map(type => simpleItemCard(type)).join('')}
953
1049
  </div>
954
1050
  </body>
955
1051
  </html>`;
package/src/types.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ToolDefinition, ToolUse } from "@llumiverse/common";
2
2
  import { VertesiaClient } from "@vertesia/client";
3
- import { AuthTokenPayload, ToolResult, ToolResultContent } from "@vertesia/common";
3
+ import { AgentToolDefinition, AuthTokenPayload, ToolExecutionMetadata, ToolResult, ToolResultContent } from "@vertesia/common";
4
+
5
+ export type { ToolExecutionMetadata };
4
6
 
5
7
  export type ICollection<T = any> = CollectionProperties & Iterable<T>
6
8
 
@@ -80,23 +82,30 @@ export interface ToolExecutionPayload<ParamsT extends Record<string, any>> {
80
82
  /**
81
83
  * Optional metadata related to the current execution request
82
84
  */
83
- metadata?: Record<string, any>,
85
+ metadata?: ToolExecutionMetadata,
84
86
  }
85
87
 
86
88
  export type ToolFn<ParamsT extends Record<string, any>> = (payload: ToolExecutionPayload<ParamsT>, context: ToolExecutionContext) => Promise<ToolExecutionResult>;
87
89
 
88
90
  export interface Tool<ParamsT extends Record<string, any>> extends ToolDefinition {
89
91
  run: ToolFn<ParamsT>;
92
+ /**
93
+ * Whether this tool is available by default.
94
+ * - true/undefined: Tool is always available to agents
95
+ * - false: Tool is only available when activated by a skill's related_tools
96
+ */
97
+ default?: boolean;
90
98
  }
91
99
 
100
+
92
101
  /**
93
- * The interface that should be return when requesting a collection endpoint using a GET
102
+ * The interface that should be returned when requesting a collection endpoint using a GET
94
103
  */
95
104
  export interface ToolCollectionDefinition {
96
105
  title: string;
97
106
  description: string;
98
107
  src: string;
99
- tools: ToolDefinition[];
108
+ tools: AgentToolDefinition[];
100
109
  }
101
110
 
102
111
  export type { ToolDefinition };
@@ -167,19 +176,6 @@ export interface SkillExecution {
167
176
  template?: string;
168
177
  }
169
178
 
170
- /**
171
- * Script file bundled with a skill
172
- */
173
- export interface SkillScript {
174
- /**
175
- * Filename (e.g., "analyze.py")
176
- */
177
- name: string;
178
- /**
179
- * Script content
180
- */
181
- content: string;
182
- }
183
179
 
184
180
  /**
185
181
  * Skill definition - parsed from SKILL.md or SKILL.jst
@@ -229,7 +225,15 @@ export interface SkillDefinition {
229
225
  /**
230
226
  * Scripts bundled with this skill (synced to sandbox when skill is used)
231
227
  */
232
- scripts?: SkillScript[];
228
+ scripts?: string[];
229
+ /**
230
+ * The name of the widgets provided by this skill (if any)
231
+ * The name will be used to load the widget dynamically from the agent chat
232
+ * and must match the code block language returned by the LLM (e.g., ```my-widget)
233
+ * which will be rendered using the widget.
234
+ * The widget file must be located in the skill directory under the name {{widget-name}}.tsx.
235
+ */
236
+ widgets?: string[];
233
237
  }
234
238
 
235
239
  /**
package/src/utils.ts CHANGED
@@ -1,3 +1,23 @@
1
1
  export function kebabCaseToTitle(name: string) {
2
2
  return name.split('-').map(p => p[0].toUpperCase() + p.substring(1)).join(' ');
3
+ }
4
+
5
+ export function makeScriptUrl(origin: string, script: string) {
6
+ return join(origin, join("/scripts", script));
7
+ }
8
+
9
+ export function join(left: string, right: string) {
10
+ if (left.endsWith('/')) {
11
+ if (right.startsWith('/')) {
12
+ return left + right.slice(1);
13
+ } else {
14
+ return left + right;
15
+ }
16
+ } else if (right.startsWith('/')) {
17
+ return left + right;
18
+ } else if (right.startsWith('./')) {
19
+ return left + '/' + right.slice(2);
20
+ } else {
21
+ return left + '/' + right;
22
+ }
3
23
  }