From 8753bf90d5e6b5e93dc70d15ab17fab9a819c274 Mon Sep 17 00:00:00 2001 From: vagesh Date: Sun, 14 Jun 2026 12:29:42 +1000 Subject: [PATCH] feat(install): add Hermes Agent support Adds Hermes Agent as a supported provider with native skill installation. Hermes Agent uses SKILL.md format (same as upstream), so the installer copies all 7 caveman skills directly to ~/.hermes/skills/productivity/. Closes #45914 (related) --- bin/install.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/bin/install.js b/bin/install.js index 75223dc..c9951b2 100755 --- a/bin/install.js +++ b/bin/install.js @@ -228,7 +228,8 @@ const PROVIDERS = [ // CLI agents — require the binary. The `||dir:~/.foo` fallbacks were the // main source of false positives (warp, kiro, junie etc. leave config dirs // behind on uninstall). - { id: 'aider-desk', label: 'Aider Desk', mech: 'npx skills add (aider-desk)', detect: 'command:aider', profile: 'aider-desk' }, + { id: 'hermes', label: 'Hermes Agent', mech: 'npx skills add (hermes)', detect: 'command:hermes', profile: 'hermes' }, + { id: 'aider-desk', label: 'Aider Desk', mech: 'npx skills add (aider-desk)', detect: 'command:aider', profile: 'aider-desk' }, { id: 'amp', label: 'Sourcegraph Amp', mech: 'npx skills add (amp)', detect: 'command:amp', profile: 'amp' }, { id: 'bob', label: 'IBM Bob', mech: 'npx skills add (bob)', detect: 'command:bob', profile: 'bob' }, { id: 'crush', label: 'Crush', mech: 'npx skills add (crush)', detect: 'command:crush', profile: 'crush' }, @@ -560,6 +561,63 @@ function installViaSkills(ctx, prov) { process.stdout.write('\n'); } +// ── hermes native install ────────────────────────────────────────────────── +// Drops the caveman skills into ~/.hermes/skills/productivity/ (or HERMES_HOME if set). +const HERMES_SKILL_DIRS = ['caveman', 'caveman-commit', 'caveman-review', 'caveman-help', 'caveman-stats', 'caveman-compress', 'cavecrew']; + +function hermesConfigDir() { + // Hermes uses ~/.hermes by default, or HERMES_HOME env var. + if (process.env.HERMES_HOME) return path.join(process.env.HERMES_HOME, 'skills'); + return path.join(os.homedir(), '.hermes', 'skills'); +} + +function installHermes(ctx) { + const { say, note, warn, opts, repoRoot, results } = ctx; + results.detected++; + say('→ Hermes Agent detected'); + + if (!repoRoot) { + warn(' Hermes native install requires a local clone of the caveman repo.'); + note(' Re-run from a clone: git clone https://github.com/' + REPO + ' && cd caveman && node bin/install.js --only hermes'); + results.failed.push(['hermes', 'native install requires local repo clone']); + process.stdout.write('\n'); + return; + } + + const skillsRoot = path.join(hermesConfigDir(), 'productivity'); + + if (opts.dryRun) { + note(` would mkdir ${skillsRoot}/`); + note(` would copy ${HERMES_SKILL_DIRS.length} skill dirs into ${skillsRoot}/`); + results.installed.push('hermes'); + process.stdout.write('\n'); + return; + } + + try { + fs.mkdirSync(skillsRoot, { recursive: true }); + + for (const skillDir of HERMES_SKILL_DIRS) { + const srcDir = path.join(repoRoot, 'skills', skillDir); + const destDir = path.join(skillsRoot, skillDir); + if (fs.existsSync(srcDir)) { + // Remove existing to ensure clean copy + if (fs.existsSync(destDir)) fs.rmSync(destDir, { recursive: true, force: true }); + copyDirRecursive(srcDir, destDir); + note(` copied ${skillDir} → ${destDir}`); + } else { + warn(` skill dir not found: ${srcDir}`); + } + } + + results.installed.push('hermes'); + } catch (err) { + results.failed.push(['hermes', 'copy failed: ' + err.message]); + } + + process.stdout.write('\n'); +} + // ── opencode native install ─────────────────────────────────────────────── // Drops the in-repo plugin (src/plugins/opencode/) plus skills, agents, // commands, and an AGENTS.md ruleset into ~/.config/opencode/. Patches @@ -1340,10 +1398,11 @@ async function main() { // missing without --force). if (!explicit(prov.id) && !detectMatch(prov.detect)) continue; if (prov.id === 'claude') { await installClaude(ctx); continue; } - if (prov.id === 'gemini') { installGemini(ctx); continue; } - if (prov.id === 'opencode') { installOpencode(ctx); continue; } - if (prov.id === 'openclaw') { installOpenclaw(ctx); continue; } - if (prov.profile) { installViaSkills(ctx, prov); continue; } + if (prov.id === 'gemini') { installGemini(ctx); continue; } + if (prov.id === 'opencode') { installOpencode(ctx); continue; } + if (prov.id === 'openclaw') { installOpenclaw(ctx); continue; } + if (prov.id === 'hermes') { installHermes(ctx); continue; } + if (prov.profile) { installViaSkills(ctx, prov); continue; } } // Auto-detect fallback if nothing matched