@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
53 lines (52 loc) • 2.5 kB
JavaScript
import { z } from "zod";
import { getProjectSourcePath } from "../project-config.js";
import { resolveProject } from "../store-routing.js";
import { isValidProjectName } from "../utils.js";
import { gitHostToken } from "./git-publish.js";
import { gitPulls } from "./git.js";
import { locateProject } from "./locate.js";
import { gitRoot } from "./projects.js";
import { BridgeError } from "./protocol.js";
/** Project git reads and token Connect share the same checkout lookup.
* `GET /v1/projects/:project/git`: the project's host, branch and open pull
* requests with their checks, read from its checkout on this computer, so the
* desktop's Projects page needs no session running there. */
export const PROJECT_GIT_ROUTE = /^\/v1\/projects\/([^/]+)\/git(?:\/(host-token))?$/;
/** The recorded source folder for this computer wins; otherwise the folders
* `/v1/projects/locate` offers, newest session first. */
async function projectCheckout(base, rawProject, activity) {
let project;
try {
project = decodeURIComponent(rawProject);
}
catch {
throw new BridgeError(400, "Invalid project name.");
}
if (!isValidProjectName(project))
throw new BridgeError(400, "Invalid project name.");
const folders = [];
try {
const source = getProjectSourcePath(resolveProject(base, project).store.path, project);
if (source)
folders.push(source);
}
catch { /* not in the store: located folders only */ }
for (const candidate of await locateProject(project, activity))
folders.push(candidate.directory);
for (const folder of folders) {
const root = await gitRoot(folder).catch(() => undefined);
if (root)
return { project, directory: root };
}
throw new BridgeError(404, "No checkout of this project on this computer.", { code: "no-checkout" });
}
export async function projectGit(base, rawProject, activity) {
const checkout = await projectCheckout(base, rawProject, activity);
return { ...checkout, ...(await gitPulls(checkout.directory)) };
}
/** No pull-list read here: resolve locally before passing a token to the host. */
export async function projectHostToken(base, rawProject, activity, data) {
const request = z.object({ token: z.string().max(4096), expectedHost: z.string().trim().min(1).max(253) }).parse(data);
const checkout = await projectCheckout(base, rawProject, activity);
return gitHostToken(checkout.directory, request);
}