Maps .agents/skills (global and per project) into .claude/skills so every skill is active, without duplicating existing ones

This repository contains Claude Code configuration files and automatically syncs with the upstream repository.
This repository automatically syncs from Melvynx/aiblueprint-cli-premium once per day at midnight UTC.
mainTo trigger a sync manually:
main branch and click "Run workflow"hooks/register.ts 97 lines1import type { Register } from 'claude-code'
2
3// `$` as a hook receives it.
4type Engine = Parameters<Parameters<Parameters<Register>[0]>[2]>[0]
5
6type Linked = { root: string; name: string }
7
8const SKILL_FILES = ['SKILL.md', 'skill.md']
9
10const realPath = async ($: Engine, path: string) =>
11 (await $.fs.stat(path, { resolve: true }).catch(() => undefined))?.realPath
12
13const homeDir = async ($: Engine) =>
14 (await $.process.run(['printenv', 'HOME'])).stdout.trim()
15
16// The session's cwd and every ancestor below $HOME, then $HOME itself (global).
17const rootsFor = (cwd: string, home: string) => {
18 const roots: string[] = []
19 let dir = cwd.replace(/\/+$/, '')
20 while (dir.startsWith(`${home}/`)) {
21 roots.push(dir)
22 dir = dir.slice(0, dir.lastIndexOf('/'))
23 }
24 if (!cwd.startsWith(`${home}/`) && cwd !== home) roots.push(cwd)
25 roots.push(home)
26 return roots
27}
28
29const isSkillDir = async ($: Engine, dir: string) => {
30 const stat = await $.fs.stat(dir).catch(() => undefined)
31 if (stat?.kind !== 'dir') return false
32 for (const file of SKILL_FILES) {
33 if (await $.fs.exists(`${dir}/${file}`)) return true
34 }
35 return false
36}
37
38// Links each <root>/.agents/skills/<name> missing from <root>/.claude/skills.
39const syncRoot = async ($: Engine, root: string): Promise<Linked[]> => {
40 const source = `${root}/.agents/skills`
41 const target = `${root}/.claude/skills`
42 if (!(await $.fs.exists(source))) return []
43
44 // .claude/skills already is .agents/skills (a symlinked folder): nothing to map.
45 const sourceReal = await realPath($, source)
46 if (sourceReal !== undefined && sourceReal === (await realPath($, target))) return []
47
48 // Every name already in .claude/skills, dangling links included, case-insensitive.
49 const existing = new Set(
50 (await $.fs.list(target).catch(() => [])).map(entry => entry.name.toLowerCase()),
51 )
52
53 const linked: Linked[] = []
54 for (const entry of await $.fs.list(source)) {
55 const { name } = entry
56 if (name.startsWith('.') || name.startsWith('__')) continue
57 if (existing.has(name.toLowerCase())) continue
58 if (!(await isSkillDir($, `${source}/${name}`))) continue
59
60 if (linked.length === 0) await $.process.run(['mkdir', '-p', target])
61 const ran = await $.process.run(['ln', '-s', `../../.agents/skills/${name}`, `${target}/${name}`])
62 if (ran.exitCode === 0) linked.push({ root, name })
63 else $.ui.log(`agent4everything: ln failed for ${name}: ${ran.stderr.trim()}`, { to: 'debug' })
64 }
65 return linked
66}
67
68const syncAll = async ($: Engine, cwd: string) => {
69 const home = await homeDir($)
70 const linked: Linked[] = []
71 for (const root of rootsFor(cwd, home)) linked.push(...(await syncRoot($, root)))
72 if (linked.length > 0) {
73 $.ui.toast(`agent4everything: linked ${linked.map(l => l.name).join(', ')}`)
74 }
75 return linked
76}
77
78export const register: Register = on => {
79 // Awaited before the first prompt: links exist before the session reads skills again.
80 on('session.start', async ($, e, next) => {
81 await syncAll($, e.cwd).catch(error => $.ui.log(`agent4everything: ${error}`, { to: 'debug' }))
82 return next(e)
83 })
84
85 // Startup, resume, /clear, compact: sync again and have the engine re-read skills.
86 on('classic.SessionStart', async ($, e, next) => {
87 await syncAll($, e.cwd).catch(() => [])
88 const result = await next(e)
89 return { ...result, reloadSkills: true }
90 })
91
92 on('classic.CwdChanged', async ($, e, next) => {
93 await syncAll($, e.new_cwd).catch(() => [])
94 return next(e)
95 })
96}
97