char-slop/ai-dots
ai dotfiles
git clone https://git.t4t.associates/char-slop/ai-dots
b9b9000
main
1/** 2* Tools Extension 3* 4* Provides a /tools command to enable/disable tools interactively. 5* Tool selection persists across session reloads and respects branch navigation. 6* 7* Usage: 8* 1. Copy this file to ~/.pi/agent/extensions/ or your project's .pi/extensions/ 9* 2. Use /tools to open the tool selector 10*/ 11 12import type { ExtensionAPI , ExtensionContext , ToolInfo } from "@earendil-works/pi-coding-agent" ; 13import { getSettingsListTheme } from "@earendil-works/pi-coding-agent" ; 14import { Container , type SettingItem , SettingsList } from "@earendil-works/pi-tui" ; 15 16// State persisted to session 17interface ToolsState { 18enabledTools :string []; 19} 20 21export default function toolsExtension ( pi :ExtensionAPI ) { 22// Track enabled tools 23let enabledTools :Set < string > = new Set (); 24let allTools :ToolInfo [] = []; 25 26// Persist current state 27function persistState () { 28pi . appendEntry < ToolsState >( "tools-config" , { 29enabledTools :Array . from ( enabledTools ), 30}); 31} 32 33// Apply current tool selection 34function applyTools () { 35pi . setActiveTools ( Array . from ( enabledTools )); 36} 37 38// Find the last tools-config entry in the current branch 39function restoreFromBranch ( ctx :ExtensionContext ) { 40allTools = pi . getAllTools (); 41 42// Get entries in current branch only 43const branchEntries = ctx . sessionManager . getBranch (); 44let savedTools :string [] | undefined ; 45 46for ( const entry of branchEntries ) { 47if ( entry . type === "custom" && entry . customType === "tools-config" ) { 48const data = entry . data as ToolsState | undefined ; 49if ( data ?. enabledTools ) { 50savedTools = data . enabledTools ; 51} 52} 53} 54 55if ( savedTools ) { 56// Restore saved tool selection (filter to only tools that still exist) 57const allToolNames = allTools . map (( t ) => t . name ); 58enabledTools = new Set ( savedTools . filter (( t :string ) => allToolNames . includes ( t ))); 59applyTools (); 60} else { 61// No saved state - sync with currently active tools 62enabledTools = new Set ( pi . getActiveTools ()); 63} 64} 65 66// Register /tools command 67pi . registerCommand ( "tools" , { 68description :"Enable/disable tools" , 69handler :async ( _args , ctx ) => { 70// Refresh tool list 71allTools = pi . getAllTools (); 72 73await ctx . ui . custom (( tui , theme , _kb , done ) => { 74// Build settings items for each tool 75const items :SettingItem [] = allTools . map (( tool ) => ({ 76id :tool . name , 77label :tool . name , 78currentValue :enabledTools . has ( tool . name ) ?"enabled" :"disabled" , 79values :[ "enabled" , "disabled" ], 80})); 81 82const container = new Container (); 83container . addChild ( 84new ( class { 85render ( _width :number ) { 86return [ theme . fg ( "accent" , theme . bold ( "Tool Configuration" )), "" ]; 87} 88invalidate () {} 89})(), 90); 91 92const settingsList = new SettingsList ( 93items , 94Math . min ( items . length + 2 , 15 ), 95getSettingsListTheme (), 96( id , newValue ) => { 97// Update enabled state and apply immediately 98if ( newValue === "enabled" ) { 99enabledTools . add ( id ); 100} else { 101enabledTools . delete ( id ); 102} 103applyTools (); 104persistState (); 105}, 106() => { 107// Close dialog 108done ( undefined ); 109}, 110); 111 112container . addChild ( settingsList ); 113 114const component = { 115render ( width :number ) { 116return container . render ( width ); 117}, 118invalidate () { 119container . invalidate (); 120}, 121handleInput ( data :string ) { 122settingsList . handleInput ?.( data ); 123tui . requestRender (); 124}, 125}; 126 127return component ; 128}); 129}, 130}); 131 132// Restore state on session start 133pi . on ( "session_start" , async ( _event , ctx ) => { 134restoreFromBranch ( ctx ); 135}); 136 137// Restore state when navigating the session tree 138pi . on ( "session_tree" , async ( _event , ctx ) => { 139restoreFromBranch ( ctx ); 140}); 141}