mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-18 07:01:30 +00:00
When hooks are loaded via jiti, they get a separate module instance from the main app. This means the global 'theme' variable in the hook's module is never initialized. Adding an optional theme parameter allows hooks to pass the theme from ctx.ui.custom() callback. Usage in hooks: getSettingsListTheme(theme) // theme from ctx.ui.custom callback
145 lines
3.7 KiB
TypeScript
145 lines
3.7 KiB
TypeScript
/**
|
|
* Tools Hook
|
|
*
|
|
* Provides a /tools command to enable/disable tools interactively.
|
|
* Tool selection persists across session reloads and respects branch navigation.
|
|
*
|
|
* Usage:
|
|
* 1. Copy this file to ~/.pi/agent/hooks/ or your project's .pi/hooks/
|
|
* 2. Use /tools to open the tool selector
|
|
*/
|
|
|
|
import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
|
|
import type { HookAPI, HookContext } from "@mariozechner/pi-coding-agent/hooks";
|
|
import { Container, type SettingItem, SettingsList } from "@mariozechner/pi-tui";
|
|
|
|
// State persisted to session
|
|
interface ToolsState {
|
|
enabledTools: string[];
|
|
}
|
|
|
|
export default function toolsHook(pi: HookAPI) {
|
|
// Track enabled tools
|
|
let enabledTools: Set<string> = new Set();
|
|
let allTools: string[] = [];
|
|
|
|
// Persist current state
|
|
function persistState() {
|
|
pi.appendEntry<ToolsState>("tools-config", {
|
|
enabledTools: Array.from(enabledTools),
|
|
});
|
|
}
|
|
|
|
// Apply current tool selection
|
|
function applyTools() {
|
|
pi.setActiveTools(Array.from(enabledTools));
|
|
}
|
|
|
|
// Find the last tools-config entry in the current branch
|
|
function restoreFromBranch(ctx: HookContext) {
|
|
allTools = pi.getAllTools();
|
|
|
|
// Get entries in current branch only
|
|
const branchEntries = ctx.sessionManager.getBranch();
|
|
let savedTools: string[] | undefined;
|
|
|
|
for (const entry of branchEntries) {
|
|
if (entry.type === "custom" && entry.customType === "tools-config") {
|
|
const data = entry.data as ToolsState | undefined;
|
|
if (data?.enabledTools) {
|
|
savedTools = data.enabledTools;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (savedTools) {
|
|
// Restore saved tool selection (filter to only tools that still exist)
|
|
enabledTools = new Set(savedTools.filter((t: string) => allTools.includes(t)));
|
|
applyTools();
|
|
} else {
|
|
// No saved state - enable all tools by default
|
|
enabledTools = new Set(allTools);
|
|
}
|
|
}
|
|
|
|
// Register /tools command
|
|
pi.registerCommand("tools", {
|
|
description: "Enable/disable tools",
|
|
handler: async (_args, ctx) => {
|
|
// Refresh tool list
|
|
allTools = pi.getAllTools();
|
|
|
|
await ctx.ui.custom((tui, theme, done) => {
|
|
// Build settings items for each tool
|
|
const items: SettingItem[] = allTools.map((tool) => ({
|
|
id: tool,
|
|
label: tool,
|
|
currentValue: enabledTools.has(tool) ? "enabled" : "disabled",
|
|
values: ["enabled", "disabled"],
|
|
}));
|
|
|
|
const container = new Container();
|
|
container.addChild(
|
|
new (class {
|
|
render(_width: number) {
|
|
return [theme.fg("accent", theme.bold("Tool Configuration")), ""];
|
|
}
|
|
invalidate() {}
|
|
})(),
|
|
);
|
|
|
|
const settingsList = new SettingsList(
|
|
items,
|
|
Math.min(items.length + 2, 15),
|
|
getSettingsListTheme(theme),
|
|
(id, newValue) => {
|
|
// Update enabled state and apply immediately
|
|
if (newValue === "enabled") {
|
|
enabledTools.add(id);
|
|
} else {
|
|
enabledTools.delete(id);
|
|
}
|
|
applyTools();
|
|
persistState();
|
|
},
|
|
() => {
|
|
// Close dialog
|
|
done(undefined);
|
|
},
|
|
);
|
|
|
|
container.addChild(settingsList);
|
|
|
|
const component = {
|
|
render(width: number) {
|
|
return container.render(width);
|
|
},
|
|
invalidate() {
|
|
container.invalidate();
|
|
},
|
|
handleInput(data: string) {
|
|
settingsList.handleInput?.(data);
|
|
tui.requestRender();
|
|
},
|
|
};
|
|
|
|
return component;
|
|
});
|
|
},
|
|
});
|
|
|
|
// Restore state on session start
|
|
pi.on("session_start", async (_event, ctx) => {
|
|
restoreFromBranch(ctx);
|
|
});
|
|
|
|
// Restore state when navigating the session tree
|
|
pi.on("session_tree", async (_event, ctx) => {
|
|
restoreFromBranch(ctx);
|
|
});
|
|
|
|
// Restore state after branching
|
|
pi.on("session_branch", async (_event, ctx) => {
|
|
restoreFromBranch(ctx);
|
|
});
|
|
}
|