registry-fetcher.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /**
  2. * Registry Fetcher
  3. * Fetches and parses registry.json from GitHub
  4. */
  5. import { RegistrySchema, type Registry, type Component, type Profile } from '../types/registry'
  6. export interface FetchRegistryOptions {
  7. source?: 'github' | 'local'
  8. repository?: string
  9. branch?: string
  10. localPath?: string
  11. }
  12. /**
  13. * Fetch registry from GitHub or local file
  14. */
  15. export async function fetchRegistry(
  16. options: FetchRegistryOptions = {}
  17. ): Promise<Registry> {
  18. const {
  19. source = 'github',
  20. repository = 'darrenhinde/OpenAgentsControl',
  21. branch = 'main',
  22. localPath,
  23. } = options
  24. let registryData: unknown
  25. if (source === 'github') {
  26. // Fetch from GitHub raw URL
  27. const url = `https://raw.githubusercontent.com/${repository}/${branch}/registry.json`
  28. const response = await fetch(url)
  29. if (!response.ok) {
  30. throw new Error(`Failed to fetch registry: ${response.statusText}`)
  31. }
  32. registryData = await response.json()
  33. } else {
  34. // Load from local file
  35. if (!localPath) {
  36. throw new Error('localPath required for local source')
  37. }
  38. const fs = await import('fs/promises')
  39. const content = await fs.readFile(localPath, 'utf-8')
  40. registryData = JSON.parse(content)
  41. }
  42. // Validate with Zod
  43. const registry = RegistrySchema.parse(registryData)
  44. return registry
  45. }
  46. /**
  47. * Filter context components by profile
  48. */
  49. export function filterContextByProfile(
  50. registry: Registry,
  51. profile: Profile
  52. ): Component[] {
  53. const contexts = registry.components.contexts || []
  54. if (profile === 'all') {
  55. return contexts
  56. }
  57. // Map profiles to categories
  58. const profileCategories: Record<Profile, string[]> = {
  59. essential: ['essential', 'core'],
  60. standard: ['essential', 'core', 'standard'],
  61. extended: ['essential', 'core', 'standard', 'extended'],
  62. specialized: ['essential', 'core', 'standard', 'extended', 'specialized'],
  63. all: [], // handled above
  64. }
  65. const categories = profileCategories[profile]
  66. if (!categories) {
  67. throw new Error(`Unknown profile: ${profile}`)
  68. }
  69. return contexts.filter((component) => categories.includes(component.category))
  70. }
  71. /**
  72. * Filter context components by custom IDs
  73. */
  74. export function filterContextByIds(
  75. registry: Registry,
  76. componentIds: string[]
  77. ): Component[] {
  78. const contexts = registry.components.contexts || []
  79. return contexts.filter((component) => componentIds.includes(component.id))
  80. }
  81. /**
  82. * Get unique paths from components
  83. */
  84. export function getUniquePaths(components: Component[]): string[] {
  85. const paths = new Set<string>()
  86. for (const component of components) {
  87. // Extract directory path from component path
  88. const pathParts = component.path.split('/')
  89. pathParts.pop() // Remove filename
  90. const dirPath = pathParts.join('/')
  91. if (dirPath) {
  92. paths.add(dirPath)
  93. }
  94. }
  95. return Array.from(paths)
  96. }