This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/api.ts
T

120 lines
4.2 KiB
TypeScript

import * as routes from "./routes.js";
import type {
ApiError,
Branches,
CreateSessionRequest,
DaemonInfo,
DirListing,
GitRoot,
NameBatch,
Projects,
RecentDirs,
RetainedWorktree,
Session,
Uuid,
} from "./types.js";
async function jsonOrError<T>(res: Response): Promise<T> {
if (res.ok) {
if (res.status === 204) return undefined as unknown as T;
return (await res.json()) as T;
}
let detail = "";
try {
const body = (await res.json()) as ApiError;
detail = `${body.error.code}: ${body.error.message}`;
} catch {
detail = await res.text();
}
throw new Error(`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ""}`);
}
export async function listSessions(): Promise<Session[]> {
return jsonOrError(await fetch(routes.SESSIONS, { credentials: "same-origin" }));
}
export async function createSession(req: CreateSessionRequest): Promise<Session> {
return jsonOrError(
await fetch(routes.SESSIONS, {
method: "POST",
credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify(req),
}),
);
}
export async function killSession(id: Uuid): Promise<void> {
await jsonOrError(
await fetch(routes.session(id), {
method: "DELETE",
credentials: "same-origin",
}),
);
}
/** List one directory's immediate subdirectories (NAP-329). `path` may be empty,
* `~`, relative, or absolute — the daemon resolves it against its home dir. */
export async function listDir(path: string): Promise<DirListing> {
const url = routes.withPathQuery(routes.DIRS, path);
return jsonOrError(await fetch(url, { credentials: "same-origin" }));
}
/** Identity + spawn presets of the daemon this web app is connected to (#26). */
export async function getDaemonInfo(): Promise<DaemonInfo> {
return jsonOrError(await fetch(routes.INFO, { credentials: "same-origin" }));
}
/** Recently-used working dirs, newest-first and deduped by dir (#26). */
export async function recentDirs(): Promise<RecentDirs> {
return jsonOrError(await fetch(routes.RECENT_DIRS, { credentials: "same-origin" }));
}
/** Every discovered git repository / worktree, newest-activity-first (#54).
* Drives the spawn form's project picker. */
export async function projects(): Promise<Projects> {
return jsonOrError(await fetch(routes.PROJECTS, { credentials: "same-origin" }));
}
/** Probe the enclosing git repo of `path` (#26). `root` is absent when the path
* isn't inside a repo. `path` may be `~`/relative/absolute; the daemon resolves
* it against its home dir. */
export async function gitRoot(path: string): Promise<GitRoot> {
const url = routes.withPathQuery(routes.GITROOT, path);
return jsonOrError(await fetch(url, { credentials: "same-origin" }));
}
/** A batch of fresh session handles for the spawn form's instant reroll (#53).
* Rerolls consume the batch locally; only exhaustion goes back to the daemon. */
export async function fetchNames(count: number): Promise<NameBatch> {
const url = `${routes.NAMES}?${routes.COUNT_QUERY}=${encodeURIComponent(count)}`;
return jsonOrError(await fetch(url, { credentials: "same-origin" }));
}
/** One repository's local branches, with worktree occupancy (#55). Empty for
* a path outside any repo. Drives the spawn form's existing-branch picker. */
export async function fetchBranches(path: string): Promise<Branches> {
const url = routes.withPathQuery(routes.BRANCHES, path);
return jsonOrError(await fetch(url, { credentials: "same-origin" }));
}
/** The daemon's retained worktrees (#80): trees kept after a session ended
* with unsaved work, with what is currently in the way. Drives the
* retained-worktrees view (#84). */
export async function listWorktrees(): Promise<RetainedWorktree[]> {
return jsonOrError(await fetch(routes.WORKTREES, { credentials: "same-origin" }));
}
/** Reclaim a retained worktree: removes its directory and drops the store
* entry. Never deletes the branch (docs/WORKTREES.md). */
export async function reclaimWorktree(id: Uuid): Promise<void> {
await jsonOrError(
await fetch(routes.worktree(id), {
method: "DELETE",
credentials: "same-origin",
}),
);
}
export { eventsWsUrl } from "./routes.js";