@vertesia/tools-sdk 0.80.0-dev-20251118 → 0.80.0

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 (61) hide show
  1. package/README.md +122 -0
  2. package/lib/cjs/InteractionCollection.js +118 -0
  3. package/lib/cjs/InteractionCollection.js.map +1 -1
  4. package/lib/cjs/SkillCollection.js +318 -0
  5. package/lib/cjs/SkillCollection.js.map +1 -0
  6. package/lib/cjs/ToolCollection.js +98 -0
  7. package/lib/cjs/ToolCollection.js.map +1 -1
  8. package/lib/cjs/copy-assets.js +84 -0
  9. package/lib/cjs/copy-assets.js.map +1 -0
  10. package/lib/cjs/index.js +6 -1
  11. package/lib/cjs/index.js.map +1 -1
  12. package/lib/cjs/server.js +327 -0
  13. package/lib/cjs/server.js.map +1 -0
  14. package/lib/cjs/site/styles.js +621 -0
  15. package/lib/cjs/site/styles.js.map +1 -0
  16. package/lib/cjs/site/templates.js +932 -0
  17. package/lib/cjs/site/templates.js.map +1 -0
  18. package/lib/esm/InteractionCollection.js +83 -0
  19. package/lib/esm/InteractionCollection.js.map +1 -1
  20. package/lib/esm/SkillCollection.js +311 -0
  21. package/lib/esm/SkillCollection.js.map +1 -0
  22. package/lib/esm/ToolCollection.js +64 -0
  23. package/lib/esm/ToolCollection.js.map +1 -1
  24. package/lib/esm/copy-assets.js +81 -0
  25. package/lib/esm/copy-assets.js.map +1 -0
  26. package/lib/esm/index.js +4 -0
  27. package/lib/esm/index.js.map +1 -1
  28. package/lib/esm/server.js +323 -0
  29. package/lib/esm/server.js.map +1 -0
  30. package/lib/esm/site/styles.js +618 -0
  31. package/lib/esm/site/styles.js.map +1 -0
  32. package/lib/esm/site/templates.js +920 -0
  33. package/lib/esm/site/templates.js.map +1 -0
  34. package/lib/types/InteractionCollection.d.ts +29 -0
  35. package/lib/types/InteractionCollection.d.ts.map +1 -1
  36. package/lib/types/SkillCollection.d.ts +111 -0
  37. package/lib/types/SkillCollection.d.ts.map +1 -0
  38. package/lib/types/ToolCollection.d.ts +18 -0
  39. package/lib/types/ToolCollection.d.ts.map +1 -1
  40. package/lib/types/copy-assets.d.ts +14 -0
  41. package/lib/types/copy-assets.d.ts.map +1 -0
  42. package/lib/types/index.d.ts +4 -0
  43. package/lib/types/index.d.ts.map +1 -1
  44. package/lib/types/server.d.ts +72 -0
  45. package/lib/types/server.d.ts.map +1 -0
  46. package/lib/types/site/styles.d.ts +5 -0
  47. package/lib/types/site/styles.d.ts.map +1 -0
  48. package/lib/types/site/templates.d.ts +54 -0
  49. package/lib/types/site/templates.d.ts.map +1 -0
  50. package/lib/types/types.d.ts +152 -0
  51. package/lib/types/types.d.ts.map +1 -1
  52. package/package.json +55 -42
  53. package/src/InteractionCollection.ts +90 -0
  54. package/src/SkillCollection.ts +389 -0
  55. package/src/ToolCollection.ts +68 -0
  56. package/src/copy-assets.ts +104 -0
  57. package/src/index.ts +4 -0
  58. package/src/server.ts +444 -0
  59. package/src/site/styles.ts +617 -0
  60. package/src/site/templates.ts +956 -0
  61. package/src/types.ts +162 -0
