mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { existsSync } from "fs";
|
||||
import { addWorktree, listWorktrees, removeWorktree, resolveProject } from "@/lib/worktree";
|
||||
import { allowFileRoot, getAllowedFileRoots, isExistingFilePathAllowed, isFilePathAllowed } from "@/lib/file-access";
|
||||
|
||||
/** Same gate as /api/files: only session cwds / project roots / explicitly
|
||||
* allowed dirs may be inspected or mutated through this endpoint. */
|
||||
async function checkCwdAllowed(cwd: string): Promise<NextResponse | null> {
|
||||
const allowedRoots = await getAllowedFileRoots();
|
||||
if (!isFilePathAllowed(cwd, allowedRoots) || !isExistingFilePathAllowed(cwd, allowedRoots)) {
|
||||
return NextResponse.json({ error: "Access denied" }, { status: 403 });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// GET /api/worktrees?cwd= → { projectRoot, isGit, isTopLevel, worktrees }
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const cwd = new URL(req.url).searchParams.get("cwd");
|
||||
if (!cwd) {
|
||||
return NextResponse.json({ error: "cwd is required" }, { status: 400 });
|
||||
}
|
||||
const denied = await checkCwdAllowed(cwd);
|
||||
if (denied) return denied;
|
||||
|
||||
const project = await resolveProject(cwd);
|
||||
let worktrees: Awaited<ReturnType<typeof listWorktrees>> = [];
|
||||
let isGit = true;
|
||||
try {
|
||||
// For a removed-worktree cwd (session of a deleted worktree), fall back
|
||||
// to the inferred project root so the switcher still shows the project.
|
||||
worktrees = await listWorktrees(existsSync(cwd) ? cwd : project.projectRoot);
|
||||
} catch {
|
||||
isGit = false;
|
||||
}
|
||||
// Every listed path is a git-verified worktree of this project; allow the
|
||||
// file explorer to browse them even before they have any session (the
|
||||
// in-memory allowlist from addWorktree does not survive server restarts).
|
||||
for (const w of worktrees) allowFileRoot(w.path);
|
||||
return NextResponse.json({
|
||||
projectRoot: project.projectRoot,
|
||||
isGit,
|
||||
isTopLevel: project.isTopLevel,
|
||||
worktrees,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/worktrees body: { cwd, branch } → { path, branch }
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json() as { cwd?: string; branch?: string };
|
||||
if (!body.cwd || typeof body.cwd !== "string") {
|
||||
return NextResponse.json({ error: "cwd is required" }, { status: 400 });
|
||||
}
|
||||
if (!body.branch || typeof body.branch !== "string") {
|
||||
return NextResponse.json({ error: "branch is required" }, { status: 400 });
|
||||
}
|
||||
const denied = await checkCwdAllowed(body.cwd);
|
||||
if (denied) return denied;
|
||||
if (!existsSync(body.cwd)) {
|
||||
return NextResponse.json({ error: `Directory does not exist: ${body.cwd}` }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await addWorktree(body.cwd, body.branch);
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return NextResponse.json({ error: message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/worktrees body: { cwd, path, force? }
|
||||
export async function DELETE(req: Request) {
|
||||
try {
|
||||
const body = await req.json() as { cwd?: string; path?: string; force?: boolean };
|
||||
if (!body.cwd || typeof body.cwd !== "string") {
|
||||
return NextResponse.json({ error: "cwd is required" }, { status: 400 });
|
||||
}
|
||||
if (!body.path || typeof body.path !== "string") {
|
||||
return NextResponse.json({ error: "path is required" }, { status: 400 });
|
||||
}
|
||||
const denied = await checkCwdAllowed(body.cwd);
|
||||
if (denied) return denied;
|
||||
|
||||
await removeWorktree(body.cwd, body.path, body.force === true);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
// git refuses to remove dirty worktrees without --force; surface that so
|
||||
// the UI can offer a force-remove confirmation.
|
||||
const dirty = /contains modified or untracked files|is dirty/i.test(message);
|
||||
return NextResponse.json({ error: message, dirty }, { status: dirty ? 409 : 400 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user