2026-07-10 14:59:17 +02:00
#!/usr/bin/env node
// Enable the caveman and ponytail plugins in a Claude Code settings.json so
2026-10-31 23:46:28 +01:00
// their SessionStart hooks auto-activate both modes, register the kvb-skills-tune
// and kvb-skills-dream reminder hooks, the kvb-org-organize claim gate, the resource watch and the kvb-org-perform log. Merges non-destructively: existing marketplaces,
// plugins and hooks are preserved, hooks of renamed skills are repointed, unparseable files are left untouched.
2026-07-10 14:59:17 +02:00
"use strict" ;
const fs = require ( "fs" );
2026-07-25 18:24:53 +02:00
const path = require ( "path" );
2026-07-10 14:59:17 +02:00
const settingsPath = process . argv [ 2 ];
if ( ! settingsPath ) {
console . error ( "usage: enable-plugins.js <settings.json>" );
process . exit ( 2 );
}
const MARKETPLACES = {
caveman : { source : { source : "github" , repo : "JuliusBrussee/caveman" } },
ponytail : { source : { source : "github" , repo : "DietrichGebert/ponytail" } },
};
const PLUGINS = { "caveman@caveman" : true , "ponytail@ponytail" : true };
2026-10-31 23:46:28 +01:00
const REMINDERS = [ "kvb-skills-tune" , "kvb-skills-dream" ];
const RENAMED = {
"autotune/hooks/reminder.sh" : "kvb-skills-tune/hooks/reminder.sh" ,
"dream/hooks/reminder.sh" : "kvb-skills-dream/hooks/reminder.sh" ,
"democracy/scripts/council.py" : "kvb-org-organize/scripts/council.py" ,
"democracy/scripts/watch.py" : "kvb-org-organize/scripts/watch.py" ,
"subagent-model/scripts/log.py" : "kvb-org-perform/scripts/log.py" ,
};
2026-07-10 14:59:17 +02:00
let data = {};
if ( fs . existsSync ( settingsPath )) {
const raw = fs . readFileSync ( settingsPath , "utf8" ). trim ();
if ( raw ) {
try {
data = JSON . parse ( raw );
} catch ( err ) {
console . error ( `skills: ${ settingsPath } is not valid JSON; leaving it untouched ( ${ err . message } ).` );
process . exit ( 0 );
}
}
}
data . extraKnownMarketplaces = data . extraKnownMarketplaces || {};
for ( const [ name , entry ] of Object . entries ( MARKETPLACES )) {
if ( ! data . extraKnownMarketplaces [ name ]) data . extraKnownMarketplaces [ name ] = entry ;
}
data . enabledPlugins = Object . assign ( data . enabledPlugins || {}, PLUGINS );
2026-07-25 18:24:53 +02:00
data . hooks = data . hooks || {};
2026-10-31 23:46:28 +01:00
for ( const entries of Object . values ( data . hooks )) {
if ( ! Array . isArray ( entries )) continue ;
for ( const entry of entries ) {
for ( const hook of entry . hooks || []) {
if ( typeof hook . command !== "string" ) continue ;
for ( const [ from , to ] of Object . entries ( RENAMED )) {
hook . command = hook . command . replace ( `/skills/ ${ from } ` , `/skills/ ${ to } ` );
}
}
}
}
2026-07-25 18:24:53 +02:00
data . hooks . UserPromptSubmit = data . hooks . UserPromptSubmit || [];
2026-08-02 19:34:05 +02:00
const alreadyRegistered = JSON . stringify ( data . hooks . UserPromptSubmit );
for ( const skill of REMINDERS ) {
if ( alreadyRegistered . includes ( ` ${ skill } /hooks/reminder.sh` )) continue ;
const reminder = path . join ( path . dirname ( settingsPath ), "skills" , skill , "hooks" , "reminder.sh" );
2026-07-25 18:24:53 +02:00
data . hooks . UserPromptSubmit . push ({
hooks : [{ type : "command" , command : `bash ${ JSON . stringify ( reminder ) } ` }],
});
}
2026-10-30 15:23:32 +01:00
data . hooks . PreToolUse = data . hooks . PreToolUse || [];
2026-10-31 23:46:28 +01:00
if ( ! JSON . stringify ( data . hooks . PreToolUse ). includes ( "kvb-org-organize/scripts/council.py" )) {
const council = path . join ( path . dirname ( settingsPath ), "skills" , "kvb-org-organize" , "scripts" , "council.py" );
2026-10-30 15:23:32 +01:00
data . hooks . PreToolUse . push ({
matcher : "Edit|Write|MultiEdit|NotebookEdit" ,
hooks : [{ type : "command" , command : `python3 ${ JSON . stringify ( council ) } gate` }],
});
}
2026-10-31 17:27:26 +01:00
const SCRIPT_HOOKS = [
2026-10-31 23:46:28 +01:00
[ "kvb-org-organize/scripts/watch.py" , "UserPromptSubmit" , null , "watch" ],
[ "kvb-org-organize/scripts/watch.py" , "PostToolUse" , "Bash" , "watch" ],
[ "kvb-org-organize/scripts/watch.py" , "PreToolUse" , "Bash" , "block" ],
[ "kvb-org-perform/scripts/log.py" , "PreToolUse" , "Agent|Task" , "spawn" ],
[ "kvb-org-perform/scripts/log.py" , "PostToolUse" , "Bash" , "persist" ],
2026-10-30 17:37:47 +01:00
];
2026-10-31 17:27:26 +01:00
for ( const [ script , event , matcher , sub ] of SCRIPT_HOOKS ) {
2026-10-30 17:37:47 +01:00
data . hooks [ event ] = data . hooks [ event ] || [];
const registered = data . hooks [ event ]. some (( entry ) =>
( entry . hooks || []). some (( hook ) =>
2026-10-31 17:27:26 +01:00
String ( hook . command ). includes ( script ) && String ( hook . command ). endsWith ( ` ${ sub } ` )));
2026-10-30 17:37:47 +01:00
if ( registered ) continue ;
2026-10-31 17:27:26 +01:00
const file = path . join ( path . dirname ( settingsPath ), "skills" , ... script . split ( "/" ));
const entry = { hooks : [{ type : "command" , command : `python3 ${ JSON . stringify ( file ) } ${ sub } ` }] };
2026-10-30 17:37:47 +01:00
data . hooks [ event ]. push ( matcher ? { matcher , ... entry } : entry );
}
2026-07-10 14:59:17 +02:00
fs . writeFileSync ( settingsPath , JSON . stringify ( data , null , 2 ) + "\n" );
2026-10-31 23:46:28 +01:00
console . log ( `skills: enabled caveman + ponytail plugins, the ${ REMINDERS . join ( " + " ) } reminders, the kvb-org-organize claim gate, the resource watch and the kvb-org-perform log in ${ settingsPath } ` );