@@ -0,0 +1,920 @@
1
+ import { baseStyles } from "./styles.js";
2
+ /**
3
+ * Default icon SVG for collections without a custom icon
4
+ */
5
+ const defaultIcon = /*html*/ `
6
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
7
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
8
+ </svg>`;
9
+ const skillIcon = /*html*/ `
10
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
11
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
12
+ </svg>`;
13
+ /**
14
+ * Extended styles for detail pages
15
+ */
16
+ const detailStyles = /*css*/ `
17
+ ${baseStyles}
18
+
19
+ .nav {
20
+ margin-bottom: 1.5rem;
21
+ }
22
+
23
+ .nav a {
24
+ color: #6b7280;
25
+ text-decoration: none;
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: 0.5rem;
29
+ font-size: 0.875rem;
30
+ }
31
+
32
+ .nav a:hover {
33
+ color: #2563eb;
34
+ }
35
+
36
+ .nav svg {
37
+ width: 16px;
38
+ height: 16px;
39
+ }
40
+
41
+ .detail-card {
42
+ background: white;
43
+ border-radius: 12px;
44
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
45
+ margin-bottom: 1.5rem;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .detail-header {
50
+ padding: 1.5rem;
51
+ border-bottom: 1px solid #e5e7eb;
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: flex-start;
55
+ }
56
+
57
+ .detail-title {
58
+ font-size: 1.25rem;
59
+ font-weight: 600;
60
+ color: #111827;
61
+ margin: 0 0 0.25rem 0;
62
+ font-family: ui-monospace, monospace;
63
+ }
64
+
65
+ .detail-desc {
66
+ color: #6b7280;
67
+ font-size: 0.95rem;
68
+ margin: 0;
69
+ }
70
+
71
+ .detail-badges {
72
+ display: flex;
73
+ gap: 0.5rem;
74
+ flex-wrap: wrap;
75
+ }
76
+
77
+ .detail-body {
78
+ padding: 1.5rem;
79
+ }
80
+
81
+ .detail-section {
82
+ margin-bottom: 1.5rem;
83
+ }
84
+
85
+ .detail-section:last-child {
86
+ margin-bottom: 0;
87
+ }
88
+
89
+ .detail-section-title {
90
+ font-size: 0.75rem;
91
+ font-weight: 600;
92
+ text-transform: uppercase;
93
+ letter-spacing: 0.05em;
94
+ color: #9ca3af;
95
+ margin: 0 0 0.75rem 0;
96
+ }
97
+
98
+ .schema-block {
99
+ background: #1f2937;
100
+ color: #e5e7eb;
101
+ padding: 1rem;
102
+ border-radius: 8px;
103
+ font-family: ui-monospace, monospace;
104
+ font-size: 0.8rem;
105
+ overflow-x: auto;
106
+ white-space: pre;
107
+ line-height: 1.5;
108
+ }
109
+
110
+ .schema-block .key { color: #93c5fd; }
111
+ .schema-block .string { color: #86efac; }
112
+ .schema-block .number { color: #fcd34d; }
113
+ .schema-block .boolean { color: #f9a8d4; }
114
+ .schema-block .null { color: #9ca3af; }
115
+
116
+ .info-grid {
117
+ display: grid;
118
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
119
+ gap: 1rem;
120
+ }
121
+
122
+ .info-item {
123
+ background: #f9fafb;
124
+ padding: 1rem;
125
+ border-radius: 8px;
126
+ }
127
+
128
+ .info-label {
129
+ font-size: 0.75rem;
130
+ font-weight: 600;
131
+ text-transform: uppercase;
132
+ letter-spacing: 0.05em;
133
+ color: #9ca3af;
134
+ margin-bottom: 0.25rem;
135
+ }
136
+
137
+ .info-value {
138
+ font-size: 0.95rem;
139
+ color: #111827;
140
+ }
141
+
142
+ .info-value code {
143
+ background: #e5e7eb;
144
+ padding: 0.125rem 0.375rem;
145
+ border-radius: 4px;
146
+ font-family: ui-monospace, monospace;
147
+ font-size: 0.85rem;
148
+ }
149
+
150
+ .package-list {
151
+ display: flex;
152
+ flex-wrap: wrap;
153
+ gap: 0.5rem;
154
+ }
155
+
156
+ .package-tag {
157
+ background: #dbeafe;
158
+ color: #1e40af;
159
+ padding: 0.25rem 0.75rem;
160
+ border-radius: 9999px;
161
+ font-size: 0.8rem;
162
+ font-family: ui-monospace, monospace;
163
+ }
164
+
165
+ .script-list {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 0.5rem;
169
+ }
170
+
171
+ .script-item {
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 0.75rem;
175
+ padding: 0.75rem 1rem;
176
+ background: #f9fafb;
177
+ border-radius: 8px;
178
+ }
179
+
180
+ .script-icon {
181
+ width: 20px;
182
+ height: 20px;
183
+ color: #6b7280;
184
+ }
185
+
186
+ .script-name {
187
+ font-family: ui-monospace, monospace;
188
+ font-size: 0.9rem;
189
+ color: #111827;
190
+ }
191
+
192
+ .keyword-list {
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ gap: 0.5rem;
196
+ }
197
+
198
+ .keyword-tag {
199
+ background: #fef3c7;
200
+ color: #92400e;
201
+ padding: 0.25rem 0.75rem;
202
+ border-radius: 9999px;
203
+ font-size: 0.8rem;
204
+ }
205
+
206
+ .instructions-preview {
207
+ background: #f9fafb;
208
+ border: 1px solid #e5e7eb;
209
+ border-radius: 8px;
210
+ padding: 1rem;
211
+ max-height: 300px;
212
+ overflow-y: auto;
213
+ font-size: 0.875rem;
214
+ line-height: 1.6;
215
+ color: #374151;
216
+ white-space: pre-wrap;
217
+ }
218
+
219
+ .endpoint-box {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 0.75rem;
223
+ background: #f3f4f6;
224
+ padding: 0.75rem 1rem;
225
+ border-radius: 8px;
226
+ margin-top: 0.5rem;
227
+ }
228
+
229
+ .endpoint-box code {
230
+ flex: 1;
231
+ font-family: ui-monospace, monospace;
232
+ font-size: 0.875rem;
233
+ color: #1f2937;
234
+ }
235
+
236
+ .copy-btn {
237
+ background: #e5e7eb;
238
+ border: none;
239
+ padding: 0.5rem;
240
+ border-radius: 6px;
241
+ cursor: pointer;
242
+ color: #6b7280;
243
+ transition: all 0.15s;
244
+ }
245
+
246
+ .copy-btn:hover {
247
+ background: #d1d5db;
248
+ color: #374151;
249
+ }
250
+
251
+ .copy-btn svg {
252
+ width: 16px;
253
+ height: 16px;
254
+ }
255
+
256
+ .empty-state {
257
+ text-align: center;
258
+ padding: 3rem;
259
+ color: #9ca3af;
260
+ }
261
+
262
+ .tool-type-badge {
263
+ background: #6366f1;
264
+ color: white;
265
+ }
266
+
267
+ .skill-type-badge {
268
+ background: #10b981;
269
+ color: white;
270
+ }
271
+
272
+ @media (prefers-color-scheme: dark) {
273
+ .nav a {
274
+ color: #9ca3af;
275
+ }
276
+
277
+ .nav a:hover {
278
+ color: #60a5fa;
279
+ }
280
+
281
+ .detail-card {
282
+ background: rgba(15, 23, 42, 0.96);
283
+ box-shadow:
284
+ 0 18px 40px rgba(15, 23, 42, 0.9),
285
+ 0 0 0 1px rgba(15, 23, 42, 0.9);
286
+ }
287
+
288
+ .detail-header {
289
+ border-bottom-color: rgba(55, 65, 81, 0.9);
290
+ }
291
+
292
+ .detail-title {
293
+ color: #e5e7eb;
294
+ }
295
+
296
+ .detail-desc {
297
+ color: #9ca3af;
298
+ }
299
+
300
+ .detail-section-title {
301
+ color: #9ca3af;
302
+ }
303
+
304
+ .info-item {
305
+ background: rgba(15, 23, 42, 0.9);
306
+ }
307
+
308
+ .info-value {
309
+ color: #e5e7eb;
310
+ }
311
+
312
+ .info-value code {
313
+ background: rgba(31, 41, 55, 0.9);
314
+ color: #e5e7eb;
315
+ }
316
+
317
+ .script-item {
318
+ background: rgba(15, 23, 42, 0.9);
319
+ }
320
+
321
+ .script-name {
322
+ color: #e5e7eb;
323
+ }
324
+
325
+ .keyword-tag {
326
+ background: rgba(250, 204, 21, 0.12);
327
+ color: #facc15;
328
+ }
329
+
330
+ .instructions-preview {
331
+ background: rgba(15, 23, 42, 0.9);
332
+ border-color: rgba(55, 65, 81, 0.9);
333
+ color: #e5e7eb;
334
+ }
335
+
336
+ .endpoint-box {
337
+ background: rgba(31, 41, 55, 0.95);
338
+ }
339
+
340
+ .endpoint-box code {
341
+ color: #e5e7eb;
342
+ }
343
+
344
+ .copy-btn {
345
+ background: rgba(55, 65, 81, 0.95);
346
+ color: #e5e7eb;
347
+ }
348
+
349
+ .copy-btn:hover {
350
+ background: rgba(75, 85, 99, 0.98);
351
+ color: #f9fafb;
352
+ }
353
+
354
+ .empty-state {
355
+ color: #9ca3af;
356
+ }
357
+ }
358
+ `;
359
+ /**
360
+ * Syntax highlight JSON
361
+ */
362
+ function highlightJson(obj) {
363
+ const json = JSON.stringify(obj, null, 2);
364
+ return json
365
+ .replace(/"([^"]+)":/g, '<span class="key">"$1"</span>:')
366
+ .replace(/: "([^"]*)"([,\n])/g, ': <span class="string">"$1"</span>$2')
367
+ .replace(/: (\d+)([,\n])/g, ': <span class="number">$1</span>$2')
368
+ .replace(/: (true|false)([,\n])/g, ': <span class="boolean">$1</span>$2')
369
+ .replace(/: (null)([,\n])/g, ': <span class="null">$1</span>$2');
370
+ }
371
+ /**
372
+ * Back navigation arrow
373
+ */
374
+ const backArrow = /*html*/ `
375
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
376
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
377
+ </svg>`;
378
+ /**
379
+ * Copy icon
380
+ */
381
+ const copyIcon = /*html*/ `
382
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
383
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
384
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
385
+ </svg>`;
386
+ /**
387
+ * File icon
388
+ */
389
+ const fileIcon = /*html*/ `
390
+ <svg class="script-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
391
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
392
+ <polyline points="14 2 14 8 20 8"></polyline>
393
+ </svg>`;
394
+ /**
395
+ * Render a collection card for the index page
396
+ */
397
+ export function collectionCard(collection, pathPrefix, meta) {
398
+ return /*html*/ `
399
+ <a class="card" href="/${pathPrefix}/${collection.name}" data-collection-type="${pathPrefix}" data-collection-name="${collection.name}">
400
+ <div class="card-icon">${collection.icon || defaultIcon}</div>
401
+ <div class="card-title">${collection.title || collection.name}</div>
402
+ <div class="card-desc">${collection.description || ''}</div>
403
+ ${meta ? `<div class="card-meta">${meta}</div>` : ''}
404
+ </a>`;
405
+ }
406
+ /**
407
+ * Render a tool card (simple version for lists)
408
+ */
409
+ export function toolCard(tool) {
410
+ return /*html*/ `
411
+ <div class="item-card">
412
+ <div class="item-name">${tool.name}</div>
413
+ <div class="item-desc">${tool.description || ''}</div>
414
+ ${tool.input_schema ? /*html*/ `
415
+ <div class="item-schema">${JSON.stringify(tool.input_schema, null, 2)}</div>
416
+ ` : ''}
417
+ </div>`;
418
+ }
419
+ /**
420
+ * Render an MCP provider card
421
+ */
422
+ export function mcpProviderCard(provider) {
423
+ return /*html*/ `
424
+ <a class="card" href="/api/mcp/${provider.name}">
425
+ <div class="card-title">${provider.name}</div>
426
+ <div class="card-desc">${provider.description || ''}</div>
427
+ </a>`;
428
+ }
429
+ /**
430
+ * Render a detailed tool card
431
+ */
432
+ export function toolDetailCard(tool, collectionName) {
433
+ const schema = tool.input_schema;
434
+ const properties = schema?.properties;
435
+ const required = schema?.required;
436
+ return /*html*/ `
437
+ <div class="detail-card">
438
+ <div class="detail-header">
439
+ <div>
440
+ <h3 class="detail-title">${tool.name}</h3>
441
+ <p class="detail-desc">${tool.description || 'No description'}</p>
442
+ </div>
443
+ <div class="detail-badges">
444
+ <span class="badge tool-type-badge">Tool</span>
445
+ </div>
446
+ </div>
447
+ <div class="detail-body">
448
+ <div class="detail-section">
449
+ <h4 class="detail-section-title">Endpoint</h4>
450
+ <div class="endpoint-box">
451
+ <code>POST /api/tools/${collectionName}</code>
452
+ <button class="copy-btn" onclick="navigator.clipboard.writeText('/api/tools/${collectionName}')" title="Copy">
453
+ ${copyIcon}
454
+ </button>
455
+ </div>
456
+ </div>
457
+
458
+ ${schema ? /*html*/ `
459
+ <div class="detail-section">
460
+ <h4 class="detail-section-title">Input Schema</h4>
461
+ ${properties ? /*html*/ `
462
+ <div class="info-grid" style="margin-bottom: 1rem;">
463
+ ${Object.entries(properties).map(([key, value]) => {
464
+ const prop = value;
465
+ const isRequired = required?.includes(key);
466
+ return /*html*/ `
467
+ <div class="info-item">
468
+ <div class="info-label">${key}${isRequired ? ' *' : ''}</div>
469
+ <div class="info-value">
470
+ <code>${prop.type || 'any'}</code>
471
+ ${prop.description ? `<br><span style="color: #6b7280; font-size: 0.85rem;">${prop.description}</span>` : ''}
472
+ </div>
473
+ </div>`;
474
+ }).join('')}
475
+ </div>
476
+ ` : ''}
477
+ <details>
478
+ <summary style="cursor: pointer; color: #6b7280; font-size: 0.85rem;">View full schema</summary>
479
+ <div class="schema-block" style="margin-top: 0.75rem;">${highlightJson(schema)}</div>
480
+ </details>
481
+ </div>
482
+ ` : /*html*/ `
483
+ <div class="detail-section">
484
+ <div class="empty-state">No input schema defined</div>
485
+ </div>
486
+ `}
487
+ </div>
488
+ </div>`;
489
+ }
490
+ /**
491
+ * Render a skill card (simple version for lists)
492
+ */
493
+ export function skillCard(skill) {
494
+ return /*html*/ `
495
+ <div class="item-card skill">
496
+ <div class="item-name">${skill.name}</div>
497
+ <div class="item-desc">${skill.description || ''}</div>
498
+ <div class="item-meta">
499
+ <span class="badge ${skill.execution?.language || ''}">${skill.content_type === 'jst' ? 'Dynamic' : 'Static'}</span>
500
+ ${skill.execution?.language ? `<span class="badge ${skill.execution.language}">${skill.execution.language}</span>` : ''}
501
+ ${skill.scripts?.length ? `<span class="badge">${skill.scripts.length} script${skill.scripts.length > 1 ? 's' : ''}</span>` : ''}
502
+ </div>
503
+ </div>`;
504
+ }
505
+ /**
506
+ * Render a detailed skill card
507
+ */
508
+ export function skillDetailCard(skill) {
509
+ const hasKeywords = skill.context_triggers?.keywords?.length;
510
+ const hasPackages = skill.execution?.packages?.length;
511
+ const hasScripts = skill.scripts?.length;
512
+ return /*html*/ `
513
+ <div class="detail-card">
514
+ <div class="detail-header">
515
+ <div>
516
+ <h3 class="detail-title">${skill.name}</h3>
517
+ <p class="detail-desc">${skill.description || 'No description'}</p>
518
+ </div>
519
+ <div class="detail-badges">
520
+ <span class="badge skill-type-badge">Skill</span>
521
+ ${skill.execution?.language ? `<span class="badge ${skill.execution.language}">${skill.execution.language}</span>` : ''}
522
+ </div>
523
+ </div>
524
+ <div class="detail-body">
525
+ <div class="info-grid">
526
+ <div class="info-item">
527
+ <div class="info-label">Content Type</div>
528
+ <div class="info-value">${skill.content_type === 'jst' ? 'Dynamic (JST Template)' : 'Static (Markdown)'}</div>
529
+ </div>
530
+ ${skill.execution?.language ? /*html*/ `
531
+ <div class="info-item">
532
+ <div class="info-label">Language</div>
533
+ <div class="info-value"><code>${skill.execution.language}</code></div>
534
+ </div>
535
+ ` : ''}
536
+ </div>
537
+
538
+ ${hasKeywords ? /*html*/ `
539
+ <div class="detail-section">
540
+ <h4 class="detail-section-title">Trigger Keywords</h4>
541
+ <div class="keyword-list">
542
+ ${skill.context_triggers?.keywords?.map(kw => `<span class="keyword-tag">${kw}</span>`).join('')}
543
+ </div>
544
+ </div>
545
+ ` : ''}
546
+
547
+ ${hasPackages ? /*html*/ `
548
+ <div class="detail-section">
549
+ <h4 class="detail-section-title">Required Packages</h4>
550
+ <div class="package-list">
551
+ ${skill.execution?.packages?.map(pkg => `<span class="package-tag">${pkg}</span>`).join('')}
552
+ </div>
553
+ </div>
554
+ ` : ''}
555
+
556
+ ${hasScripts ? /*html*/ `
557
+ <div class="detail-section">
558
+ <h4 class="detail-section-title">Bundled Scripts</h4>
559
+ <div class="script-list">
560
+ ${skill.scripts?.map(script => /*html*/ `
561
+ <div class="script-item">
562
+ ${fileIcon}
563
+ <span class="script-name">${script.name}</span>
564
+ </div>
565
+ `).join('')}
566
+ </div>
567
+ </div>
568
+ ` : ''}
569
+
570
+ <div class="detail-section">
571
+ <h4 class="detail-section-title">Instructions Preview</h4>
572
+ <div class="instructions-preview">${escapeHtml(skill.instructions.slice(0, 1000))}${skill.instructions.length > 1000 ? '...' : ''}</div>
573
+ </div>
574
+ </div>
575
+ </div>`;
576
+ }
577
+ /**
578
+ * Escape HTML for safe rendering
579
+ */
580
+ function escapeHtml(str) {
581
+ return str
582
+ .replace(/&/g, '&amp;')
583
+ .replace(/</g, '&lt;')
584
+ .replace(/>/g, '&gt;')
585
+ .replace(/"/g, '&quot;')
586
+ .replace(/'/g, '&#039;');
587
+ }
588
+ /**
589
+ * Derive simple initials from a title for use in the hero avatar.
590
+ */
591
+ function getInitials(title) {
592
+ const words = title.trim().split(/\s+/).filter(Boolean);
593
+ if (!words.length)
594
+ return "TS";
595
+ const initials = words
596
+ .slice(0, 2)
597
+ .map((word) => word.charAt(0).toUpperCase())
598
+ .join("");
599
+ return initials || "TS";
600
+ }
601
+ /**
602
+ * Render the main index page
603
+ *
604
+ * Note: The fourth argument is backward compatible:
605
+ * - If a string is passed, it is treated as the title.
606
+ * - If an array is passed, it is treated as MCP providers and the fifth argument (if any) is the title.
607
+ */
608
+ export function indexPage(tools, skills, interactions, mcpProvidersOrTitle, titleParam) {
609
+ let mcpProviders = [];
610
+ let title = "Tools Server";
611
+ if (Array.isArray(mcpProvidersOrTitle)) {
612
+ mcpProviders = mcpProvidersOrTitle;
613
+ if (typeof titleParam === "string" && titleParam.length > 0) {
614
+ title = titleParam;
615
+ }
616
+ }
617
+ else if (typeof mcpProvidersOrTitle === "string" && mcpProvidersOrTitle.length > 0) {
618
+ title = mcpProvidersOrTitle;
619
+ }
620
+ else if (typeof titleParam === "string" && titleParam.length > 0) {
621
+ title = titleParam;
622
+ }
623
+ return /*html*/ `
624
+ <!DOCTYPE html>
625
+ <html lang="en">
626
+ <head>
627
+ <meta charset="UTF-8">
628
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
629
+ <title>${title}</title>
630
+ <style>${baseStyles}</style>
631
+ </head>
632
+ <body>
633
+ <div class="page">
634
+ <header class="hero">
635
+ <div class="hero-main">
636
+ <div class="hero-logo">
637
+ <span class="hero-logo-initial">${escapeHtml(getInitials(title))}</span>
638
+ </div>
639
+ <div class="hero-meta">
640
+ <p class="hero-eyebrow">Tools Server</p>
641
+ <h1 class="hero-title">${title}</h1>
642
+ <p class="hero-tagline">
643
+ Discover the tools, skills, and interactions exposed by this server.
644
+ </p>
645
+ <div class="hero-summary">
646
+ ${tools.length ? /*html*/ `<span><dot></dot> ${tools.length} tool collection${tools.length !== 1 ? 's' : ''}</span>` : ''}
647
+ ${skills.length ? /*html*/ `<span><dot></dot> ${skills.length} skill collection${skills.length !== 1 ? 's' : ''}</span>` : ''}
648
+ ${interactions.length ? /*html*/ `<span><dot></dot> ${interactions.length} interaction collection${interactions.length !== 1 ? 's' : ''}</span>` : ''}
649
+ ${mcpProviders.length ? /*html*/ `<span><dot></dot> ${mcpProviders.length} MCP provider${mcpProviders.length !== 1 ? 's' : ''}</span>` : ''}
650
+ </div>
651
+ </div>
652
+ </div>
653
+ <aside class="hero-panel">
654
+ <div class="hero-panel-label">Base endpoint</div>
655
+ <div class="hero-panel-endpoint"><code>/api</code></div>
656
+ <p class="hero-panel-hint">
657
+ Use <strong>POST /api/tools/&lt;collection&gt;</strong> or
658
+ <strong>POST /api/skills/&lt;collection&gt;</strong> to call these from your apps or agents.
659
+ </p>
660
+ </aside>
661
+ </header>
662
+
663
+ <div class="search-bar">
664
+ <input
665
+ type="search"
666
+ id="collection-search"
667
+ class="search-input"
668
+ placeholder="Search tools, skills, interactions..."
669
+ aria-label="Search collections"
670
+ autocomplete="off"
671
+ />
672
+ <p class="search-hint">
673
+ Filter collections by name or description. Search runs locally in your browser.
674
+ </p>
675
+ <p id="search-empty" class="search-empty" style="display: none;">
676
+ No collections match this search.
677
+ </p>
678
+ </div>
679
+
680
+ ${tools.length > 0 ? /*html*/ `
681
+ <section data-section="tools">
682
+ <div class="section-header">
683
+ <h2>Tool Collections</h2>
684
+ <p class="section-subtitle">Remote tools available to agents via Vertesia.</p>
685
+ </div>
686
+ <div class="card-grid">
687
+ ${tools.map(t => collectionCard(t, 'tools')).join('')}
688
+ </div>
689
+ </section>
690
+ ` : ''}
691
+
692
+ ${skills.length > 0 ? /*html*/ `
693
+ <section data-section="skills">
694
+ <hr>
695
+ <div class="section-header">
696
+ <h2>Skill Collections</h2>
697
+ <p class="section-subtitle">Reusable instructions and scripts packaged as tools.</p>
698
+ </div>
699
+ <div class="card-grid">
700
+ ${skills.map(s => {
701
+ const count = Array.from(s).length;
702
+ return collectionCard(s, 'skills', `${count} skill${count !== 1 ? 's' : ''}`);
703
+ }).join('')}
704
+ </div>
705
+ </section>
706
+ ` : ''}
707
+
708
+ ${interactions.length > 0 ? /*html*/ `
709
+ <section data-section="interactions">
710
+ <hr>
711
+ <div class="section-header">
712
+ <h2>Interaction Collections</h2>
713
+ <p class="section-subtitle">Conversation blueprints surfaced in the Vertesia UI.</p>
714
+ </div>
715
+ <div class="card-grid">
716
+ ${interactions.map(i => collectionCard(i, 'interactions')).join('')}
717
+ </div>
718
+ </section>
719
+ ` : ''}
720
+
721
+ ${mcpProviders.length > 0 ? /*html*/ `
722
+ <section data-section="mcp">
723
+ <hr>
724
+ <div class="section-header">
725
+ <h2>MCP Providers</h2>
726
+ <p class="section-subtitle">Remote MCP servers available through this tools server.</p>
727
+ </div>
728
+ <div class="card-grid">
729
+ ${mcpProviders.map(p => mcpProviderCard(p)).join('')}
730
+ </div>
731
+ </section>
732
+ ` : ''}
733
+ </div>
734
+ <script>
735
+ (function () {
736
+ var input = document.getElementById('collection-search');
737
+ if (!input) return;
738
+
739
+ var cards = Array.prototype.slice.call(document.querySelectorAll('.card'));
740
+ if (!cards.length) return;
741
+
742
+ var sections = Array.prototype.slice.call(document.querySelectorAll('[data-section]'));
743
+ var emptyState = document.getElementById('search-empty');
744
+
745
+ function normalize(value) {
746
+ return (value || '').toString().toLowerCase();
747
+ }
748
+
749
+ function update(query) {
750
+ var q = normalize(query).trim();
751
+ var anyVisible = false;
752
+
753
+ cards.forEach(function (card) {
754
+ var text = normalize(card.textContent);
755
+ var match = !q || text.indexOf(q) !== -1;
756
+ card.style.display = match ? '' : 'none';
757
+ if (match) anyVisible = true;
758
+ });
759
+
760
+ sections.forEach(function (section) {
761
+ var visibleCards = section.querySelectorAll('.card');
762
+ var hasVisible = false;
763
+ for (var i = 0; i < visibleCards.length; i++) {
764
+ var style = window.getComputedStyle(visibleCards[i]);
765
+ if (style.display !== 'none') {
766
+ hasVisible = true;
767
+ break;
768
+ }
769
+ }
770
+ section.style.display = hasVisible ? '' : 'none';
771
+ });
772
+
773
+ if (emptyState) {
774
+ emptyState.style.display = q && !anyVisible ? '' : 'none';
775
+ }
776
+ }
777
+
778
+ input.addEventListener('input', function () {
779
+ update(input.value);
780
+ });
781
+ }());
782
+ </script>
783
+ </body>
784
+ </html>`;
785
+ }
786
+ /**
787
+ * Render a tool collection detail page
788
+ */
789
+ export function toolCollectionPage(collection) {
790
+ const toolsArray = Array.from(collection);
791
+ return /*html*/ `
792
+ <!DOCTYPE html>
793
+ <html lang="en">
794
+ <head>
795
+ <meta charset="UTF-8">
796
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
797
+ <title>${collection.title || collection.name} - Tools</title>
798
+ <style>${detailStyles}</style>
799
+ </head>
800
+ <body>
801
+ <nav class="nav">
802
+ <a href="/">${backArrow} Back to all collections</a>
803
+ </nav>
804
+
805
+ <div class="header">
806
+ <div class="header-icon">${collection.icon || defaultIcon}</div>
807
+ <div>
808
+ <h1>${collection.title || collection.name}</h1>
809
+ <p style="color: #6b7280; margin: 0.25rem 0 0 0;">${collection.description || ''}</p>
810
+ <div class="endpoint-box">
811
+ <code>/api/tools/${collection.name}</code>
812
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/api/tools/${collection.name}')" title="Copy endpoint URL">
813
+ ${copyIcon}
814
+ </button>
815
+ </div>
816
+ </div>
817
+ </div>
818
+
819
+ <h2>${toolsArray.length} Tool${toolsArray.length !== 1 ? 's' : ''}</h2>
820
+
821
+ ${toolsArray.length > 0 ?
822
+ toolsArray.map(tool => toolDetailCard(tool, collection.name)).join('') :
823
+ '<div class="empty-state">No tools in this collection</div>'}
824
+ </body>
825
+ </html>`;
826
+ }
827
+ /**
828
+ * Render a skill collection detail page
829
+ */
830
+ export function skillCollectionPage(collection) {
831
+ const skillsArray = Array.from(collection);
832
+ return /*html*/ `
833
+ <!DOCTYPE html>
834
+ <html lang="en">
835
+ <head>
836
+ <meta charset="UTF-8">
837
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
838
+ <title>${collection.title || collection.name} - Skills</title>
839
+ <style>${detailStyles}</style>
840
+ </head>
841
+ <body>
842
+ <nav class="nav">
843
+ <a href="/">${backArrow} Back to all collections</a>
844
+ </nav>
845
+
846
+ <div class="header">
847
+ <div class="header-icon">${collection.icon || skillIcon}</div>
848
+ <div>
849
+ <h1>${collection.title || collection.name}</h1>
850
+ <p style="color: #6b7280; margin: 0.25rem 0 0 0;">${collection.description || ''}</p>
851
+ <div class="endpoint-box">
852
+ <code>/api/skills/${collection.name}</code>
853
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/api/skills/${collection.name}')" title="Copy endpoint URL">
854
+ ${copyIcon}
855
+ </button>
856
+ </div>
857
+ </div>
858
+ </div>
859
+
860
+ <h2>${skillsArray.length} Skill${skillsArray.length !== 1 ? 's' : ''}</h2>
861
+
862
+ ${skillsArray.length > 0 ?
863
+ skillsArray.map(skill => skillDetailCard(skill)).join('') :
864
+ '<div class="empty-state">No skills in this collection</div>'}
865
+ </body>
866
+ </html>`;
867
+ }
868
+ /**
869
+ * Render an interaction collection detail page
870
+ */
871
+ export function interactionCollectionPage(collection) {
872
+ return /*html*/ `
873
+ <!DOCTYPE html>
874
+ <html lang="en">
875
+ <head>
876
+ <meta charset="UTF-8">
877
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
878
+ <title>${collection.title || collection.name} - Interactions</title>
879
+ <style>${detailStyles}</style>
880
+ </head>
881
+ <body>
882
+ <nav class="nav">
883
+ <a href="/">${backArrow} Back to all collections</a>
884
+ </nav>
885
+
886
+ <div class="header">
887
+ <div class="header-icon">${collection.icon || defaultIcon}</div>
888
+ <div>
889
+ <h1>${collection.title || collection.name}</h1>
890
+ <p style="color: #6b7280; margin: 0.25rem 0 0 0;">${collection.description || ''}</p>
891
+ <div class="endpoint-box">
892
+ <code>/api/interactions/${collection.name}</code>
893
+ <button class="copy-btn" onclick="navigator.clipboard.writeText(window.location.origin + '/api/interactions/${collection.name}')" title="Copy endpoint URL">
894
+ ${copyIcon}
895
+ </button>
896
+ </div>
897
+ </div>
898
+ </div>
899
+
900
+ <h2>${collection.interactions.length} Interaction${collection.interactions.length !== 1 ? 's' : ''}</h2>
901
+
902
+ <div class="item-list">
903
+ ${collection.interactions.map(inter => /*html*/ `
904
+ <div class="detail-card">
905
+ <div class="detail-header">
906
+ <div>
907
+ <h3 class="detail-title">${inter.name}</h3>
908
+ <p class="detail-desc">${inter.description || 'No description'}</p>
909
+ </div>
910
+ <div class="detail-badges">
911
+ ${inter.tags?.map(tag => `<span class="badge">${tag}</span>`).join('') || ''}
912
+ </div>
913
+ </div>
914
+ </div>
915
+ `).join('')}
916
+ </div>
917
+ </body>
918
+ </html>`;
919
+ }
920
+ //# sourceMappingURL=templates.js.map