import * as InstanceState from "@/effect/instance-state" import { AppRuntime } from "@/effect/app-runtime" import { Project } from "@/project/project" import { InstanceBootstrap } from "@/project/bootstrap" import { ProjectID } from "@/project/schema" import { Effect, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" import { markInstanceForReload } from "./lifecycle" const root = "/project" export const ProjectApi = HttpApi.make("project") .add( HttpApiGroup.make("project") .add( HttpApiEndpoint.get("list", root, { success: Schema.Array(Project.Info), }).annotateMerge( OpenApi.annotations({ identifier: "project.list", summary: "List all projects", description: "Get a list of projects that have been opened with OpenCode.", }), ), HttpApiEndpoint.get("current", `${root}/current`, { success: Project.Info, }).annotateMerge( OpenApi.annotations({ identifier: "project.current", summary: "Get current project", description: "Retrieve the currently active project that OpenCode is working with.", }), ), HttpApiEndpoint.post("initGit", `${root}/git/init`, { success: Project.Info, }).annotateMerge( OpenApi.annotations({ identifier: "project.initGit", summary: "Initialize git repository", description: "Create a git repository for the current project and return the refreshed project info.", }), ), HttpApiEndpoint.patch("update", `${root}/:projectID`, { params: { projectID: ProjectID }, payload: Project.UpdatePayload, success: Project.Info, }).annotateMerge( OpenApi.annotations({ identifier: "project.update", summary: "Update project", description: "Update project properties such as name, icon, and commands.", }), ), ) .annotateMerge( OpenApi.annotations({ title: "project", description: "Experimental HttpApi project routes.", }), ) .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ title: "opencode experimental HttpApi", version: "0.0.1", description: "Experimental HttpApi surface for selected instance routes.", }), ) export const projectHandlers = HttpApiBuilder.group(ProjectApi, "project", (handlers) => Effect.gen(function* () { const svc = yield* Project.Service const list = Effect.fn("ProjectHttpApi.list")(function* () { return yield* svc.list() }) const current = Effect.fn("ProjectHttpApi.current")(function* () { return (yield* InstanceState.context).project }) const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () { const ctx = yield* InstanceState.context const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project }) if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree) return next yield* markInstanceForReload(ctx, { directory: ctx.directory, worktree: ctx.directory, project: next, init: () => AppRuntime.runPromise(InstanceBootstrap), }) return next }) const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: { params: { projectID: ProjectID } payload: Project.UpdatePayload }) { return yield* svc.update({ ...ctx.payload, projectID: ctx.params.projectID }) }) return handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update) }), )