- Fix critical bugs: coder-agent name (CoderAgent→coder-agent), manifest path in session-start.sh, /oac:setup ghost command replaced with /install-context - Fix hooks.json: add timeout/async, add description, restore matcher - Fix session-start.sh: dual-format output (Claude Code + Cursor/OpenCode), remove invalid JSON escapes for $ and backtick - Fix agent spec compliance: disallowedTools on read-only agents, WebFetch on external-scout, haiku model on context-scout, example blocks on 3 agents - Fix skill descriptions: triggering conditions only (no workflow summaries) per superpowers empirical testing — prevents Claude shortcutting skill content - Add Red Flags + Rationalization tables to 4 discipline skills - Add install-context skill + command replacing oac-setup/oac-plan/oac-add-context - Add cleanup-tmp.sh (was missing, broke /oac:cleanup) - Add TypeScript installer (install-context.ts/js) with profile-based download - Update plugin.json: keywords, homepage for marketplace discoverability - Update README: correct skill count, fix structure diagram, remove stale refs - Remove obsolete files: FIRST-TIME-SETUP.md, INSTALL.md, QUICK-START.md, download-context.sh, verify-context.sh, load-config.sh, context-manager skill, oac-setup/oac-plan/oac-add-context commands
119 lines
2.8 KiB
TypeScript
119 lines
2.8 KiB
TypeScript
/**
|
|
* Registry Fetcher
|
|
* Fetches and parses registry.json from GitHub
|
|
*/
|
|
|
|
import { RegistrySchema, type Registry, type Component, type Profile } from '../types/registry'
|
|
|
|
export interface FetchRegistryOptions {
|
|
source?: 'github' | 'local'
|
|
repository?: string
|
|
branch?: string
|
|
localPath?: string
|
|
}
|
|
|
|
/**
|
|
* Fetch registry from GitHub or local file
|
|
*/
|
|
export async function fetchRegistry(
|
|
options: FetchRegistryOptions = {}
|
|
): Promise<Registry> {
|
|
const {
|
|
source = 'github',
|
|
repository = 'darrenhinde/OpenAgentsControl',
|
|
branch = 'main',
|
|
localPath,
|
|
} = options
|
|
|
|
let registryData: unknown
|
|
|
|
if (source === 'github') {
|
|
// Fetch from GitHub raw URL
|
|
const url = `https://raw.githubusercontent.com/${repository}/${branch}/registry.json`
|
|
|
|
const response = await fetch(url)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch registry: ${response.statusText}`)
|
|
}
|
|
|
|
registryData = await response.json()
|
|
} else {
|
|
// Load from local file
|
|
if (!localPath) {
|
|
throw new Error('localPath required for local source')
|
|
}
|
|
|
|
const fs = await import('fs/promises')
|
|
const content = await fs.readFile(localPath, 'utf-8')
|
|
registryData = JSON.parse(content)
|
|
}
|
|
|
|
// Validate with Zod
|
|
const registry = RegistrySchema.parse(registryData)
|
|
|
|
return registry
|
|
}
|
|
|
|
/**
|
|
* Filter context components by profile
|
|
*/
|
|
export function filterContextByProfile(
|
|
registry: Registry,
|
|
profile: Profile
|
|
): Component[] {
|
|
const contexts = registry.components.contexts || []
|
|
|
|
if (profile === 'all') {
|
|
return contexts
|
|
}
|
|
|
|
// Map profiles to categories
|
|
const profileCategories: Record<Profile, string[]> = {
|
|
essential: ['essential', 'core'],
|
|
standard: ['essential', 'core', 'standard'],
|
|
extended: ['essential', 'core', 'standard', 'extended'],
|
|
specialized: ['essential', 'core', 'standard', 'extended', 'specialized'],
|
|
all: [], // handled above
|
|
}
|
|
|
|
const categories = profileCategories[profile]
|
|
|
|
if (!categories) {
|
|
throw new Error(`Unknown profile: ${profile}`)
|
|
}
|
|
|
|
return contexts.filter((component) => categories.includes(component.category))
|
|
}
|
|
|
|
/**
|
|
* Filter context components by custom IDs
|
|
*/
|
|
export function filterContextByIds(
|
|
registry: Registry,
|
|
componentIds: string[]
|
|
): Component[] {
|
|
const contexts = registry.components.contexts || []
|
|
|
|
return contexts.filter((component) => componentIds.includes(component.id))
|
|
}
|
|
|
|
/**
|
|
* Get unique paths from components
|
|
*/
|
|
export function getUniquePaths(components: Component[]): string[] {
|
|
const paths = new Set<string>()
|
|
|
|
for (const component of components) {
|
|
// Extract directory path from component path
|
|
const pathParts = component.path.split('/')
|
|
pathParts.pop() // Remove filename
|
|
const dirPath = pathParts.join('/')
|
|
|
|
if (dirPath) {
|
|
paths.add(dirPath)
|
|
}
|
|
}
|
|
|
|
return Array.from(paths)
|
|
}
|