mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
78 lines
3.4 KiB
TypeScript
78 lines
3.4 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
import { homedir } from "os";
|
|
import path from "path";
|
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
import { loadSkillsWithInstallInfo } from "@/lib/skills-service";
|
|
import { getAllowedFileRoots, isExistingFilePathAllowed } from "@/lib/file-access";
|
|
import { getManagedRuntimePaths, isManagedRuntime } from "@/lib/app-runtime";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// GET /api/skills?cwd=<path>
|
|
// Uses DefaultResourceLoader (same logic as AgentSession startup) so settings.json
|
|
// skill paths, package skills, and .agents/skills directories are all included.
|
|
export async function GET(req: Request) {
|
|
const { searchParams } = new URL(req.url);
|
|
const cwd = searchParams.get("cwd");
|
|
if (!cwd) return NextResponse.json({ error: "cwd required" }, { status: 400 });
|
|
|
|
try {
|
|
const allowedRoots = await getAllowedFileRoots();
|
|
if (!isExistingFilePathAllowed(cwd, allowedRoots)) {
|
|
return NextResponse.json({ error: "Access denied" }, { status: 403 });
|
|
}
|
|
return NextResponse.json(await loadSkillsWithInstallInfo(cwd));
|
|
} catch (e) {
|
|
return NextResponse.json({ error: String(e) }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// PATCH /api/skills — toggle disable-model-invocation on a SKILL.md file
|
|
export async function PATCH(req: Request) {
|
|
try {
|
|
const body = await req.json() as { filePath: string; disableModelInvocation: boolean };
|
|
const { filePath, disableModelInvocation } = body;
|
|
if (!filePath) return NextResponse.json({ error: "filePath required" }, { status: 400 });
|
|
if (!existsSync(filePath)) return NextResponse.json({ error: "file not found" }, { status: 404 });
|
|
const allowedRoots = new Set(await getAllowedFileRoots());
|
|
allowedRoots.add(getAgentDir());
|
|
if (isManagedRuntime()) {
|
|
for (const root of getManagedRuntimePaths().managedSkillRoots) {
|
|
if (existsSync(root)) allowedRoots.add(root);
|
|
}
|
|
} else {
|
|
// Upstream-compatible mode keeps the CLI's user-wide skill root.
|
|
const globalSkillsDir = path.join(homedir(), ".agents", "skills");
|
|
if (existsSync(globalSkillsDir)) allowedRoots.add(globalSkillsDir);
|
|
}
|
|
if (!isExistingFilePathAllowed(filePath, allowedRoots)) {
|
|
return NextResponse.json({ error: "Access denied" }, { status: 403 });
|
|
}
|
|
|
|
const content = readFileSync(filePath, "utf8");
|
|
const key = "disable-model-invocation";
|
|
|
|
// Use parseFrontmatter to check current value, then do a surgical line edit
|
|
// to preserve the original YAML formatting of all other fields.
|
|
const { frontmatter } = parseFrontmatter<Record<string, unknown>>(content);
|
|
const alreadySet = Boolean(frontmatter[key]);
|
|
|
|
let updated = content;
|
|
if (disableModelInvocation && !alreadySet) {
|
|
// Add key after the opening --- line
|
|
updated = content.replace(/^---\r?\n/, `---\n${key}: true\n`);
|
|
// If no frontmatter exists, create one
|
|
if (updated === content) updated = `---\n${key}: true\n---\n${content}`;
|
|
} else if (!disableModelInvocation && alreadySet) {
|
|
// Remove the key line entirely
|
|
updated = content.replace(new RegExp(`^${key}\\s*:.*\\r?\\n`, "m"), "");
|
|
}
|
|
|
|
writeFileSync(filePath, updated, "utf8");
|
|
return NextResponse.json({ success: true });
|
|
} catch (e) {
|
|
return NextResponse.json({ error: String(e) }, { status: 500 });
|
|
}
|
|
}
|