import { CommandId, OrchestrationReadModel, ProjectId, type ClientOrchestrationCommand,} from "@t3tools/contracts";import * as Console from "effect/Console";import * as Data from "effect/Data";import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as References from "effect/References";import * as Schema from "effect/Schema";import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli";import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse,} from "effect/unstable/http"; import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts";import { AuthControlPlane } from "../auth/Services/AuthControlPlane.ts";import type { AuthControlPlaneShape } from "../auth/Services/AuthControlPlane.ts";import { ServerConfig, type ServerConfigShape } from "../config.ts";import { OrchestrationEngineService } from "../orchestration/Services/OrchestrationEngine.ts";import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts";import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts";import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts";import { getAutoBootstrapDefaultModelSelection } from "../serverRuntimeStartup.ts";import { clearPersistedServerRuntimeState, readPersistedServerRuntimeState,} from "../serverRuntimeState.ts";import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts";import { WorkspacePaths } from "../workspace/Services/WorkspacePaths.ts";import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; type ProjectMutationTarget = { readonly id: ProjectId; readonly title: string; readonly workspaceRoot: string;}; type ProjectCommandExecutionMode = "live" | "offline";type ProjectCliDispatchCommand = Extract< ClientOrchestrationCommand, { type: "project.create" | "project.meta.update" | "project.delete" }>; class ProjectCommandError extends Data.TaggedError("ProjectCommandError")<{ readonly message: string;}> {} const ProjectCliRuntimeLive = Layer.mergeAll( WorkspacePathsLive, OrchestrationLayerLive.pipe( Layer.provideMerge(RepositoryIdentityResolverLive), Layer.provideMerge(SqlitePersistenceLayerLive), ),); const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);const OrchestrationHttpErrorResponse = Schema.Struct({ error: Schema.String,}); const withProjectCliSessionToken = <A, E, R>( authControlPlane: AuthControlPlaneShape, run: (token: string) => Effect.Effect<A, E, R>,) => Effect.acquireUseRelease( authControlPlane.issueSession({ role: "owner", label: "t3 project cli", }), (issued) => run(issued.token), (issued) => authControlPlane.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })), ); const withProjectCliLiveServerTimeout = <A, E, R>(effect: Effect.Effect<A, E, R>) => effect.pipe(Effect.timeout(PROJECT_CLI_LIVE_SERVER_TIMEOUT)); const runLiveServerRequest = <A, E extends Error, R>( request: HttpClientRequest.HttpClientRequest, handle: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, E, R>,) => Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; const response = yield* httpClient.execute(request); return yield* handle(response); }).pipe(withProjectCliLiveServerTimeout); const decodeOrchestrationReadModelResponse = (response: HttpClientResponse.HttpClientResponse) => HttpClientResponse.schemaBodyJson(OrchestrationReadModel)(response); const readErrorMessageFromResponse = (response: HttpClientResponse.HttpClientResponse) => HttpClientResponse.schemaBodyJson(OrchestrationHttpErrorResponse)(response).pipe( Effect.map((body) => body.error), Effect.catch(() => Effect.succeed(null)), Effect.map((body) => { if (typeof body === "string" && body.trim().length > 0) { return body; } return `Server request failed with status ${response.status}.`; }), ); const normalizeWorkspaceRootForProjectCommand = Effect.fn( "normalizeWorkspaceRootForProjectCommand",)(function* (workspaceRoot: string) { const workspacePaths = yield* WorkspacePaths; return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot);}); const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* ( workspaceRoot: string, explicitTitle?: string,) { if (explicitTitle !== undefined) { const trimmed = explicitTitle.trim(); if (trimmed.length > 0) { return trimmed; } return yield* new ProjectCommandError({ message: "Project title cannot be empty." }); } const path = yield* Path.Path; const basename = path.basename(workspaceRoot).trim(); return basename.length > 0 ? basename : "project";}); const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (input: { readonly snapshot: OrchestrationReadModel; readonly identifier: string;}) { const trimmedIdentifier = input.identifier.trim(); if (trimmedIdentifier.length === 0) { return yield* new ProjectCommandError({ message: "Project identifier cannot be empty." }); } const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null); const exactIdMatch = activeProjects.find((project) => project.id === trimmedIdentifier); if (exactIdMatch) { return { id: exactIdMatch.id, title: exactIdMatch.title, workspaceRoot: exactIdMatch.workspaceRoot, } satisfies ProjectMutationTarget; } const normalizedWorkspaceRootResult = yield* Effect.exit( normalizeWorkspaceRootForProjectCommand(trimmedIdentifier), ); const normalizedWorkspaceRoot = Exit.isSuccess(normalizedWorkspaceRootResult) ? normalizedWorkspaceRootResult.value : null; const exactWorkspaceMatch = normalizedWorkspaceRoot === null ? undefined : activeProjects.find((project) => project.workspaceRoot === normalizedWorkspaceRoot); const resolved = exactWorkspaceMatch; if (!resolved) { return yield* new ProjectCommandError({ message: `No active project found for '${trimmedIdentifier}'.`, }); } return { id: resolved.id, title: resolved.title, workspaceRoot: resolved.workspaceRoot, } satisfies ProjectMutationTarget;}); const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) => runLiveServerRequest( HttpClientRequest.get(`${origin}/api/orchestration/snapshot`).pipe( HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(bearerToken), ), HttpClientResponse.matchStatus({ "2xx": decodeOrchestrationReadModelResponse, orElse: (response) => readErrorMessageFromResponse(response).pipe( Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), ), }), ); const dispatchLiveOrchestrationCommand = ( origin: string, bearerToken: string, command: ProjectCliDispatchCommand,) => HttpClientRequest.post(`${origin}/api/orchestration/dispatch`).pipe( HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(bearerToken), HttpClientRequest.bodyJson(command), Effect.flatMap((request) => runLiveServerRequest( request, HttpClientResponse.matchStatus({ "2xx": () => Effect.void, orElse: (response) => readErrorMessageFromResponse(response).pipe( Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), ), }), ), ), ); const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () { const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; return yield* projectionSnapshotQuery.getSnapshot();}); const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")( function* (authControlPlane: AuthControlPlaneShape, config: ServerConfigShape) { const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); if (Option.isNone(runtimeState)) { return Option.none<{ readonly origin: string }>(); } const attempt = withProjectCliSessionToken(authControlPlane, (token) => fetchLiveOrchestrationSnapshot(runtimeState.value.origin, token).pipe( Effect.as({ origin: runtimeState.value.origin, }), ), ); const attempted = yield* Effect.exit(attempt); if (Exit.isSuccess(attempted)) { return Option.some(attempted.value); } yield* clearPersistedServerRuntimeState(config.serverRuntimeStatePath); return Option.none<{ readonly origin: string }>(); },); const runProjectMutation = Effect.fn("runProjectMutation")(function* ( flags: CliAuthLocationFlags, run: (input: { readonly snapshot: OrchestrationReadModel; readonly dispatch: ( command: ProjectCliDispatchCommand, ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>; readonly mode: ProjectCommandExecutionMode; }) => Effect.Effect< string, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | WorkspacePaths >,) { const logLevel = yield* GlobalFlag.LogLevel; const config = yield* resolveCliAuthConfig(flags, logLevel); const minimumLogLevel = config.logLevel; return yield* Effect.gen(function* () { const authControlPlane = yield* AuthControlPlane; const liveMode = yield* tryResolveLiveProjectExecutionMode(authControlPlane, config); if (Option.isSome(liveMode)) { return yield* withProjectCliSessionToken(authControlPlane, (token) => Effect.gen(function* () { const snapshot = yield* fetchLiveOrchestrationSnapshot(liveMode.value.origin, token); const output = yield* run({ snapshot, dispatch: (command) => dispatchLiveOrchestrationCommand(liveMode.value.origin, token, command), mode: "live", }); yield* Console.log(output); }), ); } const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe( Layer.provide(Layer.succeed(ServerConfig, config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), ); return yield* Effect.gen(function* () { const snapshot = yield* getOfflineSnapshot(); const orchestrationEngine = yield* OrchestrationEngineService; const output = yield* run({ snapshot, dispatch: (command) => orchestrationEngine.dispatch(command), mode: "offline", }); yield* Console.log(output); }).pipe(Effect.provide(offlineRuntimeLayer)); }).pipe( Effect.provide( Layer.mergeAll(AuthControlPlaneRuntimeLive, WorkspacePathsLive).pipe( Layer.provideMerge(FetchHttpClient.layer), Layer.provide(Layer.succeed(ServerConfig, config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), ), ), );}); const projectAddCommand = Command.make("add", { ...projectLocationFlags, workspaceRoot: Argument.string("path").pipe( Argument.withDescription("Workspace root to add as a project."), ), title: Flag.string("title").pipe(Flag.withDescription("Optional project title."), Flag.optional),}).pipe( Command.withDescription("Add a project."), Command.withHandler((flags) => runProjectMutation( flags, Effect.fn("projectAddMutation")(function* ({ snapshot, dispatch, }: { readonly snapshot: OrchestrationReadModel; readonly dispatch: ( command: ProjectCliDispatchCommand, ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>; }) { const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand(flags.workspaceRoot); const existingProject = snapshot.projects.find( (project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot, ); if (existingProject) { return yield* new ProjectCommandError({ message: `An active project already exists for '${workspaceRoot}'.`, }); } const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title)); const projectId = ProjectId.make(crypto.randomUUID()); yield* dispatch({ type: "project.create", commandId: CommandId.make(crypto.randomUUID()), projectId, title, workspaceRoot, defaultModelSelection: getAutoBootstrapDefaultModelSelection(), createdAt: DateTime.formatIso(yield* DateTime.now), }); return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; }), ), ),); const projectRemoveCommand = Command.make("remove", { ...projectLocationFlags, project: Argument.string("project").pipe( Argument.withDescription("Project id or workspace root to remove."), ),}).pipe( Command.withDescription("Remove a project."), Command.withHandler((flags) => runProjectMutation( flags, Effect.fn("projectRemoveMutation")(function* ({ snapshot, dispatch, }: { readonly snapshot: OrchestrationReadModel; readonly dispatch: ( command: ProjectCliDispatchCommand, ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>; }) { const project = yield* findActiveProjectTarget({ snapshot, identifier: flags.project, }); yield* dispatch({ type: "project.delete", commandId: CommandId.make(crypto.randomUUID()), projectId: project.id, }); return `Removed project ${project.id} (${project.title}).`; }), ), ),); const projectRenameCommand = Command.make("rename", { ...projectLocationFlags, project: Argument.string("project").pipe( Argument.withDescription("Project id or workspace root to rename."), ), title: Argument.string("title").pipe(Argument.withDescription("New project title.")),}).pipe( Command.withDescription("Rename a project."), Command.withHandler((flags) => runProjectMutation( flags, Effect.fn("projectRenameMutation")(function* ({ snapshot, dispatch, }: { readonly snapshot: OrchestrationReadModel; readonly dispatch: ( command: ProjectCliDispatchCommand, ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>; }) { const project = yield* findActiveProjectTarget({ snapshot, identifier: flags.project, }); const nextTitle = yield* resolveProjectTitle(project.workspaceRoot, flags.title); if (nextTitle === project.title) { return `Project ${project.id} is already named ${nextTitle}.`; } yield* dispatch({ type: "project.meta.update", commandId: CommandId.make(crypto.randomUUID()), projectId: project.id, title: nextTitle, }); return `Renamed project ${project.id} to ${nextTitle}.`; }), ), ),); export const projectCommand = Command.make("project").pipe( Command.withDescription("Manage projects."), Command.withSubcommands([projectAddCommand, projectRemoveCommand, projectRenameCommand]),);
import {
CommandId,
OrchestrationReadModel,
ProjectId,
type ClientOrchestrationCommand,
} from "@t3tools/contracts";
import * as Console from "effect/Console";
import * as Data from "effect/Data";
import * as DateTime from "effect/DateTime";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as References from "effect/References";
import * as Schema from "effect/Schema";
import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli";
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http";
import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts";
import { AuthControlPlane } from "../auth/Services/AuthControlPlane.ts";
import type { AuthControlPlaneShape } from "../auth/Services/AuthControlPlane.ts";
import { ServerConfig, type ServerConfigShape } from "../config.ts";
import { OrchestrationEngineService } from "../orchestration/Services/OrchestrationEngine.ts";
import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";
import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts";
import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts";
import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts";
import { getAutoBootstrapDefaultModelSelection } from "../serverRuntimeStartup.ts";
import {
clearPersistedServerRuntimeState,
readPersistedServerRuntimeState,
} from "../serverRuntimeState.ts";
import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts";
import { WorkspacePaths } from "../workspace/Services/WorkspacePaths.ts";
import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
type ProjectMutationTarget = {
readonly id: ProjectId;
readonly title: string;
readonly workspaceRoot: string;
};
type ProjectCommandExecutionMode = "live" | "offline";
type ProjectCliDispatchCommand = Extract<
ClientOrchestrationCommand,
{ type: "project.create" | "project.meta.update" | "project.delete" }
>;
class ProjectCommandError extends Data.TaggedError("ProjectCommandError")<{
readonly message: string;
}> {}
const ProjectCliRuntimeLive = Layer.mergeAll(
WorkspacePathsLive,
OrchestrationLayerLive.pipe(
Layer.provideMerge(RepositoryIdentityResolverLive),
Layer.provideMerge(SqlitePersistenceLayerLive),
),
);
const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
const OrchestrationHttpErrorResponse = Schema.Struct({
error: Schema.String,
});
const withProjectCliSessionToken = <A, E, R>(
authControlPlane: AuthControlPlaneShape,
run: (token: string) => Effect.Effect<A, E, R>,
) =>
Effect.acquireUseRelease(
authControlPlane.issueSession({
role: "owner",
label: "t3 project cli",
}),
(issued) => run(issued.token),
(issued) => authControlPlane.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })),
);
const withProjectCliLiveServerTimeout = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
effect.pipe(Effect.timeout(PROJECT_CLI_LIVE_SERVER_TIMEOUT));
const runLiveServerRequest = <A, E extends Error, R>(
request: HttpClientRequest.HttpClientRequest,
handle: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, E, R>,
) =>
Effect.gen(function* () {
const httpClient = yield* HttpClient.HttpClient;
const response = yield* httpClient.execute(request);
return yield* handle(response);
}).pipe(withProjectCliLiveServerTimeout);
const decodeOrchestrationReadModelResponse = (response: HttpClientResponse.HttpClientResponse) =>
HttpClientResponse.schemaBodyJson(OrchestrationReadModel)(response);
const readErrorMessageFromResponse = (response: HttpClientResponse.HttpClientResponse) =>
HttpClientResponse.schemaBodyJson(OrchestrationHttpErrorResponse)(response).pipe(
Effect.map((body) => body.error),
Effect.catch(() => Effect.succeed(null)),
Effect.map((body) => {
if (typeof body === "string" && body.trim().length > 0) {
return body;
}
return `Server request failed with status ${response.status}.`;
}),
);
const normalizeWorkspaceRootForProjectCommand = Effect.fn(
"normalizeWorkspaceRootForProjectCommand",
)(function* (workspaceRoot: string) {
const workspacePaths = yield* WorkspacePaths;
return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot);
});
const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* (
workspaceRoot: string,
explicitTitle?: string,
) {
if (explicitTitle !== undefined) {
const trimmed = explicitTitle.trim();
if (trimmed.length > 0) {
return trimmed;
}
return yield* new ProjectCommandError({ message: "Project title cannot be empty." });
}
const path = yield* Path.Path;
const basename = path.basename(workspaceRoot).trim();
return basename.length > 0 ? basename : "project";
});
const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (input: {
readonly snapshot: OrchestrationReadModel;
readonly identifier: string;
}) {
const trimmedIdentifier = input.identifier.trim();
if (trimmedIdentifier.length === 0) {
return yield* new ProjectCommandError({ message: "Project identifier cannot be empty." });
}
const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null);
const exactIdMatch = activeProjects.find((project) => project.id === trimmedIdentifier);
if (exactIdMatch) {
return {
id: exactIdMatch.id,
title: exactIdMatch.title,
workspaceRoot: exactIdMatch.workspaceRoot,
} satisfies ProjectMutationTarget;
}
const normalizedWorkspaceRootResult = yield* Effect.exit(
normalizeWorkspaceRootForProjectCommand(trimmedIdentifier),
);
const normalizedWorkspaceRoot = Exit.isSuccess(normalizedWorkspaceRootResult)
? normalizedWorkspaceRootResult.value
: null;
const exactWorkspaceMatch =
normalizedWorkspaceRoot === null
? undefined
: activeProjects.find((project) => project.workspaceRoot === normalizedWorkspaceRoot);
const resolved = exactWorkspaceMatch;
if (!resolved) {
return yield* new ProjectCommandError({
message: `No active project found for '${trimmedIdentifier}'.`,
});
}
return {
id: resolved.id,
title: resolved.title,
workspaceRoot: resolved.workspaceRoot,
} satisfies ProjectMutationTarget;
});
const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) =>
runLiveServerRequest(
HttpClientRequest.get(`${origin}/api/orchestration/snapshot`).pipe(
HttpClientRequest.acceptJson,
HttpClientRequest.bearerToken(bearerToken),
),
HttpClientResponse.matchStatus({
"2xx": decodeOrchestrationReadModelResponse,
orElse: (response) =>
readErrorMessageFromResponse(response).pipe(
Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))),
),
}),
);
const dispatchLiveOrchestrationCommand = (
origin: string,
bearerToken: string,
command: ProjectCliDispatchCommand,
) =>
HttpClientRequest.post(`${origin}/api/orchestration/dispatch`).pipe(
HttpClientRequest.acceptJson,
HttpClientRequest.bearerToken(bearerToken),
HttpClientRequest.bodyJson(command),
Effect.flatMap((request) =>
runLiveServerRequest(
request,
HttpClientResponse.matchStatus({
"2xx": () => Effect.void,
orElse: (response) =>
readErrorMessageFromResponse(response).pipe(
Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))),
),
}),
),
),
);
const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {
const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
return yield* projectionSnapshotQuery.getSnapshot();
});
const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(
function* (authControlPlane: AuthControlPlaneShape, config: ServerConfigShape) {
const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
if (Option.isNone(runtimeState)) {
return Option.none<{ readonly origin: string }>();
}
const attempt = withProjectCliSessionToken(authControlPlane, (token) =>
fetchLiveOrchestrationSnapshot(runtimeState.value.origin, token).pipe(
Effect.as({
origin: runtimeState.value.origin,
}),
),
);
const attempted = yield* Effect.exit(attempt);
if (Exit.isSuccess(attempted)) {
return Option.some(attempted.value);
}
yield* clearPersistedServerRuntimeState(config.serverRuntimeStatePath);
return Option.none<{ readonly origin: string }>();
},
);
const runProjectMutation = Effect.fn("runProjectMutation")(function* (
flags: CliAuthLocationFlags,
run: (input: {
readonly snapshot: OrchestrationReadModel;
readonly dispatch: (
command: ProjectCliDispatchCommand,
) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;
readonly mode: ProjectCommandExecutionMode;
}) => Effect.Effect<
string,
Error,
FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | WorkspacePaths
>,
) {
const logLevel = yield* GlobalFlag.LogLevel;
const config = yield* resolveCliAuthConfig(flags, logLevel);
const minimumLogLevel = config.logLevel;
return yield* Effect.gen(function* () {
const authControlPlane = yield* AuthControlPlane;
const liveMode = yield* tryResolveLiveProjectExecutionMode(authControlPlane, config);
if (Option.isSome(liveMode)) {
return yield* withProjectCliSessionToken(authControlPlane, (token) =>
Effect.gen(function* () {
const snapshot = yield* fetchLiveOrchestrationSnapshot(liveMode.value.origin, token);
const output = yield* run({
snapshot,
dispatch: (command) =>
dispatchLiveOrchestrationCommand(liveMode.value.origin, token, command),
mode: "live",
});
yield* Console.log(output);
}),
);
}
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(
Layer.provide(Layer.succeed(ServerConfig, config)),
Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
);
return yield* Effect.gen(function* () {
const snapshot = yield* getOfflineSnapshot();
const orchestrationEngine = yield* OrchestrationEngineService;
const output = yield* run({
snapshot,
dispatch: (command) => orchestrationEngine.dispatch(command),
mode: "offline",
});
yield* Console.log(output);
}).pipe(Effect.provide(offlineRuntimeLayer));
}).pipe(
Effect.provide(
Layer.mergeAll(AuthControlPlaneRuntimeLive, WorkspacePathsLive).pipe(
Layer.provideMerge(FetchHttpClient.layer),
Layer.provide(Layer.succeed(ServerConfig, config)),
Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
),
),
);
});
const projectAddCommand = Command.make("add", {
...projectLocationFlags,
workspaceRoot: Argument.string("path").pipe(
Argument.withDescription("Workspace root to add as a project."),
),
title: Flag.string("title").pipe(Flag.withDescription("Optional project title."), Flag.optional),
}).pipe(
Command.withDescription("Add a project."),
Command.withHandler((flags) =>
runProjectMutation(
flags,
Effect.fn("projectAddMutation")(function* ({
snapshot,
dispatch,
}: {
readonly snapshot: OrchestrationReadModel;
readonly dispatch: (
command: ProjectCliDispatchCommand,
) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;
}) {
const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand(flags.workspaceRoot);
const existingProject = snapshot.projects.find(
(project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot,
);
if (existingProject) {
return yield* new ProjectCommandError({
message: `An active project already exists for '${workspaceRoot}'.`,
});
}
const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title));
const projectId = ProjectId.make(crypto.randomUUID());
yield* dispatch({
type: "project.create",
commandId: CommandId.make(crypto.randomUUID()),
projectId,
title,
workspaceRoot,
defaultModelSelection: getAutoBootstrapDefaultModelSelection(),
createdAt: DateTime.formatIso(yield* DateTime.now),
});
return `Added project ${projectId} (${title}) at ${workspaceRoot}.`;
}),
),
),
);
const projectRemoveCommand = Command.make("remove", {
...projectLocationFlags,
project: Argument.string("project").pipe(
Argument.withDescription("Project id or workspace root to remove."),
),
}).pipe(
Command.withDescription("Remove a project."),
Command.withHandler((flags) =>
runProjectMutation(
flags,
Effect.fn("projectRemoveMutation")(function* ({
snapshot,
dispatch,
}: {
readonly snapshot: OrchestrationReadModel;
readonly dispatch: (
command: ProjectCliDispatchCommand,
) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;
}) {
const project = yield* findActiveProjectTarget({
snapshot,
identifier: flags.project,
});
yield* dispatch({
type: "project.delete",
commandId: CommandId.make(crypto.randomUUID()),
projectId: project.id,
});
return `Removed project ${project.id} (${project.title}).`;
}),
),
),
);
const projectRenameCommand = Command.make("rename", {
...projectLocationFlags,
project: Argument.string("project").pipe(
Argument.withDescription("Project id or workspace root to rename."),
),
title: Argument.string("title").pipe(Argument.withDescription("New project title.")),
}).pipe(
Command.withDescription("Rename a project."),
Command.withHandler((flags) =>
runProjectMutation(
flags,
Effect.fn("projectRenameMutation")(function* ({
snapshot,
dispatch,
}: {
readonly snapshot: OrchestrationReadModel;
readonly dispatch: (
command: ProjectCliDispatchCommand,
) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;
}) {
const project = yield* findActiveProjectTarget({
snapshot,
identifier: flags.project,
});
const nextTitle = yield* resolveProjectTitle(project.workspaceRoot, flags.title);
if (nextTitle === project.title) {
return `Project ${project.id} is already named ${nextTitle}.`;
}
yield* dispatch({
type: "project.meta.update",
commandId: CommandId.make(crypto.randomUUID()),
projectId: project.id,
title: nextTitle,
});
return `Renamed project ${project.id} to ${nextTitle}.`;
}),
),
),
);
export const projectCommand = Command.make("project").pipe(
Command.withDescription("Manage projects."),
Command.withSubcommands([projectAddCommand, projectRemoveCommand, projectRenameCommand]),
);