import { AuthRelayWriteScope, EnvironmentHttpApi, type RelayClientInstallProgressEvent, type RelayClientInstallProgressStage,} from "@t3tools/contracts";import { RelayOkResponse } from "@t3tools/contracts/relay";import * as RelayClient from "@t3tools/shared/relayClient";import * as Console from "effect/Console";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 References from "effect/References";import { Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse,} from "effect/unstable/http";import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";import * as ServerSecretStore from "../auth/ServerSecretStore.ts";import * as CliState from "../cloud/CliState.ts";import * as CliTokenManager from "../cloud/CliTokenManager.ts";import { CLOUD_LINKED_USER_ID, RELAY_URL_SECRET } from "../cloud/config.ts";import { relayUrlConfig } from "../cloud/publicConfig.ts";import { ServerConfig } from "../config.ts";import { ServerEnvironmentLive } from "../environment/Layers/ServerEnvironment.ts";import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts";import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts";import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; const jsonFlag = Flag.boolean("json").pipe( Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false),); function bytesToString(value: Uint8Array): string { return new TextDecoder().decode(value);} interface CloudCliStatus { readonly desired: boolean; readonly authenticated: boolean; readonly linked: boolean; readonly cloudUserId: string | null; readonly relayUrl: string | null; readonly relayClient: RelayClient.RelayClientStatus;} function formatRelayClientStatus(executable: RelayClient.RelayClientStatus): ReadonlyArray<string> { switch (executable.status) { case "available": { const source = executable.source === "path" ? "PATH" : executable.source === "managed" ? "managed install" : "configured override"; return [ ` Relay client: available via ${source}`, ` Path: ${executable.executablePath}`, ` Version: ${executable.version}`, ]; } case "missing": return [" Relay client: not installed"]; case "unsupported": return [ ` Relay client: unsupported on ${executable.platform}-${executable.arch}`, ` Managed version: ${executable.version}`, ]; }} function formatCloudStatus(status: CloudCliStatus, options?: { readonly json?: boolean }): string { if (options?.json) { return JSON.stringify(status, null, 2); } const provisioned = status.linked ? "provisioned" : status.desired && status.authenticated ? "pending server startup" : "not provisioned"; const nextStep = !status.authenticated ? "Run `t3 cloud link` to authorize and enable cloud exposure." : !status.desired ? "Run `t3 cloud link` to enable cloud exposure." : !status.linked ? "Start T3 to provision the environment link and launch its managed tunnel." : undefined; return [ "T3 Cloud", ` Exposure: ${status.desired ? "enabled" : "disabled"}`, ` Authorization: ${status.authenticated ? "stored credential" : "missing"}`, ` Environment link: ${provisioned}`, ` Relay: ${status.relayUrl ?? "not provisioned"}`, ...formatRelayClientStatus(status.relayClient), ...(nextStep ? ["", `Next: ${nextStep}`] : []), ].join("\n");} const CLOUD_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(5); const confirmRelayClientInstall = (version: string) => Prompt.run( Prompt.confirm({ message: `The T3 relay client is required for T3 Cloud exposure. Download and install version ${version}?`, initial: false, }), ); function relayClientInstallProgressMessage(stage: RelayClientInstallProgressStage): string { switch (stage) { case "checking": return "Checking existing installation"; case "waiting_for_lock": return "Waiting for installation lock"; case "downloading": return "Downloading"; case "verifying": return "Verifying download"; case "installing": return "Installing"; case "validating": return "Validating executable"; case "activating": return "Activating installation"; }} const reportRelayClientInstallProgress = (event: RelayClientInstallProgressEvent) => event.type === "progress" ? Console.log(`Relay client: ${relayClientInstallProgressMessage(event.stage)}...`) : Effect.void; export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_client_for_link")( function* <ConfirmError, ConfirmContext>( relayClient: RelayClient.RelayClientShape, confirmInstall: (version: string) => Effect.Effect<boolean, ConfirmError, ConfirmContext>, reportProgress: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>, ) { const executable = yield* relayClient.resolve; if (executable.status === "available") { return Option.some(executable); } if (executable.status === "unsupported") { return Option.some(yield* relayClient.installWithProgress(reportProgress)); } if (!(yield* confirmInstall(executable.version))) { return Option.none(); } return Option.some(yield* relayClient.installWithProgress(reportProgress)); },); const withCloudCliSessionToken = <A, E, R>( environmentAuth: EnvironmentAuth.EnvironmentAuthShape, run: (token: string) => Effect.Effect<A, E, R>,) => Effect.acquireUseRelease( environmentAuth.issueSession({ scopes: [AuthRelayWriteScope], subject: "cloud-cli", label: "t3 cloud cli", }), (issued) => run(issued.token), (issued) => environmentAuth.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })), ); type LiveCloudActionResult = | { readonly status: "not-running" } | { readonly status: "succeeded" } | { readonly status: "failed"; readonly cause: unknown }; const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () { const config = yield* ServerConfig; const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); if (Option.isNone(runtimeState)) { return { status: "not-running" } satisfies LiveCloudActionResult; } const environmentAuth = yield* EnvironmentAuth.EnvironmentAuth; const result = yield* Effect.exit( withCloudCliSessionToken(environmentAuth, (token) => HttpApiClient.make(EnvironmentHttpApi, { baseUrl: runtimeState.value.origin, }).pipe( Effect.flatMap((client) => client.cloud.unlink({ headers: { authorization: `Bearer ${token}` } }), ), Effect.timeout(CLOUD_CLI_LIVE_SERVER_TIMEOUT), ), ), ); return Exit.isSuccess(result) ? ({ status: "succeeded" } satisfies LiveCloudActionResult) : ({ status: "failed", cause: result.cause } satisfies LiveCloudActionResult);}); type RelayUnlinkResult = | { readonly status: "not-authenticated" } | { readonly status: "revoked" } | { readonly status: "not-linked" }; const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(function* () { const tokens = yield* CliTokenManager.CloudCliTokenManager; const token = yield* tokens.getExisting; if (Option.isNone(token)) { return { status: "not-authenticated" } satisfies RelayUnlinkResult; } const environment = yield* ServerEnvironment; const environmentId = yield* environment.getEnvironmentId; const relayUrl = yield* relayUrlConfig; const httpClient = yield* HttpClient.HttpClient; const response = yield* HttpClientRequest.delete( `${relayUrl}/v1/client/environment-links/${encodeURIComponent(environmentId)}`, ).pipe( HttpClientRequest.bearerToken(token.value.accessToken), httpClient.execute, Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.flatMap(HttpClientResponse.schemaBodyJson(RelayOkResponse)), ); return response.ok ? ({ status: "revoked" } satisfies RelayUnlinkResult) : ({ status: "not-linked" } satisfies RelayUnlinkResult);}); const disconnectCloud = Effect.fn("cloud.cli.disconnect")(function* (options: { readonly clearAuthorization: boolean;}) { yield* CliState.setCliDesiredCloudLink(false); const liveResult = yield* runLiveCloudUnlink(); const relayResult = yield* Effect.exit(unlinkRelayEnvironment()); yield* CliState.clearPersistedCloudLink; if (options.clearAuthorization) { const tokens = yield* CliTokenManager.CloudCliTokenManager; yield* tokens.clear; } if (liveResult.status === "failed") { yield* Console.warn( `T3 Cloud exposure is disabled, but the running server could not stop its tunnel: ${String(liveResult.cause)}\nRestart that server to stop the connector.`, ); } else { yield* Console.log("T3 Cloud exposure is disabled locally."); } if (Exit.isFailure(relayResult)) { yield* Console.warn( options.clearAuthorization ? `Could not revoke the relay-side environment record before signing out: ${String(relayResult.cause)}\nThe stored CLI authorization was still removed locally.` : `Could not revoke the relay-side environment record yet: ${String(relayResult.cause)}\nRun \`t3 cloud unlink\` again when the relay is reachable.`, ); } else if (relayResult.value.status === "revoked") { yield* Console.log("Revoked the relay-side environment record."); } if (options.clearAuthorization) { yield* Console.log("Signed out of T3 Cloud locally."); }}); const runCloudCommand = <A, E>( flags: { readonly baseDir: Option.Option<string> }, run: Effect.Effect< A, E, | ServerSecretStore.ServerSecretStore | CliTokenManager.CloudCliTokenManager | RelayClient.RelayClient | EnvironmentAuth.EnvironmentAuth | FileSystem.FileSystem | HttpClient.HttpClient | Prompt.Environment | ServerConfig | ServerEnvironment >, options?: { readonly quietLogs?: boolean; },) => Effect.gen(function* () { const logLevel = yield* GlobalFlag.LogLevel; const config = yield* resolveCliAuthConfig(flags, logLevel); const minimumLogLevel = options?.quietLogs ? "Error" : config.logLevel; const runtimeLayer = Layer.mergeAll( ServerSecretStore.layer, CliTokenManager.layer.pipe(Layer.provide(ServerSecretStore.layer)), RelayClient.layerCloudflared({ baseDir: config.baseDir }), EnvironmentAuth.runtimeLayer, ServerEnvironmentLive, ).pipe( Layer.provideMerge(FetchHttpClient.layer), Layer.provideMerge(Layer.succeed(ServerConfig, config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), ); return yield* run.pipe(Effect.provide(runtimeLayer)); }); const cloudLoginCommand = Command.make("login", { ...projectLocationFlags,}).pipe( Command.withDescription("Authorize the T3 Cloud CLI without enabling cloud exposure."), Command.withHandler((flags) => runCloudCommand( flags, Effect.gen(function* () { const tokens = yield* CliTokenManager.CloudCliTokenManager; yield* tokens.get; yield* Console.log("Signed in to T3 Cloud."); }), ), ),); const cloudLinkCommand = Command.make("link", { ...projectLocationFlags,}).pipe( Command.withDescription("Authorize this environment for T3 Cloud and expose it on next start."), Command.withHandler((flags) => runCloudCommand( flags, Effect.gen(function* () { const relayClient = yield* RelayClient.RelayClient; const installed = yield* acquireRelayClientForLink( relayClient, confirmRelayClientInstall, reportRelayClientInstallProgress, ); if (Option.isNone(installed)) { yield* Console.log("T3 Cloud link cancelled. The relay client was not installed."); return; } yield* Console.log( `Using relay client ${installed.value.version} from ${installed.value.executablePath}.`, ); const tokens = yield* CliTokenManager.CloudCliTokenManager; yield* tokens.get; yield* CliState.setCliDesiredCloudLink(true); yield* Console.log( "This T3 environment will be available over T3 Cloud the next time T3 starts.", ); }), ), ),); const cloudStatusCommand = Command.make("status", { ...projectLocationFlags, json: jsonFlag,}).pipe( Command.withDescription("Show persisted T3 Cloud and relay client state."), Command.withHandler((flags) => runCloudCommand( flags, Effect.gen(function* () { const secrets = yield* ServerSecretStore.ServerSecretStore; const relayClient = yield* RelayClient.RelayClient; const tokens = yield* CliTokenManager.CloudCliTokenManager; const [desired, authenticated, cloudUserId, relayUrl, executable] = yield* Effect.all( [ CliState.readCliDesiredCloudLink, tokens.hasCredential, secrets.get(CLOUD_LINKED_USER_ID), secrets.get(RELAY_URL_SECRET), relayClient.resolve, ], { concurrency: "unbounded" }, ); const status: CloudCliStatus = { desired, authenticated, linked: cloudUserId !== null, cloudUserId: cloudUserId ? bytesToString(cloudUserId) : null, relayUrl: relayUrl ? bytesToString(relayUrl) : null, relayClient: executable, }; yield* Console.log(formatCloudStatus(status, { json: flags.json })); }), { quietLogs: flags.json, }, ), ),); const cloudUnlinkCommand = Command.make("unlink", { ...projectLocationFlags,}).pipe( Command.withDescription("Disable T3 Cloud exposure while retaining the stored authorization."), Command.withHandler((flags) => runCloudCommand(flags, disconnectCloud({ clearAuthorization: false })), ),); const cloudLogoutCommand = Command.make("logout", { ...projectLocationFlags,}).pipe( Command.withDescription("Disable T3 Cloud exposure and clear the stored CLI authorization."), Command.withHandler((flags) => runCloudCommand(flags, disconnectCloud({ clearAuthorization: true })), ),); export const cloudCommand = Command.make("cloud").pipe( Command.withDescription("Manage headless T3 Cloud exposure."), Command.withSubcommands([ cloudLoginCommand, cloudLinkCommand, cloudStatusCommand, cloudUnlinkCommand, cloudLogoutCommand, ]),);
import {
AuthRelayWriteScope,
EnvironmentHttpApi,
type RelayClientInstallProgressEvent,
type RelayClientInstallProgressStage,
} from "@t3tools/contracts";
import { RelayOkResponse } from "@t3tools/contracts/relay";
import * as RelayClient from "@t3tools/shared/relayClient";
import * as Console from "effect/Console";
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 References from "effect/References";
import { Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http";
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as CliState from "../cloud/CliState.ts";
import * as CliTokenManager from "../cloud/CliTokenManager.ts";
import { CLOUD_LINKED_USER_ID, RELAY_URL_SECRET } from "../cloud/config.ts";
import { relayUrlConfig } from "../cloud/publicConfig.ts";
import { ServerConfig } from "../config.ts";
import { ServerEnvironmentLive } from "../environment/Layers/ServerEnvironment.ts";
import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts";
import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
const jsonFlag = Flag.boolean("json").pipe(
Flag.withDescription("Emit JSON instead of human-readable output."),
Flag.withDefault(false),
);
function bytesToString(value: Uint8Array): string {
return new TextDecoder().decode(value);
}
interface CloudCliStatus {
readonly desired: boolean;
readonly authenticated: boolean;
readonly linked: boolean;
readonly cloudUserId: string | null;
readonly relayUrl: string | null;
readonly relayClient: RelayClient.RelayClientStatus;
}
function formatRelayClientStatus(executable: RelayClient.RelayClientStatus): ReadonlyArray<string> {
switch (executable.status) {
case "available": {
const source =
executable.source === "path"
? "PATH"
: executable.source === "managed"
? "managed install"
: "configured override";
return [
` Relay client: available via ${source}`,
` Path: ${executable.executablePath}`,
` Version: ${executable.version}`,
];
}
case "missing":
return [" Relay client: not installed"];
case "unsupported":
return [
` Relay client: unsupported on ${executable.platform}-${executable.arch}`,
` Managed version: ${executable.version}`,
];
}
}
function formatCloudStatus(status: CloudCliStatus, options?: { readonly json?: boolean }): string {
if (options?.json) {
return JSON.stringify(status, null, 2);
}
const provisioned = status.linked
? "provisioned"
: status.desired && status.authenticated
? "pending server startup"
: "not provisioned";
const nextStep = !status.authenticated
? "Run `t3 cloud link` to authorize and enable cloud exposure."
: !status.desired
? "Run `t3 cloud link` to enable cloud exposure."
: !status.linked
? "Start T3 to provision the environment link and launch its managed tunnel."
: undefined;
return [
"T3 Cloud",
` Exposure: ${status.desired ? "enabled" : "disabled"}`,
` Authorization: ${status.authenticated ? "stored credential" : "missing"}`,
` Environment link: ${provisioned}`,
` Relay: ${status.relayUrl ?? "not provisioned"}`,
...formatRelayClientStatus(status.relayClient),
...(nextStep ? ["", `Next: ${nextStep}`] : []),
].join("\n");
}
const CLOUD_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(5);
const confirmRelayClientInstall = (version: string) =>
Prompt.run(
Prompt.confirm({
message: `The T3 relay client is required for T3 Cloud exposure. Download and install version ${version}?`,
initial: false,
}),
);
function relayClientInstallProgressMessage(stage: RelayClientInstallProgressStage): string {
switch (stage) {
case "checking":
return "Checking existing installation";
case "waiting_for_lock":
return "Waiting for installation lock";
case "downloading":
return "Downloading";
case "verifying":
return "Verifying download";
case "installing":
return "Installing";
case "validating":
return "Validating executable";
case "activating":
return "Activating installation";
}
}
const reportRelayClientInstallProgress = (event: RelayClientInstallProgressEvent) =>
event.type === "progress"
? Console.log(`Relay client: ${relayClientInstallProgressMessage(event.stage)}...`)
: Effect.void;
export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_client_for_link")(
function* <ConfirmError, ConfirmContext>(
relayClient: RelayClient.RelayClientShape,
confirmInstall: (version: string) => Effect.Effect<boolean, ConfirmError, ConfirmContext>,
reportProgress: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>,
) {
const executable = yield* relayClient.resolve;
if (executable.status === "available") {
return Option.some(executable);
}
if (executable.status === "unsupported") {
return Option.some(yield* relayClient.installWithProgress(reportProgress));
}
if (!(yield* confirmInstall(executable.version))) {
return Option.none();
}
return Option.some(yield* relayClient.installWithProgress(reportProgress));
},
);
const withCloudCliSessionToken = <A, E, R>(
environmentAuth: EnvironmentAuth.EnvironmentAuthShape,
run: (token: string) => Effect.Effect<A, E, R>,
) =>
Effect.acquireUseRelease(
environmentAuth.issueSession({
scopes: [AuthRelayWriteScope],
subject: "cloud-cli",
label: "t3 cloud cli",
}),
(issued) => run(issued.token),
(issued) => environmentAuth.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })),
);
type LiveCloudActionResult =
| { readonly status: "not-running" }
| { readonly status: "succeeded" }
| { readonly status: "failed"; readonly cause: unknown };
const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () {
const config = yield* ServerConfig;
const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
if (Option.isNone(runtimeState)) {
return { status: "not-running" } satisfies LiveCloudActionResult;
}
const environmentAuth = yield* EnvironmentAuth.EnvironmentAuth;
const result = yield* Effect.exit(
withCloudCliSessionToken(environmentAuth, (token) =>
HttpApiClient.make(EnvironmentHttpApi, {
baseUrl: runtimeState.value.origin,
}).pipe(
Effect.flatMap((client) =>
client.cloud.unlink({ headers: { authorization: `Bearer ${token}` } }),
),
Effect.timeout(CLOUD_CLI_LIVE_SERVER_TIMEOUT),
),
),
);
return Exit.isSuccess(result)
? ({ status: "succeeded" } satisfies LiveCloudActionResult)
: ({ status: "failed", cause: result.cause } satisfies LiveCloudActionResult);
});
type RelayUnlinkResult =
| { readonly status: "not-authenticated" }
| { readonly status: "revoked" }
| { readonly status: "not-linked" };
const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(function* () {
const tokens = yield* CliTokenManager.CloudCliTokenManager;
const token = yield* tokens.getExisting;
if (Option.isNone(token)) {
return { status: "not-authenticated" } satisfies RelayUnlinkResult;
}
const environment = yield* ServerEnvironment;
const environmentId = yield* environment.getEnvironmentId;
const relayUrl = yield* relayUrlConfig;
const httpClient = yield* HttpClient.HttpClient;
const response = yield* HttpClientRequest.delete(
`${relayUrl}/v1/client/environment-links/${encodeURIComponent(environmentId)}`,
).pipe(
HttpClientRequest.bearerToken(token.value.accessToken),
httpClient.execute,
Effect.flatMap(HttpClientResponse.filterStatusOk),
Effect.flatMap(HttpClientResponse.schemaBodyJson(RelayOkResponse)),
);
return response.ok
? ({ status: "revoked" } satisfies RelayUnlinkResult)
: ({ status: "not-linked" } satisfies RelayUnlinkResult);
});
const disconnectCloud = Effect.fn("cloud.cli.disconnect")(function* (options: {
readonly clearAuthorization: boolean;
}) {
yield* CliState.setCliDesiredCloudLink(false);
const liveResult = yield* runLiveCloudUnlink();
const relayResult = yield* Effect.exit(unlinkRelayEnvironment());
yield* CliState.clearPersistedCloudLink;
if (options.clearAuthorization) {
const tokens = yield* CliTokenManager.CloudCliTokenManager;
yield* tokens.clear;
}
if (liveResult.status === "failed") {
yield* Console.warn(
`T3 Cloud exposure is disabled, but the running server could not stop its tunnel: ${String(liveResult.cause)}\nRestart that server to stop the connector.`,
);
} else {
yield* Console.log("T3 Cloud exposure is disabled locally.");
}
if (Exit.isFailure(relayResult)) {
yield* Console.warn(
options.clearAuthorization
? `Could not revoke the relay-side environment record before signing out: ${String(relayResult.cause)}\nThe stored CLI authorization was still removed locally.`
: `Could not revoke the relay-side environment record yet: ${String(relayResult.cause)}\nRun \`t3 cloud unlink\` again when the relay is reachable.`,
);
} else if (relayResult.value.status === "revoked") {
yield* Console.log("Revoked the relay-side environment record.");
}
if (options.clearAuthorization) {
yield* Console.log("Signed out of T3 Cloud locally.");
}
});
const runCloudCommand = <A, E>(
flags: { readonly baseDir: Option.Option<string> },
run: Effect.Effect<
A,
E,
| ServerSecretStore.ServerSecretStore
| CliTokenManager.CloudCliTokenManager
| RelayClient.RelayClient
| EnvironmentAuth.EnvironmentAuth
| FileSystem.FileSystem
| HttpClient.HttpClient
| Prompt.Environment
| ServerConfig
| ServerEnvironment
>,
options?: {
readonly quietLogs?: boolean;
},
) =>
Effect.gen(function* () {
const logLevel = yield* GlobalFlag.LogLevel;
const config = yield* resolveCliAuthConfig(flags, logLevel);
const minimumLogLevel = options?.quietLogs ? "Error" : config.logLevel;
const runtimeLayer = Layer.mergeAll(
ServerSecretStore.layer,
CliTokenManager.layer.pipe(Layer.provide(ServerSecretStore.layer)),
RelayClient.layerCloudflared({ baseDir: config.baseDir }),
EnvironmentAuth.runtimeLayer,
ServerEnvironmentLive,
).pipe(
Layer.provideMerge(FetchHttpClient.layer),
Layer.provideMerge(Layer.succeed(ServerConfig, config)),
Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
);
return yield* run.pipe(Effect.provide(runtimeLayer));
});
const cloudLoginCommand = Command.make("login", {
...projectLocationFlags,
}).pipe(
Command.withDescription("Authorize the T3 Cloud CLI without enabling cloud exposure."),
Command.withHandler((flags) =>
runCloudCommand(
flags,
Effect.gen(function* () {
const tokens = yield* CliTokenManager.CloudCliTokenManager;
yield* tokens.get;
yield* Console.log("Signed in to T3 Cloud.");
}),
),
),
);
const cloudLinkCommand = Command.make("link", {
...projectLocationFlags,
}).pipe(
Command.withDescription("Authorize this environment for T3 Cloud and expose it on next start."),
Command.withHandler((flags) =>
runCloudCommand(
flags,
Effect.gen(function* () {
const relayClient = yield* RelayClient.RelayClient;
const installed = yield* acquireRelayClientForLink(
relayClient,
confirmRelayClientInstall,
reportRelayClientInstallProgress,
);
if (Option.isNone(installed)) {
yield* Console.log("T3 Cloud link cancelled. The relay client was not installed.");
return;
}
yield* Console.log(
`Using relay client ${installed.value.version} from ${installed.value.executablePath}.`,
);
const tokens = yield* CliTokenManager.CloudCliTokenManager;
yield* tokens.get;
yield* CliState.setCliDesiredCloudLink(true);
yield* Console.log(
"This T3 environment will be available over T3 Cloud the next time T3 starts.",
);
}),
),
),
);
const cloudStatusCommand = Command.make("status", {
...projectLocationFlags,
json: jsonFlag,
}).pipe(
Command.withDescription("Show persisted T3 Cloud and relay client state."),
Command.withHandler((flags) =>
runCloudCommand(
flags,
Effect.gen(function* () {
const secrets = yield* ServerSecretStore.ServerSecretStore;
const relayClient = yield* RelayClient.RelayClient;
const tokens = yield* CliTokenManager.CloudCliTokenManager;
const [desired, authenticated, cloudUserId, relayUrl, executable] = yield* Effect.all(
[
CliState.readCliDesiredCloudLink,
tokens.hasCredential,
secrets.get(CLOUD_LINKED_USER_ID),
secrets.get(RELAY_URL_SECRET),
relayClient.resolve,
],
{ concurrency: "unbounded" },
);
const status: CloudCliStatus = {
desired,
authenticated,
linked: cloudUserId !== null,
cloudUserId: cloudUserId ? bytesToString(cloudUserId) : null,
relayUrl: relayUrl ? bytesToString(relayUrl) : null,
relayClient: executable,
};
yield* Console.log(formatCloudStatus(status, { json: flags.json }));
}),
{
quietLogs: flags.json,
},
),
),
);
const cloudUnlinkCommand = Command.make("unlink", {
...projectLocationFlags,
}).pipe(
Command.withDescription("Disable T3 Cloud exposure while retaining the stored authorization."),
Command.withHandler((flags) =>
runCloudCommand(flags, disconnectCloud({ clearAuthorization: false })),
),
);
const cloudLogoutCommand = Command.make("logout", {
...projectLocationFlags,
}).pipe(
Command.withDescription("Disable T3 Cloud exposure and clear the stored CLI authorization."),
Command.withHandler((flags) =>
runCloudCommand(flags, disconnectCloud({ clearAuthorization: true })),
),
);
export const cloudCommand = Command.make("cloud").pipe(
Command.withDescription("Manage headless T3 Cloud exposure."),
Command.withSubcommands([
cloudLoginCommand,
cloudLinkCommand,
cloudStatusCommand,
cloudUnlinkCommand,
cloudLogoutCommand,
]),
);