131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { resolve } from "node:path";
|
|
|
|
import type { ExtensionAPI, SlashCommandInfo, ToolInfo } from "@mariozechner/pi-coding-agent";
|
|
|
|
function resolveFeynmanSettingsPath(): string {
|
|
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
const agentDir = configured
|
|
? configured.startsWith("~/")
|
|
? resolve(homedir(), configured.slice(2))
|
|
: resolve(configured)
|
|
: resolve(homedir(), ".feynman", "agent");
|
|
return resolve(agentDir, "settings.json");
|
|
}
|
|
|
|
function readConfiguredPackages(): string[] {
|
|
const settingsPath = resolveFeynmanSettingsPath();
|
|
if (!existsSync(settingsPath)) return [];
|
|
|
|
try {
|
|
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { packages?: unknown[] };
|
|
return Array.isArray(parsed.packages)
|
|
? parsed.packages
|
|
.map((entry) => {
|
|
if (typeof entry === "string") return entry;
|
|
if (!entry || typeof entry !== "object") return undefined;
|
|
const record = entry as { source?: unknown };
|
|
return typeof record.source === "string" ? record.source : undefined;
|
|
})
|
|
.filter((entry): entry is string => Boolean(entry))
|
|
: [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function formatSourceLabel(sourceInfo: { source: string; path: string }): string {
|
|
if (sourceInfo.source === "local") {
|
|
if (sourceInfo.path.includes("/prompts/")) return "workflow";
|
|
if (sourceInfo.path.includes("/extensions/")) return "extension";
|
|
return "local";
|
|
}
|
|
return sourceInfo.source.replace(/^npm:/, "").replace(/^git:/, "");
|
|
}
|
|
|
|
function formatCommandLine(command: SlashCommandInfo): string {
|
|
const source = formatSourceLabel(command.sourceInfo);
|
|
return `/${command.name} — ${command.description ?? ""} [${source}]`;
|
|
}
|
|
|
|
function summarizeToolParameters(tool: ToolInfo): string {
|
|
const properties =
|
|
tool.parameters &&
|
|
typeof tool.parameters === "object" &&
|
|
"properties" in tool.parameters &&
|
|
tool.parameters.properties &&
|
|
typeof tool.parameters.properties === "object"
|
|
? Object.keys(tool.parameters.properties as Record<string, unknown>)
|
|
: [];
|
|
return properties.length > 0 ? properties.join(", ") : "no parameters";
|
|
}
|
|
|
|
function formatToolLine(tool: ToolInfo): string {
|
|
const source = formatSourceLabel(tool.sourceInfo);
|
|
return `${tool.name} — ${tool.description ?? ""} [${source}]`;
|
|
}
|
|
|
|
export function registerDiscoveryCommands(pi: ExtensionAPI): void {
|
|
pi.registerCommand("commands", {
|
|
description: "Browse all available slash commands, including package and built-in commands.",
|
|
handler: async (_args, ctx) => {
|
|
const commands = pi
|
|
.getCommands()
|
|
.slice()
|
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
const items = commands.map((command) => formatCommandLine(command));
|
|
const selected = await ctx.ui.select("Slash Commands", items);
|
|
if (!selected) return;
|
|
ctx.ui.setEditorText(selected.split(" — ")[0] ?? "");
|
|
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("tools", {
|
|
description: "Browse all callable tools with their source and parameter summary.",
|
|
handler: async (_args, ctx) => {
|
|
const tools = pi
|
|
.getAllTools()
|
|
.slice()
|
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
const selected = await ctx.ui.select("Tools", tools.map((tool) => formatToolLine(tool)));
|
|
if (!selected) return;
|
|
|
|
const toolName = selected.split(" — ")[0] ?? selected;
|
|
const tool = tools.find((entry) => entry.name === toolName);
|
|
if (!tool) return;
|
|
ctx.ui.notify(`${tool.name}: ${summarizeToolParameters(tool)}`, "info");
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("capabilities", {
|
|
description: "Show installed packages, discovery entrypoints, and high-level runtime capability counts.",
|
|
handler: async (_args, ctx) => {
|
|
const commands = pi.getCommands();
|
|
const tools = pi.getAllTools();
|
|
const workflows = commands.filter((command) => formatSourceLabel(command.sourceInfo) === "workflow");
|
|
const packages = readConfiguredPackages();
|
|
const items = [
|
|
`Commands: ${commands.length}`,
|
|
`Workflows: ${workflows.length}`,
|
|
`Tools: ${tools.length}`,
|
|
`Packages: ${packages.length}`,
|
|
"--- Discovery ---",
|
|
"/commands — browse slash commands",
|
|
"/tools — inspect callable tools",
|
|
"/hotkeys — view keyboard shortcuts",
|
|
"/service-tier — set request tier for supported providers",
|
|
"--- Installed Packages ---",
|
|
...packages.map((pkg) => pkg),
|
|
];
|
|
const selected = await ctx.ui.select("Capabilities", items);
|
|
if (!selected || selected.startsWith("---")) return;
|
|
if (selected.startsWith("/")) {
|
|
ctx.ui.setEditorText(selected.split(" — ")[0] ?? selected);
|
|
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
|
}
|
|
},
|
|
});
|
|
}
|