OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / scripts / update-skill-metadata.js

153 lines · javascript ref:
#!/usr/bin/env node

/**
 * Update Skill Metadata
 *
 * Manually update skill metadata after creating or regenerating a skill.
 *
 * Usage:
 *   node scripts/update-skill-metadata.js hypercore          # Mark hypercore as updated
 *   node scripts/update-skill-metadata.js --init             # Initialize all skills
 *   node scripts/update-skill-metadata.js --batch skill1 skill2 skill3
 */

import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'

const __dirname = dirname(fileURLToPath(import.meta.url))
const KB_ROOT = join(__dirname, '..')
const SKILLS_DIR = join(KB_ROOT, '.claude', 'skills')
const METADATA_FILE = join(SKILLS_DIR, '.skill-metadata.json')

// Load or create metadata
function loadMetadata() {
  if (!existsSync(METADATA_FILE)) {
    return {}
  }
  return JSON.parse(readFileSync(METADATA_FILE, 'utf-8'))
}

function saveMetadata(metadata) {
  writeFileSync(METADATA_FILE, JSON.stringify(metadata, null, 2))
  console.log(`Saved metadata for ${Object.keys(metadata).length} skills`)
}

// Get all skills
function getAllSkills() {
  const skills = []
  const entries = readdirSync(SKILLS_DIR, { withFileTypes: true })

  for (const entry of entries) {
    if (entry.isDirectory()) {
      const skillFile = join(SKILLS_DIR, entry.name, 'SKILL.md')
      if (existsSync(skillFile)) {
        const stats = statSync(skillFile)
        skills.push({
          name: entry.name,
          path: skillFile,
          mtime: stats.mtime.getTime()
        })
      }
    }
  }

  return skills
}

// Initialize metadata for all skills based on file mtime
function initializeMetadata() {
  console.log('Initializing metadata for all skills...')

  const skills = getAllSkills()
  const metadata = {}

  for (const skill of skills) {
    metadata[skill.name] = {
      lastUpdated: skill.mtime,
      lastChecked: Date.now(),
      source: 'file-mtime'
    }
  }

  saveMetadata(metadata)
  console.log(`✅ Initialized metadata for ${skills.length} skills`)
}

// Mark specific skill as updated
function markSkillUpdated(skillName) {
  const metadata = loadMetadata()

  const skillPath = join(SKILLS_DIR, skillName, 'SKILL.md')
  if (!existsSync(skillPath)) {
    console.error(`❌ Skill not found: ${skillName}`)
    process.exit(1)
  }

  metadata[skillName] = {
    lastUpdated: Date.now(),
    lastChecked: Date.now(),
    source: 'manual'
  }

  saveMetadata(metadata)
  console.log(`✅ Marked ${skillName} as updated`)
}

// Mark multiple skills as updated
function markBatchUpdated(skillNames) {
  const metadata = loadMetadata()

  for (const skillName of skillNames) {
    const skillPath = join(SKILLS_DIR, skillName, 'SKILL.md')
    if (!existsSync(skillPath)) {
      console.error(`❌ Skill not found: ${skillName}`)
      continue
    }

    metadata[skillName] = {
      lastUpdated: Date.now(),
      lastChecked: Date.now(),
      source: 'batch-update'
    }

    console.log(`✅ Marked ${skillName} as updated`)
  }

  saveMetadata(metadata)
}

// Main
function main() {
  const args = process.argv.slice(2)

  if (args.length === 0) {
    console.error('Usage:')
    console.error('  node update-skill-metadata.js <skill-name>')
    console.error('  node update-skill-metadata.js --init')
    console.error('  node update-skill-metadata.js --batch skill1 skill2 ...')
    process.exit(1)
  }

  if (args[0] === '--init') {
    initializeMetadata()
    return
  }

  if (args[0] === '--batch') {
    const skillNames = args.slice(1)
    if (skillNames.length === 0) {
      console.error('No skills specified for batch update')
      process.exit(1)
    }
    markBatchUpdated(skillNames)
    return
  }

  // Single skill update
  const skillName = args[0]
  markSkillUpdated(skillName)
}

main()