import { z } from "zod";
import { NixOSClient } from "../nixos-client.ts";
import type { NixOSOption } from "../types.ts";
/**
* Zod schema for search_nixos_options tool input validation
*/
export const searchOptionsInputSchema = z.object({
query: z.string().min(1, "Query cannot be empty").describe(
"Search query for NixOS options (e.g., 'networking firewall', 'services nginx')"
),
from: z
.number()
.int()
.min(0)
.default(0)
.describe("Starting index for pagination (default: 0)"),
size: z
.number()
.int()
.min(1)
.max(50)
.default(20)
.describe("Number of results to return (1-50, default: 20)"),
});
export type SearchOptionsInput = z.infer<typeof searchOptionsInputSchema>;
/**
* Format a single option for display
*/
function formatOption(option: NixOSOption, index: number): string {
const lines: string[] = [];
lines.push(`${index}. ${option.option_name}`);
if (option.option_type) {
lines.push(` Type: ${option.option_type}`);
}
if (option.option_default !== undefined && option.option_default !== null) {
const defaultValue = formatValue(option.option_default);
lines.push(` Default: ${defaultValue}`);
}
if (option.option_description) {
// Wrap description to 80 characters
const wrapped = wrapText(option.option_description, 80);
lines.push(` Description: ${wrapped}`);
}
return lines.join("\n");
}
/**
* Format a value for display (handles complex types)
*/
function formatValue(value: unknown): string {
if (typeof value === "string") {
return `"${value}"`;
}
if (typeof value === "boolean") {
return String(value);
}
if (typeof value === "number") {
return String(value);
}
if (value === null) {
return "null";
}
if (Array.isArray(value)) {
if (value.length === 0) return "[]";
return "[ " + value.map((v) => formatValue(v)).join(", ") + " ]";
}
if (typeof value === "object") {
return JSON.stringify(value, null, 2).split("\n")[0] + "...";
}
return String(value);
}
/**
* Wrap text to a specified width
*/
function wrapText(text: string, width: number): string {
const words = text.split(" ");
const lines: string[] = [];
let currentLine = "";
for (const word of words) {
if ((currentLine + word).length > width && currentLine.length > 0) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = currentLine ? `${currentLine} ${word}` : word;
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines.join("\n ");
}
/**
* Handler for the search_nixos_options tool
*/
export async function handleSearchOptions(
input: SearchOptionsInput
): Promise<string> {
const client = new NixOSClient();
try {
const { options, total } = await client.searchOptions(
input.query,
input.from,
input.size
);
if (options.length === 0) {
return `No NixOS options found for query: "${input.query}"`;
}
const resultLines: string[] = [];
resultLines.push(`Found ${total} results for "${input.query}":\n`);
options.forEach((option, index) => {
resultLines.push(formatOption(option, input.from + index + 1));
resultLines.push(""); // Empty line between results
});
const endIndex = input.from + options.length;
resultLines.push("---");
resultLines.push(
`Showing results ${input.from + 1}-${endIndex} of ${total} total`
);
if (total > endIndex) {
resultLines.push(
`\nTo see more results, search again with from: ${endIndex}`
);
}
return resultLines.join("\n");
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown error occurred";
return `Error searching NixOS options: ${message}`;
}
}