import * as NetService from "@t3tools/shared/Net";import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts";import * as Config from "effect/Config";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as LogLevel from "effect/LogLevel";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import * as SchemaIssue from "effect/SchemaIssue";import * as SchemaTransformation from "effect/SchemaTransformation";import { Argument, Flag } from "effect/unstable/cli"; import { readBootstrapEnvelope } from "../bootstrap.ts";import * as ServerConfig from "../config.ts";import { expandHomePath, resolveBaseDir } from "../os-jank.ts"; export const modeFlag = Flag.choice("mode", ServerConfig.RuntimeMode.literals).pipe( Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), Flag.optional,);export const portFlag = Flag.integer("port").pipe( Flag.withSchema(PortSchema), Flag.withDescription("Port for the HTTP/WebSocket server."), Flag.optional,);export const hostFlag = Flag.string("host").pipe( Flag.withDescription("Host/interface to bind (for example 127.0.0.1, 0.0.0.0, or a Tailnet IP)."), Flag.optional,);export const baseDirFlag = Flag.string("base-dir").pipe( Flag.withDescription( "Explicit T3 Code data directory; runtime state is stored under userdata (equivalent to T3CODE_HOME).", ), Flag.optional,);export const devUrlFlag = Flag.string("dev-url").pipe( Flag.withSchema(Schema.URLFromString), Flag.withDescription("Dev web URL to proxy/redirect to (equivalent to VITE_DEV_SERVER_URL)."), Flag.optional,);export const noBrowserFlag = Flag.boolean("no-browser").pipe( Flag.withDescription("Disable automatic browser opening."), Flag.optional,);export const bootstrapFdFlag = Flag.integer("bootstrap-fd").pipe( Flag.withSchema(Schema.Int), Flag.withDescription("Read one-time bootstrap secrets from the given file descriptor."), Flag.optional,);export const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-from-cwd").pipe( Flag.withDescription( "Create a project for the current working directory on startup when missing.", ), Flag.optional,);export const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe( Flag.withDescription( "Emit server-side logs for outbound WebSocket push traffic (equivalent to T3CODE_LOG_WS_EVENTS).", ), Flag.withAlias("log-ws-events"), Flag.optional,);export const tailscaleServeFlag = Flag.boolean("tailscale-serve").pipe( Flag.withDescription( "Configure Tailscale Serve to expose this backend over HTTPS on the Tailnet.", ), Flag.optional,);export const tailscaleServePortFlag = Flag.integer("tailscale-serve-port").pipe( Flag.withSchema(PortSchema), Flag.withDescription("HTTPS port for Tailscale Serve when --tailscale-serve is enabled."), Flag.optional,); const EnvServerConfig = Config.all({ logLevel: Config.logLevel("T3CODE_LOG_LEVEL").pipe(Config.withDefault("Info")), traceMinLevel: Config.logLevel("T3CODE_TRACE_MIN_LEVEL").pipe(Config.withDefault("Info")), traceTimingEnabled: Config.boolean("T3CODE_TRACE_TIMING_ENABLED").pipe(Config.withDefault(true)), traceFile: Config.string("T3CODE_TRACE_FILE").pipe( Config.option, Config.map(Option.getOrUndefined), ), traceMaxBytes: Config.int("T3CODE_TRACE_MAX_BYTES").pipe(Config.withDefault(10 * 1024 * 1024)), traceMaxFiles: Config.int("T3CODE_TRACE_MAX_FILES").pipe(Config.withDefault(10)), traceBatchWindowMs: Config.int("T3CODE_TRACE_BATCH_WINDOW_MS").pipe(Config.withDefault(200)), otlpTracesUrl: Config.string("T3CODE_OTLP_TRACES_URL").pipe( Config.option, Config.map(Option.getOrUndefined), ), otlpMetricsUrl: Config.string("T3CODE_OTLP_METRICS_URL").pipe( Config.option, Config.map(Option.getOrUndefined), ), otlpExportIntervalMs: Config.int("T3CODE_OTLP_EXPORT_INTERVAL_MS").pipe( Config.withDefault(10_000), ), otlpServiceName: Config.string("T3CODE_OTLP_SERVICE_NAME").pipe(Config.withDefault("t3-server")), mode: Config.schema(ServerConfig.RuntimeMode, "T3CODE_MODE").pipe( Config.option, Config.map(Option.getOrUndefined), ), port: Config.port("T3CODE_PORT").pipe(Config.option, Config.map(Option.getOrUndefined)), host: Config.string("T3CODE_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)), t3Home: Config.string("T3CODE_HOME").pipe(Config.option, Config.map(Option.getOrUndefined)), devUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option, Config.map(Option.getOrUndefined)), devAllowedOrigins: Config.string("T3CODE_DEV_ALLOWED_ORIGINS").pipe( Config.withDefault(""), Config.map((value) => value .split(",") .map((entry) => entry.trim()) .filter((entry) => entry.length > 0), ), ), noBrowser: Config.boolean("T3CODE_NO_BROWSER").pipe( Config.option, Config.map(Option.getOrUndefined), ), bootstrapFd: Config.int("T3CODE_BOOTSTRAP_FD").pipe( Config.option, Config.map(Option.getOrUndefined), ), autoBootstrapProjectFromCwd: Config.boolean("T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe( Config.option, Config.map(Option.getOrUndefined), ), logWebSocketEvents: Config.boolean("T3CODE_LOG_WS_EVENTS").pipe( Config.option, Config.map(Option.getOrUndefined), ), tailscaleServeEnabled: Config.boolean("T3CODE_TAILSCALE_SERVE").pipe( Config.option, Config.map(Option.getOrUndefined), ), tailscaleServePort: Config.port("T3CODE_TAILSCALE_SERVE_PORT").pipe( Config.option, Config.map(Option.getOrUndefined), ),}); export interface CliServerFlags { readonly mode: Option.Option<ServerConfig.RuntimeMode>; readonly port: Option.Option<number>; readonly host: Option.Option<string>; readonly baseDir: Option.Option<string>; readonly cwd: Option.Option<string>; readonly devUrl: Option.Option<URL>; readonly noBrowser: Option.Option<boolean>; readonly bootstrapFd: Option.Option<number>; readonly autoBootstrapProjectFromCwd: Option.Option<boolean>; readonly logWebSocketEvents: Option.Option<boolean>; readonly tailscaleServeEnabled: Option.Option<boolean>; readonly tailscaleServePort: Option.Option<number>;} export interface CliAuthLocationFlags { readonly baseDir: Option.Option<string>; readonly devUrl?: Option.Option<URL>;} export const sharedServerLocationFlags = { baseDir: baseDirFlag, devUrl: devUrlFlag,} as const; export const projectLocationFlags = { baseDir: baseDirFlag,} as const; export const sharedServerCommandFlags = { mode: modeFlag, port: portFlag, host: hostFlag, baseDir: baseDirFlag, cwd: Argument.string("cwd").pipe( Argument.withDescription( "Working directory for provider sessions (defaults to the current directory).", ), Argument.optional, ), devUrl: devUrlFlag, noBrowser: noBrowserFlag, bootstrapFd: bootstrapFdFlag, autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag, logWebSocketEvents: logWebSocketEventsFlag, tailscaleServeEnabled: tailscaleServeFlag, tailscaleServePort: tailscaleServePortFlag,} as const; export const authLocationFlags = sharedServerLocationFlags; const resolveOptionPrecedence = <Value>( ...values: ReadonlyArray<Option.Option<Value>>): Option.Option<Value> => Option.firstSomeOf(values); const loadPersistedObservabilitySettings = Effect.fn(function* (settingsPath: string) { const fs = yield* FileSystem.FileSystem; const exists = yield* fs.exists(settingsPath).pipe(Effect.orElseSucceed(() => false)); if (!exists) { return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined }; } const raw = yield* fs.readFileString(settingsPath).pipe(Effect.orElseSucceed(() => "")); return parsePersistedServerObservabilitySettings(raw);}); export const resolveServerConfig = ( flags: CliServerFlags, cliLogLevel: Option.Option<LogLevel.LogLevel>, options?: { readonly startupPresentation?: ServerConfig.StartupPresentation; readonly forceAutoBootstrapProjectFromCwd?: boolean; },) => Effect.gen(function* () { const { findAvailablePort } = yield* NetService.NetService; const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const env = yield* EnvServerConfig; const normalizedFlags = { mode: flags.mode ?? Option.none(), port: flags.port ?? Option.none(), host: flags.host ?? Option.none(), baseDir: flags.baseDir ?? Option.none(), cwd: flags.cwd ?? Option.none(), devUrl: flags.devUrl ?? Option.none(), noBrowser: flags.noBrowser ?? Option.none(), bootstrapFd: flags.bootstrapFd ?? Option.none(), autoBootstrapProjectFromCwd: flags.autoBootstrapProjectFromCwd ?? Option.none(), logWebSocketEvents: flags.logWebSocketEvents ?? Option.none(), tailscaleServeEnabled: flags.tailscaleServeEnabled ?? Option.none(), tailscaleServePort: flags.tailscaleServePort ?? Option.none(), } satisfies CliServerFlags; const bootstrapFd = Option.getOrUndefined(normalizedFlags.bootstrapFd) ?? env.bootstrapFd; const bootstrapEnvelope = bootstrapFd !== undefined ? yield* readBootstrapEnvelope(DesktopBackendBootstrap, bootstrapFd) : Option.none(); const bootstrap = Option.getOrUndefined(bootstrapEnvelope); const mode: ServerConfig.RuntimeMode = Option.getOrElse( resolveOptionPrecedence( normalizedFlags.mode, Option.fromUndefinedOr(env.mode), Option.fromUndefinedOr(bootstrap?.mode), ), () => "web", ); const port = yield* Option.match( resolveOptionPrecedence( normalizedFlags.port, Option.fromUndefinedOr(env.port), Option.fromUndefinedOr(bootstrap?.port), ), { onSome: (value) => Effect.succeed(value), onNone: () => { if (mode === "desktop") { return Effect.succeed(ServerConfig.DEFAULT_PORT); } return findAvailablePort(ServerConfig.DEFAULT_PORT); }, }, ); const devUrl = Option.getOrElse( resolveOptionPrecedence(normalizedFlags.devUrl, Option.fromUndefinedOr(env.devUrl)), () => undefined, ); const explicitBaseDir = resolveOptionPrecedence( normalizedFlags.baseDir, Option.fromUndefinedOr(env.t3Home), ).pipe(Option.filter((value) => value.trim().length > 0)); const baseDir = yield* resolveBaseDir( Option.getOrUndefined( resolveOptionPrecedence(explicitBaseDir, Option.fromUndefinedOr(bootstrap?.t3Home)), ), ); const rawCwd = Option.getOrElse(normalizedFlags.cwd, () => process.cwd()); const cwd = path.resolve(yield* expandHomePath(rawCwd.trim())); yield* fs.makeDirectory(cwd, { recursive: true }); const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl, { baseDirIsExplicit: Option.isSome(explicitBaseDir), }); yield* ServerConfig.ensureServerDirectories(derivedPaths); const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings( derivedPaths.settingsPath, ); const serverTracePath = env.traceFile ?? derivedPaths.serverTracePath; yield* fs.makeDirectory(path.dirname(serverTracePath), { recursive: true }); const startupPresentation = options?.startupPresentation ?? "browser"; const isHeadlessStartup = startupPresentation === "headless"; const noBrowser = Option.getOrElse( resolveOptionPrecedence( isHeadlessStartup ? Option.some(true) : Option.none(), normalizedFlags.noBrowser, Option.fromUndefinedOr(env.noBrowser), Option.fromUndefinedOr(bootstrap?.noBrowser), ), () => mode === "desktop", ); const desktopBootstrapToken = bootstrap?.desktopBootstrapToken; const autoBootstrapProjectFromCwd = Option.getOrElse( resolveOptionPrecedence( Option.fromUndefinedOr(options?.forceAutoBootstrapProjectFromCwd), isHeadlessStartup ? Option.some(false) : Option.none(), normalizedFlags.autoBootstrapProjectFromCwd, Option.fromUndefinedOr(env.autoBootstrapProjectFromCwd), ), () => mode === "web", ); const logWebSocketEvents = Option.getOrElse( resolveOptionPrecedence( normalizedFlags.logWebSocketEvents, Option.fromUndefinedOr(env.logWebSocketEvents), ), () => Boolean(devUrl), ); const tailscaleServeEnabled = Option.getOrElse( resolveOptionPrecedence( normalizedFlags.tailscaleServeEnabled, Option.fromUndefinedOr(env.tailscaleServeEnabled), Option.fromUndefinedOr(bootstrap?.tailscaleServeEnabled), ), () => false, ); const tailscaleServePort = Option.getOrElse( resolveOptionPrecedence( normalizedFlags.tailscaleServePort, Option.fromUndefinedOr(env.tailscaleServePort), Option.fromUndefinedOr(bootstrap?.tailscaleServePort), ), () => 443, ); const staticDir = devUrl ? undefined : yield* ServerConfig.resolveStaticDir(); const host = Option.getOrElse( resolveOptionPrecedence( normalizedFlags.host, Option.fromUndefinedOr(env.host), Option.fromUndefinedOr(bootstrap?.host), ), () => (mode === "desktop" ? "127.0.0.1" : undefined), ); const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel); const config: ServerConfig.ServerConfig["Service"] = { logLevel, traceMinLevel: env.traceMinLevel, traceTimingEnabled: env.traceTimingEnabled, traceBatchWindowMs: env.traceBatchWindowMs, traceMaxBytes: env.traceMaxBytes, traceMaxFiles: env.traceMaxFiles, otlpTracesUrl: env.otlpTracesUrl ?? bootstrap?.otlpTracesUrl ?? persistedObservabilitySettings.otlpTracesUrl, otlpMetricsUrl: env.otlpMetricsUrl ?? bootstrap?.otlpMetricsUrl ?? persistedObservabilitySettings.otlpMetricsUrl, otlpExportIntervalMs: env.otlpExportIntervalMs, otlpServiceName: env.otlpServiceName, mode, port, cwd, baseDir, ...derivedPaths, serverTracePath, host, staticDir, devUrl, devAllowedOrigins: env.devAllowedOrigins, noBrowser, startupPresentation, desktopBootstrapToken, autoBootstrapProjectFromCwd, logWebSocketEvents, tailscaleServeEnabled, tailscaleServePort, }; return config; }); export const resolveCliAuthConfig = ( flags: CliAuthLocationFlags, cliLogLevel: Option.Option<LogLevel.LogLevel>,) => resolveServerConfig( { mode: Option.none(), port: Option.none(), host: Option.none(), baseDir: flags.baseDir, cwd: Option.none(), devUrl: flags.devUrl ?? Option.none(), noBrowser: Option.none(), bootstrapFd: Option.none(), autoBootstrapProjectFromCwd: Option.none(), logWebSocketEvents: Option.none(), tailscaleServeEnabled: Option.none(), tailscaleServePort: Option.none(), }, cliLogLevel, ); const DurationShorthandPattern = /^(?<value>\d+)(?<unit>ms|s|m|h|d|w)$/i; const parseDurationInput = (value: string): Duration.Duration | null => { const trimmed = value.trim(); if (trimmed.length === 0) return null; const shorthand = DurationShorthandPattern.exec(trimmed); const normalizedInput = shorthand?.groups ? (() => { const amountText = shorthand.groups.value; const unitText = shorthand.groups.unit; if (typeof amountText !== "string" || typeof unitText !== "string") { return null; } const amount = Number.parseInt(amountText, 10); if (!Number.isFinite(amount)) return null; switch (unitText.toLowerCase()) { case "ms": return `${amount} millis`; case "s": return `${amount} seconds`; case "m": return `${amount} minutes`; case "h": return `${amount} hours`; case "d": return `${amount} days`; case "w": return `${amount} weeks`; default: return null; } })() : (trimmed as Duration.Input); if (normalizedInput === null) return null; const decoded = Duration.fromInput(normalizedInput as Duration.Input); return Option.isSome(decoded) ? decoded.value : null;}; export const DurationFromString = Schema.String.pipe( Schema.decodeTo( Schema.Duration, SchemaTransformation.transformOrFail({ decode: (value) => { const duration = parseDurationInput(value); if (duration !== null) { return Effect.succeed(duration); } return Effect.fail( new SchemaIssue.InvalidValue(Option.some(value), { message: "Invalid duration. Use values like 5m, 1h, 30d, or 15 minutes.", }), ); }, encode: (duration) => Effect.succeed(Duration.format(duration)), }), ),);
import * as NetService from "@t3tools/shared/Net";
import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";
import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts";
import * as Config from "effect/Config";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as LogLevel from "effect/LogLevel";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as SchemaIssue from "effect/SchemaIssue";
import * as SchemaTransformation from "effect/SchemaTransformation";
import { Argument, Flag } from "effect/unstable/cli";
import { readBootstrapEnvelope } from "../bootstrap.ts";
import * as ServerConfig from "../config.ts";
import { expandHomePath, resolveBaseDir } from "../os-jank.ts";
export const modeFlag = Flag.choice("mode", ServerConfig.RuntimeMode.literals).pipe(
Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
Flag.optional,
);
export const portFlag = Flag.integer("port").pipe(
Flag.withSchema(PortSchema),
Flag.withDescription("Port for the HTTP/WebSocket server."),
Flag.optional,
);
export const hostFlag = Flag.string("host").pipe(
Flag.withDescription("Host/interface to bind (for example 127.0.0.1, 0.0.0.0, or a Tailnet IP)."),
Flag.optional,
);
export const baseDirFlag = Flag.string("base-dir").pipe(
Flag.withDescription(
"Explicit T3 Code data directory; runtime state is stored under userdata (equivalent to T3CODE_HOME).",
),
Flag.optional,
);
export const devUrlFlag = Flag.string("dev-url").pipe(
Flag.withSchema(Schema.URLFromString),
Flag.withDescription("Dev web URL to proxy/redirect to (equivalent to VITE_DEV_SERVER_URL)."),
Flag.optional,
);
export const noBrowserFlag = Flag.boolean("no-browser").pipe(
Flag.withDescription("Disable automatic browser opening."),
Flag.optional,
);
export const bootstrapFdFlag = Flag.integer("bootstrap-fd").pipe(
Flag.withSchema(Schema.Int),
Flag.withDescription("Read one-time bootstrap secrets from the given file descriptor."),
Flag.optional,
);
export const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-from-cwd").pipe(
Flag.withDescription(
"Create a project for the current working directory on startup when missing.",
),
Flag.optional,
);
export const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe(
Flag.withDescription(
"Emit server-side logs for outbound WebSocket push traffic (equivalent to T3CODE_LOG_WS_EVENTS).",
),
Flag.withAlias("log-ws-events"),
Flag.optional,
);
export const tailscaleServeFlag = Flag.boolean("tailscale-serve").pipe(
Flag.withDescription(
"Configure Tailscale Serve to expose this backend over HTTPS on the Tailnet.",
),
Flag.optional,
);
export const tailscaleServePortFlag = Flag.integer("tailscale-serve-port").pipe(
Flag.withSchema(PortSchema),
Flag.withDescription("HTTPS port for Tailscale Serve when --tailscale-serve is enabled."),
Flag.optional,
);
const EnvServerConfig = Config.all({
logLevel: Config.logLevel("T3CODE_LOG_LEVEL").pipe(Config.withDefault("Info")),
traceMinLevel: Config.logLevel("T3CODE_TRACE_MIN_LEVEL").pipe(Config.withDefault("Info")),
traceTimingEnabled: Config.boolean("T3CODE_TRACE_TIMING_ENABLED").pipe(Config.withDefault(true)),
traceFile: Config.string("T3CODE_TRACE_FILE").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
traceMaxBytes: Config.int("T3CODE_TRACE_MAX_BYTES").pipe(Config.withDefault(10 * 1024 * 1024)),
traceMaxFiles: Config.int("T3CODE_TRACE_MAX_FILES").pipe(Config.withDefault(10)),
traceBatchWindowMs: Config.int("T3CODE_TRACE_BATCH_WINDOW_MS").pipe(Config.withDefault(200)),
otlpTracesUrl: Config.string("T3CODE_OTLP_TRACES_URL").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
otlpMetricsUrl: Config.string("T3CODE_OTLP_METRICS_URL").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
otlpExportIntervalMs: Config.int("T3CODE_OTLP_EXPORT_INTERVAL_MS").pipe(
Config.withDefault(10_000),
),
otlpServiceName: Config.string("T3CODE_OTLP_SERVICE_NAME").pipe(Config.withDefault("t3-server")),
mode: Config.schema(ServerConfig.RuntimeMode, "T3CODE_MODE").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
port: Config.port("T3CODE_PORT").pipe(Config.option, Config.map(Option.getOrUndefined)),
host: Config.string("T3CODE_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)),
t3Home: Config.string("T3CODE_HOME").pipe(Config.option, Config.map(Option.getOrUndefined)),
devUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option, Config.map(Option.getOrUndefined)),
devAllowedOrigins: Config.string("T3CODE_DEV_ALLOWED_ORIGINS").pipe(
Config.withDefault(""),
Config.map((value) =>
value
.split(",")
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0),
),
),
noBrowser: Config.boolean("T3CODE_NO_BROWSER").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
bootstrapFd: Config.int("T3CODE_BOOTSTRAP_FD").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
autoBootstrapProjectFromCwd: Config.boolean("T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
logWebSocketEvents: Config.boolean("T3CODE_LOG_WS_EVENTS").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
tailscaleServeEnabled: Config.boolean("T3CODE_TAILSCALE_SERVE").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
tailscaleServePort: Config.port("T3CODE_TAILSCALE_SERVE_PORT").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
});
export interface CliServerFlags {
readonly mode: Option.Option<ServerConfig.RuntimeMode>;
readonly port: Option.Option<number>;
readonly host: Option.Option<string>;
readonly baseDir: Option.Option<string>;
readonly cwd: Option.Option<string>;
readonly devUrl: Option.Option<URL>;
readonly noBrowser: Option.Option<boolean>;
readonly bootstrapFd: Option.Option<number>;
readonly autoBootstrapProjectFromCwd: Option.Option<boolean>;
readonly logWebSocketEvents: Option.Option<boolean>;
readonly tailscaleServeEnabled: Option.Option<boolean>;
readonly tailscaleServePort: Option.Option<number>;
}
export interface CliAuthLocationFlags {
readonly baseDir: Option.Option<string>;
readonly devUrl?: Option.Option<URL>;
}
export const sharedServerLocationFlags = {
baseDir: baseDirFlag,
devUrl: devUrlFlag,
} as const;
export const projectLocationFlags = {
baseDir: baseDirFlag,
} as const;
export const sharedServerCommandFlags = {
mode: modeFlag,
port: portFlag,
host: hostFlag,
baseDir: baseDirFlag,
cwd: Argument.string("cwd").pipe(
Argument.withDescription(
"Working directory for provider sessions (defaults to the current directory).",
),
Argument.optional,
),
devUrl: devUrlFlag,
noBrowser: noBrowserFlag,
bootstrapFd: bootstrapFdFlag,
autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag,
logWebSocketEvents: logWebSocketEventsFlag,
tailscaleServeEnabled: tailscaleServeFlag,
tailscaleServePort: tailscaleServePortFlag,
} as const;
export const authLocationFlags = sharedServerLocationFlags;
const resolveOptionPrecedence = <Value>(
...values: ReadonlyArray<Option.Option<Value>>
): Option.Option<Value> => Option.firstSomeOf(values);
const loadPersistedObservabilitySettings = Effect.fn(function* (settingsPath: string) {
const fs = yield* FileSystem.FileSystem;
const exists = yield* fs.exists(settingsPath).pipe(Effect.orElseSucceed(() => false));
if (!exists) {
return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined };
}
const raw = yield* fs.readFileString(settingsPath).pipe(Effect.orElseSucceed(() => ""));
return parsePersistedServerObservabilitySettings(raw);
});
export const resolveServerConfig = (
flags: CliServerFlags,
cliLogLevel: Option.Option<LogLevel.LogLevel>,
options?: {
readonly startupPresentation?: ServerConfig.StartupPresentation;
readonly forceAutoBootstrapProjectFromCwd?: boolean;
},
) =>
Effect.gen(function* () {
const { findAvailablePort } = yield* NetService.NetService;
const path = yield* Path.Path;
const fs = yield* FileSystem.FileSystem;
const env = yield* EnvServerConfig;
const normalizedFlags = {
mode: flags.mode ?? Option.none(),
port: flags.port ?? Option.none(),
host: flags.host ?? Option.none(),
baseDir: flags.baseDir ?? Option.none(),
cwd: flags.cwd ?? Option.none(),
devUrl: flags.devUrl ?? Option.none(),
noBrowser: flags.noBrowser ?? Option.none(),
bootstrapFd: flags.bootstrapFd ?? Option.none(),
autoBootstrapProjectFromCwd: flags.autoBootstrapProjectFromCwd ?? Option.none(),
logWebSocketEvents: flags.logWebSocketEvents ?? Option.none(),
tailscaleServeEnabled: flags.tailscaleServeEnabled ?? Option.none(),
tailscaleServePort: flags.tailscaleServePort ?? Option.none(),
} satisfies CliServerFlags;
const bootstrapFd = Option.getOrUndefined(normalizedFlags.bootstrapFd) ?? env.bootstrapFd;
const bootstrapEnvelope =
bootstrapFd !== undefined
? yield* readBootstrapEnvelope(DesktopBackendBootstrap, bootstrapFd)
: Option.none();
const bootstrap = Option.getOrUndefined(bootstrapEnvelope);
const mode: ServerConfig.RuntimeMode = Option.getOrElse(
resolveOptionPrecedence(
normalizedFlags.mode,
Option.fromUndefinedOr(env.mode),
Option.fromUndefinedOr(bootstrap?.mode),
),
() => "web",
);
const port = yield* Option.match(
resolveOptionPrecedence(
normalizedFlags.port,
Option.fromUndefinedOr(env.port),
Option.fromUndefinedOr(bootstrap?.port),
),
{
onSome: (value) => Effect.succeed(value),
onNone: () => {
if (mode === "desktop") {
return Effect.succeed(ServerConfig.DEFAULT_PORT);
}
return findAvailablePort(ServerConfig.DEFAULT_PORT);
},
},
);
const devUrl = Option.getOrElse(
resolveOptionPrecedence(normalizedFlags.devUrl, Option.fromUndefinedOr(env.devUrl)),
() => undefined,
);
const explicitBaseDir = resolveOptionPrecedence(
normalizedFlags.baseDir,
Option.fromUndefinedOr(env.t3Home),
).pipe(Option.filter((value) => value.trim().length > 0));
const baseDir = yield* resolveBaseDir(
Option.getOrUndefined(
resolveOptionPrecedence(explicitBaseDir, Option.fromUndefinedOr(bootstrap?.t3Home)),
),
);
const rawCwd = Option.getOrElse(normalizedFlags.cwd, () => process.cwd());
const cwd = path.resolve(yield* expandHomePath(rawCwd.trim()));
yield* fs.makeDirectory(cwd, { recursive: true });
const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl, {
baseDirIsExplicit: Option.isSome(explicitBaseDir),
});
yield* ServerConfig.ensureServerDirectories(derivedPaths);
const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings(
derivedPaths.settingsPath,
);
const serverTracePath = env.traceFile ?? derivedPaths.serverTracePath;
yield* fs.makeDirectory(path.dirname(serverTracePath), { recursive: true });
const startupPresentation = options?.startupPresentation ?? "browser";
const isHeadlessStartup = startupPresentation === "headless";
const noBrowser = Option.getOrElse(
resolveOptionPrecedence(
isHeadlessStartup ? Option.some(true) : Option.none(),
normalizedFlags.noBrowser,
Option.fromUndefinedOr(env.noBrowser),
Option.fromUndefinedOr(bootstrap?.noBrowser),
),
() => mode === "desktop",
);
const desktopBootstrapToken = bootstrap?.desktopBootstrapToken;
const autoBootstrapProjectFromCwd = Option.getOrElse(
resolveOptionPrecedence(
Option.fromUndefinedOr(options?.forceAutoBootstrapProjectFromCwd),
isHeadlessStartup ? Option.some(false) : Option.none(),
normalizedFlags.autoBootstrapProjectFromCwd,
Option.fromUndefinedOr(env.autoBootstrapProjectFromCwd),
),
() => mode === "web",
);
const logWebSocketEvents = Option.getOrElse(
resolveOptionPrecedence(
normalizedFlags.logWebSocketEvents,
Option.fromUndefinedOr(env.logWebSocketEvents),
),
() => Boolean(devUrl),
);
const tailscaleServeEnabled = Option.getOrElse(
resolveOptionPrecedence(
normalizedFlags.tailscaleServeEnabled,
Option.fromUndefinedOr(env.tailscaleServeEnabled),
Option.fromUndefinedOr(bootstrap?.tailscaleServeEnabled),
),
() => false,
);
const tailscaleServePort = Option.getOrElse(
resolveOptionPrecedence(
normalizedFlags.tailscaleServePort,
Option.fromUndefinedOr(env.tailscaleServePort),
Option.fromUndefinedOr(bootstrap?.tailscaleServePort),
),
() => 443,
);
const staticDir = devUrl ? undefined : yield* ServerConfig.resolveStaticDir();
const host = Option.getOrElse(
resolveOptionPrecedence(
normalizedFlags.host,
Option.fromUndefinedOr(env.host),
Option.fromUndefinedOr(bootstrap?.host),
),
() => (mode === "desktop" ? "127.0.0.1" : undefined),
);
const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel);
const config: ServerConfig.ServerConfig["Service"] = {
logLevel,
traceMinLevel: env.traceMinLevel,
traceTimingEnabled: env.traceTimingEnabled,
traceBatchWindowMs: env.traceBatchWindowMs,
traceMaxBytes: env.traceMaxBytes,
traceMaxFiles: env.traceMaxFiles,
otlpTracesUrl:
env.otlpTracesUrl ??
bootstrap?.otlpTracesUrl ??
persistedObservabilitySettings.otlpTracesUrl,
otlpMetricsUrl:
env.otlpMetricsUrl ??
bootstrap?.otlpMetricsUrl ??
persistedObservabilitySettings.otlpMetricsUrl,
otlpExportIntervalMs: env.otlpExportIntervalMs,
otlpServiceName: env.otlpServiceName,
mode,
port,
cwd,
baseDir,
...derivedPaths,
serverTracePath,
host,
staticDir,
devUrl,
devAllowedOrigins: env.devAllowedOrigins,
noBrowser,
startupPresentation,
desktopBootstrapToken,
autoBootstrapProjectFromCwd,
logWebSocketEvents,
tailscaleServeEnabled,
tailscaleServePort,
};
return config;
});
export const resolveCliAuthConfig = (
flags: CliAuthLocationFlags,
cliLogLevel: Option.Option<LogLevel.LogLevel>,
) =>
resolveServerConfig(
{
mode: Option.none(),
port: Option.none(),
host: Option.none(),
baseDir: flags.baseDir,
cwd: Option.none(),
devUrl: flags.devUrl ?? Option.none(),
noBrowser: Option.none(),
bootstrapFd: Option.none(),
autoBootstrapProjectFromCwd: Option.none(),
logWebSocketEvents: Option.none(),
tailscaleServeEnabled: Option.none(),
tailscaleServePort: Option.none(),
},
cliLogLevel,
);
const DurationShorthandPattern = /^(?<value>\d+)(?<unit>ms|s|m|h|d|w)$/i;
const parseDurationInput = (value: string): Duration.Duration | null => {
const trimmed = value.trim();
if (trimmed.length === 0) return null;
const shorthand = DurationShorthandPattern.exec(trimmed);
const normalizedInput = shorthand?.groups
? (() => {
const amountText = shorthand.groups.value;
const unitText = shorthand.groups.unit;
if (typeof amountText !== "string" || typeof unitText !== "string") {
return null;
}
const amount = Number.parseInt(amountText, 10);
if (!Number.isFinite(amount)) return null;
switch (unitText.toLowerCase()) {
case "ms":
return `${amount} millis`;
case "s":
return `${amount} seconds`;
case "m":
return `${amount} minutes`;
case "h":
return `${amount} hours`;
case "d":
return `${amount} days`;
case "w":
return `${amount} weeks`;
default:
return null;
}
})()
: (trimmed as Duration.Input);
if (normalizedInput === null) return null;
const decoded = Duration.fromInput(normalizedInput as Duration.Input);
return Option.isSome(decoded) ? decoded.value : null;
};
export const DurationFromString = Schema.String.pipe(
Schema.decodeTo(
Schema.Duration,
SchemaTransformation.transformOrFail({
decode: (value) => {
const duration = parseDurationInput(value);
if (duration !== null) {
return Effect.succeed(duration);
}
return Effect.fail(
new SchemaIssue.InvalidValue(Option.some(value), {
message: "Invalid duration. Use values like 5m, 1h, 30d, or 15 minutes.",
}),
);
},
encode: (duration) => Effect.succeed(Duration.format(duration)),
}),
),
);