mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 11:09:34 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user