UNPKG

vde-layout

Version:

Terminal multiplexer layout management tool for VDE (Vibe Coding Development Environment)

4,340 lines 152 kB
#!/usr/bin/env node
import { createRequire } from "node:module";
import { defineCommand, parseArgs, renderUsage } from "citty";
import fs from "fs-extra";
import path from "node:path";
import os from "node:os";
import * as YAML from "yaml";
import { parse } from "yaml";
import { z } from "zod";
import { createHash } from "node:crypto";
import chalk, { Chalk } from "chalk";
import { execFileSync } from "node:child_process";
import { execa } from "execa";
import { createInterface } from "node:readline/promises";
import { stdin, stdout } from "node:process";
import stringWidth from "string-width";

//#region src/utils/errors.ts
const ErrorCodes = {
	CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
	CONFIG_PARSE_ERROR: "CONFIG_PARSE_ERROR",
	CONFIG_PERMISSION_ERROR: "CONFIG_PERMISSION_ERROR",
	INVALID_PRESET: "INVALID_PRESET",
	PRESET_NOT_FOUND: "PRESET_NOT_FOUND",
	INVALID_LAYOUT: "INVALID_LAYOUT",
	INVALID_PANE: "INVALID_PANE",
	INVALID_PLAN: "INVALID_PLAN",
	MISSING_TARGET: "MISSING_TARGET",
	TMUX_NOT_RUNNING: "TMUX_NOT_RUNNING",
	TMUX_COMMAND_FAILED: "TMUX_COMMAND_FAILED",
	NOT_IN_TMUX_SESSION: "NOT_IN_TMUX_SESSION",
	TMUX_NOT_FOUND: "TMUX_NOT_FOUND",
	TMUX_NOT_INSTALLED: "TMUX_NOT_INSTALLED",
	UNSUPPORTED_TMUX_VERSION: "UNSUPPORTED_TMUX_VERSION",
	BACKEND_NOT_FOUND: "BACKEND_NOT_FOUND",
	TERMINAL_COMMAND_FAILED: "TERMINAL_COMMAND_FAILED",
	TEMPLATE_TOKEN_ERROR: "TEMPLATE_TOKEN_ERROR",
	WEZTERM_NOT_FOUND: "WEZTERM_NOT_FOUND",
	UNSUPPORTED_WEZTERM_VERSION: "UNSUPPORTED_WEZTERM_VERSION",
	USER_CANCELLED: "USER_CANCELLED",
	SPLIT_SIZE_RESOLUTION_FAILED: "SPLIT_SIZE_RESOLUTION_FAILED"
};
const createBaseError = (name, message, code, details = {}) => {
	const error = new Error(message);
	error.name = name;
	error.code = code;
	error.details = details;
	return error;
};
const createConfigError = (message, code, details = {}) => {
	return createBaseError("ConfigError", message, code, details);
};
const createValidationError = (message, code, details = {}) => {
	return createBaseError("ValidationError", message, code, details);
};
const createTmuxError = (message, code, details = {}) => {
	return createBaseError("TmuxError", message, code, details);
};
const createEnvironmentError = (message, code, details = {}) => {
	return createBaseError("EnvironmentError", message, code, details);
};
const isVDELayoutError = (error) => {
	if (typeof error !== "object" || error === null) return false;
	if (!("code" in error)) return false;
	const { code } = error;
	if (typeof code !== "string") return false;
	if (!("details" in error)) return false;
	return true;
};
const formatters = {
	[ErrorCodes.CONFIG_NOT_FOUND]: (error) => {
		const searchPaths = error.details.searchPaths;
		if (!Array.isArray(searchPaths)) return "";
		const lines = ["", "Searched in the following locations:"];
		searchPaths.forEach((location) => lines.push(`  - ${location}`));
		lines.push("", "To create a configuration file, run:");
		lines.push("  mkdir -p ~/.config/vde");
		lines.push("  echo \"presets: {}\" > ~/.config/vde/layout.yml");
		return lines.join("\n");
	},
	[ErrorCodes.NOT_IN_TMUX_SESSION]: () => {
		return "\nThis command must be run inside a tmux session.\nStart tmux first with: tmux\n";
	},
	[ErrorCodes.TMUX_NOT_INSTALLED]: () => {
		return "\ntmux is required but not installed.\nInstall tmux using your package manager:\n  - macOS: brew install tmux\n  - Ubuntu/Debian: sudo apt-get install tmux\n  - Fedora: sudo dnf install tmux\n";
	},
	[ErrorCodes.UNSUPPORTED_TMUX_VERSION]: (error) => {
		const requiredVersion = error.details.requiredVersion;
		if (typeof requiredVersion !== "string") return "";
		return `\nRequired tmux version: ${requiredVersion} or higher\n`;
	},
	[ErrorCodes.BACKEND_NOT_FOUND]: (error) => {
		const backend = typeof error.details.backend === "string" ? error.details.backend : "terminal backend";
		return `\nMissing binary: ${typeof error.details.binary === "string" ? error.details.binary : backend}${backend === "wezterm" ? [
			"",
			`${backend} is required but not installed.`,
			"Install wezterm using your package manager:",
			"  - macOS: brew install --cask wezterm",
			"  - Ubuntu/Debian: sudo apt-get install wezterm",
			"  - Fedora: sudo dnf install wezterm"
		].join("\n") : ""}`;
	},
	[ErrorCodes.WEZTERM_NOT_FOUND]: () => {
		return "\nwezterm command was not found.\nInstall wezterm using your package manager:\n  - macOS: brew install --cask wezterm\n  - Ubuntu/Debian: sudo apt-get install wezterm\n  - Fedora: sudo dnf install wezterm\n";
	},
	[ErrorCodes.UNSUPPORTED_WEZTERM_VERSION]: (error) => {
		const requiredVersion = typeof error.details.requiredVersion === "string" ? error.details.requiredVersion : "";
		const detected = typeof error.details.detectedVersion === "string" ? error.details.detectedVersion : "";
		const lines = ["", "Unsupported wezterm version detected."];
		if (detected) lines.push(`Detected version: ${detected}`);
		if (requiredVersion) lines.push(`Required version: ${requiredVersion} or higher`);
		return lines.join("\n");
	},
	[ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED]: (error) => {
		const paneId = typeof error.details.paneId === "string" ? error.details.paneId : "";
		const paneCells = typeof error.details.paneCells === "number" ? String(error.details.paneCells) : "";
		const detected = typeof error.details.detectedVersion === "string" ? error.details.detectedVersion : "";
		const required = typeof error.details.requiredVersion === "string" ? error.details.requiredVersion : "";
		const lines = ["", "Unable to resolve split size from pane dimensions."];
		if (paneId) lines.push(`Pane ID: ${paneId}`);
		if (paneCells) lines.push(`Pane cells: ${paneCells}`);
		if (detected) lines.push(`Detected version: ${detected}`);
		if (required) lines.push(`Required version: ${required}`);
		return lines.join("\n");
	}
};

//#endregion
//#region src/models/schema.ts
const WindowModeSchema = z.enum(["new-window", "current-window"]);
const TerminalBackendSchema = z.enum(["tmux", "wezterm"]);
const SELECT_UI_MODES = ["auto", "fzf"];
const SELECT_SURFACE_MODES = [
	"auto",
	"inline",
	"tmux-popup"
];
const SelectUiModeSchema = z.enum(SELECT_UI_MODES);
const SelectSurfaceModeSchema = z.enum(SELECT_SURFACE_MODES);
const SelectorFzfSchema = z.object({ extraArgs: z.array(z.string().min(1)).optional() }).strict();
const HooksSchema = z.object({ afterApply: z.string().min(1).optional() }).strict();
const SelectorDefaultsSchema = z.object({
	ui: SelectUiModeSchema.optional(),
	surface: SelectSurfaceModeSchema.optional(),
	tmuxPopupOpts: z.string().min(1).optional(),
	fzf: SelectorFzfSchema.optional()
}).strict();
const RatioValueSchema = z.union([z.number().positive(), z.string().regex(/^[1-9][0-9]*c$/, { message: "ratio value must be a positive number or \"<positive-integer>c\"" })]);
const hasNumericWeight = (ratio) => {
	return ratio.some((value) => typeof value === "number");
};
const LayoutIssueParamCodes = {
	RATIO_LENGTH_MISMATCH: "RATIO_LENGTH_MISMATCH",
	RATIO_WEIGHT_MISSING: "RATIO_WEIGHT_MISSING"
};
const TerminalPaneSchema = z.object({
	name: z.string().min(1),
	command: z.string().optional(),
	cwd: z.string().optional(),
	env: z.record(z.string()).optional(),
	delay: z.number().int().positive().optional(),
	title: z.string().optional(),
	focus: z.boolean().optional(),
	ephemeral: z.boolean().optional(),
	closeOnError: z.boolean().optional()
}).strict();
const SplitPaneSchema = z.lazy(() => z.object({
	type: z.enum(["horizontal", "vertical"]),
	ratio: z.array(RatioValueSchema).min(1),
	panes: z.array(PaneSchema).min(1)
}).strict().refine((data) => data.ratio.length === data.panes.length, {
	message: "Number of elements in ratio array does not match number of elements in panes array",
	params: { code: LayoutIssueParamCodes.RATIO_LENGTH_MISMATCH }
}).refine((data) => hasNumericWeight(data.ratio), {
	message: "ratio must include at least one numeric weight",
	params: { code: LayoutIssueParamCodes.RATIO_WEIGHT_MISSING }
}));
const PaneSchema = z.lazy(() => z.union([SplitPaneSchema, TerminalPaneSchema]));
const LayoutSchema = z.object({
	type: z.enum(["horizontal", "vertical"]),
	ratio: z.array(RatioValueSchema).min(1),
	panes: z.array(PaneSchema).min(1)
}).refine((data) => data.ratio.length === data.panes.length, {
	message: "Number of elements in ratio array does not match number of elements in panes array",
	params: { code: LayoutIssueParamCodes.RATIO_LENGTH_MISMATCH }
}).refine((data) => hasNumericWeight(data.ratio), {
	message: "ratio must include at least one numeric weight",
	params: { code: LayoutIssueParamCodes.RATIO_WEIGHT_MISSING }
});
const PresetSchema = z.object({
	name: z.string().min(1),
	description: z.string().optional(),
	layout: LayoutSchema.optional(),
	command: z.string().optional(),
	windowMode: WindowModeSchema.optional(),
	backend: TerminalBackendSchema.optional(),
	hooks: HooksSchema.optional()
});
const ConfigSchema = z.object({
	defaults: z.object({
		windowMode: WindowModeSchema.optional(),
		selector: SelectorDefaultsSchema.optional()
	}).optional(),
	presets: z.record(PresetSchema)
});

//#endregion
//#region src/config/validator.ts
/**
* Parse YAML text into an object
* @param yamlText - YAML text to parse
* @returns Parsed object
* @throws {ValidationError} When YAML parsing fails
*/
const parseYAML = (yamlText) => {
	if (!yamlText || typeof yamlText !== "string") throw createValidationError("YAML text not provided", ErrorCodes.CONFIG_PARSE_ERROR, { received: typeof yamlText });
	try {
		return YAML.parse(yamlText);
	} catch (error) {
		throw createValidationError("Failed to parse YAML", ErrorCodes.CONFIG_PARSE_ERROR, {
			parseError: error instanceof Error ? error.message : String(error),
			yamlSnippet: yamlText.substring(0, 200)
		});
	}
};
/**
* Validate basic configuration structure
* @param parsed - Parsed YAML object
* @throws {ValidationError} When structure is invalid
*/
const validateConfigStructure = (parsed) => {
	if (parsed === null || parsed === void 0 || typeof parsed !== "object") throw createValidationError("YAML is empty or invalid format", ErrorCodes.CONFIG_PARSE_ERROR, { parsed });
	const parsedObj = parsed;
	if (!("presets" in parsedObj) || parsedObj.presets === void 0 || parsedObj.presets === null) throw createValidationError("presets field is required", ErrorCodes.INVALID_PRESET, { availableFields: Object.keys(parsedObj) });
	const presetsObj = parsedObj.presets;
	if (typeof presetsObj !== "object" || presetsObj === null || Object.keys(presetsObj).length === 0) throw createValidationError("At least one preset is required", ErrorCodes.INVALID_PRESET, { presets: presetsObj });
};
/**
* Format Zod validation errors into user-friendly messages
* @param error - Zod validation error
* @returns Formatted error issues
*/
const formatZodErrors = (error) => {
	return error.issues.map((issue) => {
		const path = issue.path.join(".");
		let message = issue.message;
		if (issue.code === "invalid_type") {
			if (issue.path.includes("command") && issue.expected === "string") message = "command field must be a string";
			else if (issue.path.includes("workingDirectory") && issue.expected === "string") message = "workingDirectory field must be a string";
			else if (issue.received === "number" && issue.expected === "string") message = `${path} must be a string`;
			else if (issue.received === "array" && issue.expected === "string") message = `${path} must be a string`;
		} else if (issue.code === "invalid_union") {
			const unionIssue = issue;
			if (unionIssue.unionErrors !== void 0) if (unionIssue.unionErrors.find((e) => e.issues?.some((i) => i.path.includes("command") && i.code === "invalid_type") === true) !== void 0) message = "command field is required";
			else if (unionIssue.unionErrors.find((e) => e.issues?.some((i) => i.path.includes("panes") && i.code === "invalid_type") === true) !== void 0) message = "panes field is required";
			else message = "Pane type must be \"terminal\" or \"split\"";
			else message = "Pane type must be \"terminal\" or \"split\"";
		} else if (issue.code === "invalid_literal") {
			if (issue.path.includes("direction")) message = "direction must be \"horizontal\" or \"vertical\"";
		} else if (issue.message.includes("required")) message = issue.message;
		else if (issue.code === "custom" && issue.message.includes("ratio array")) message = issue.message;
		else if (issue.code === "too_small" && issue.message.includes("Array must contain at least")) if (path.includes("panes")) message = "panes array must contain at least 2 elements";
		else if (path.includes("ratio")) message = "ratio array must contain at least 2 elements";
		else message = issue.message;
		return {
			path,
			message,
			code: issue.code
		};
	});
};
/**
* Validates YAML text and converts it to a type-safe Config object
* @param yamlText - YAML text to validate
* @returns Validated Config object
* @throws {ValidationError} When YAML is invalid
*/
const validateYAML = (yamlText) => {
	const parsed = parseYAML(yamlText);
	validateConfigStructure(parsed);
	try {
		return ConfigSchema.parse(parsed);
	} catch (error) {
		if (error instanceof z.ZodError) {
			const issues = formatZodErrors(error);
			throw createValidationError(issues.length > 0 && issues[0] ? issues[0].message : "Configuration validation failed", ErrorCodes.CONFIG_PARSE_ERROR, {
				issues,
				rawErrors: error.issues
			});
		}
		if (isVDELayoutError(error) && error.name === "ValidationError") throw error;
		throw createValidationError("Unexpected validation error occurred", ErrorCodes.CONFIG_PARSE_ERROR, { error: error instanceof Error ? error.message : String(error) });
	}
};

//#endregion
//#region src/config/loader.ts
const createConfigLoader = (options = {}) => {
	const explicitConfigPaths = options.configPaths;
	const emitWarning = options.onWarning ?? ((message) => console.warn(message));
	const computeCachedSearchPaths = () => {
		if (explicitConfigPaths && explicitConfigPaths.length > 0) return [...explicitConfigPaths];
		const candidates = [];
		const projectCandidate = findProjectConfigCandidate();
		if (projectCandidate !== null) candidates.push(projectCandidate);
		const defaultSearchPathGroups = buildDefaultSearchPathGroups();
		candidates.push(...flattenSearchPathGroups(defaultSearchPathGroups));
		return [...new Set(candidates)];
	};
	const loadConfig = async () => {
		if (explicitConfigPaths && explicitConfigPaths.length > 0) {
			const filePath = await findFirstExisting(explicitConfigPaths);
			if (filePath === null) throw createConfigError("Configuration file not found", ErrorCodes.CONFIG_NOT_FOUND, { searchPaths: explicitConfigPaths });
			return validateYAML(await safeReadFile(filePath));
		}
		const searchPaths = computeCachedSearchPaths();
		const globalPaths = await resolveFirstExistingPaths(buildDefaultSearchPathGroups());
		const projectPath = findProjectConfigCandidate();
		const projectConfigExists = projectPath !== null ? await fs.pathExists(projectPath) : false;
		if (globalPaths.length === 0 && !projectConfigExists) throw createConfigError("Configuration file not found", ErrorCodes.CONFIG_NOT_FOUND, { searchPaths });
		let mergedConfig = { presets: {} };
		for (const globalPath of globalPaths) {
			const config = validateYAML(await safeReadFile(globalPath));
			mergedConfig = mergeConfigs(mergedConfig, config, emitWarning);
		}
		if (projectPath !== null && projectConfigExists) {
			const config = validateYAML(await safeReadFile(projectPath));
			mergedConfig = mergeConfigs(mergedConfig, config, emitWarning);
		}
		return mergedConfig;
	};
	return {
		loadYAML: async () => {
			const config = await loadConfig();
			return YAML.stringify(config);
		},
		loadConfig,
		findConfigFile: async () => {
			const searchPaths = explicitConfigPaths && explicitConfigPaths.length > 0 ? [...explicitConfigPaths] : computeCachedSearchPaths();
			for (const searchPath of searchPaths) if (await fs.pathExists(searchPath)) return searchPath;
			return null;
		},
		getSearchPaths: () => computeCachedSearchPaths()
	};
};
const buildDefaultSearchPathGroups = () => {
	const pathGroups = [];
	const vdeConfigPath = process.env.VDE_CONFIG_PATH;
	if (vdeConfigPath !== void 0) pathGroups.push([path.join(vdeConfigPath, "layout.yml")]);
	const homeDir = process.env.HOME ?? os.homedir();
	const xdgConfigHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, ".config");
	pathGroups.push([path.join(xdgConfigHome, "vde", "layout", "config.yml"), path.join(xdgConfigHome, "vde", "layout.yml")]);
	return pathGroups.map((group) => [...new Set(group)]);
};
const flattenSearchPathGroups = (pathGroups) => {
	const paths = [];
	for (const group of pathGroups) paths.push(...group);
	return [...new Set(paths)];
};
const resolveFirstExistingPaths = async (pathGroups) => {
	const existingPaths = await Promise.all(pathGroups.map(async (group) => findFirstExisting(group)));
	const seenPaths = /* @__PURE__ */ new Set();
	const resolvedPaths = [];
	for (const existingPath of existingPaths) if (existingPath !== null && !seenPaths.has(existingPath)) {
		seenPaths.add(existingPath);
		resolvedPaths.push(existingPath);
	}
	return resolvedPaths;
};
const findProjectConfigCandidate = () => {
	let currentDir = process.cwd();
	const { root } = path.parse(currentDir);
	while (true) {
		const candidates = [path.join(currentDir, ".vde", "layout", "config.yml"), path.join(currentDir, ".vde", "layout.yml")];
		for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
		if (currentDir === root) break;
		const parent = path.dirname(currentDir);
		if (parent === currentDir) break;
		currentDir = parent;
	}
	return null;
};
const findFirstExisting = async (paths) => {
	for (const candidate of paths) if (await fs.pathExists(candidate)) return candidate;
	return null;
};
const safeReadFile = async (filePath) => {
	try {
		return await fs.readFile(filePath, "utf8");
	} catch (error) {
		const errorMessage = error instanceof Error ? error.message : String(error);
		throw createConfigError(`Failed to read configuration file`, ErrorCodes.CONFIG_PERMISSION_ERROR, {
			filePath,
			error: errorMessage
		});
	}
};
const mergeConfigs = (base, override, emitWarning) => {
	const mergedPresets = { ...base.presets };
	for (const [presetKey, overridePreset] of Object.entries(override.presets)) {
		const basePreset = base.presets[presetKey];
		if (basePreset !== void 0 && basePreset.windowMode !== void 0 && overridePreset.windowMode !== void 0 && basePreset.windowMode !== overridePreset.windowMode) emitWarning(`[vde-layout] Preset "${presetKey}" windowMode conflict: "${basePreset.windowMode}" overridden by "${overridePreset.windowMode}"`);
		mergedPresets[presetKey] = overridePreset;
	}
	const baseDefaults = base.defaults;
	const overrideDefaults = override.defaults;
	if (baseDefaults?.windowMode !== void 0 && overrideDefaults?.windowMode !== void 0 && baseDefaults.windowMode !== overrideDefaults.windowMode) emitWarning(`[vde-layout] defaults.windowMode conflict: "${baseDefaults.windowMode}" overridden by "${overrideDefaults.windowMode}"`);
	const mergedSelectorDefaults = mergeSelectorDefaults({
		baseSelector: baseDefaults?.selector,
		overrideSelector: overrideDefaults?.selector,
		emitWarning
	});
	const mergedDefaults = baseDefaults !== void 0 || overrideDefaults !== void 0 ? {
		...baseDefaults ?? {},
		...overrideDefaults ?? {},
		...mergedSelectorDefaults !== void 0 ? { selector: mergedSelectorDefaults } : {}
	} : void 0;
	return mergedDefaults === void 0 ? { presets: mergedPresets } : {
		defaults: mergedDefaults,
		presets: mergedPresets
	};
};
const mergeSelectorDefaults = ({ baseSelector, overrideSelector, emitWarning }) => {
	if (baseSelector === void 0 && overrideSelector === void 0) return;
	if (baseSelector?.ui !== void 0 && overrideSelector?.ui !== void 0 && baseSelector.ui !== overrideSelector.ui) emitWarning(`[vde-layout] defaults.selector.ui conflict: "${baseSelector.ui}" overridden by "${overrideSelector.ui}"`);
	if (baseSelector?.surface !== void 0 && overrideSelector?.surface !== void 0 && baseSelector.surface !== overrideSelector.surface) emitWarning(`[vde-layout] defaults.selector.surface conflict: "${baseSelector.surface}" overridden by "${overrideSelector.surface}"`);
	if (baseSelector?.tmuxPopupOpts !== void 0 && overrideSelector?.tmuxPopupOpts !== void 0 && baseSelector.tmuxPopupOpts !== overrideSelector.tmuxPopupOpts) emitWarning(`[vde-layout] defaults.selector.tmuxPopupOpts conflict: "${baseSelector.tmuxPopupOpts}" overridden by "${overrideSelector.tmuxPopupOpts}"`);
	const baseExtraArgs = baseSelector?.fzf?.extraArgs;
	const overrideExtraArgs = overrideSelector?.fzf?.extraArgs;
	if (Array.isArray(baseExtraArgs) && Array.isArray(overrideExtraArgs) && JSON.stringify(baseExtraArgs) !== JSON.stringify(overrideExtraArgs)) emitWarning(`[vde-layout] defaults.selector.fzf.extraArgs conflict: global value overridden by project value`);
	const mergedFzf = baseSelector?.fzf !== void 0 || overrideSelector?.fzf !== void 0 ? {
		...baseSelector?.fzf ?? {},
		...overrideSelector?.fzf ?? {}
	} : void 0;
	return {
		...baseSelector ?? {},
		...overrideSelector ?? {},
		...mergedFzf !== void 0 ? { fzf: mergedFzf } : {}
	};
};

//#endregion
//#region src/layout/preset.ts
const createState = (options = {}) => {
	let loaderOptions = options;
	let cachedConfig = null;
	const setConfigPath = (filePath) => {
		loaderOptions = { configPaths: [filePath] };
		cachedConfig = null;
	};
	const loadConfig = async () => {
		cachedConfig = await createConfigLoader(loaderOptions).loadConfig();
	};
	const ensureConfig = () => {
		if (cachedConfig === null) throw createConfigError("Configuration not loaded", ErrorCodes.CONFIG_NOT_FOUND);
		return cachedConfig;
	};
	const getPreset = (name) => {
		const config = ensureConfig();
		const preset = config.presets[name];
		if (preset === void 0) throw createConfigError(`Preset "${name}" not found`, ErrorCodes.PRESET_NOT_FOUND, { availablePresets: Object.keys(config.presets) });
		return preset;
	};
	const listPresets = () => {
		if (cachedConfig === null) return [];
		return Object.entries(cachedConfig.presets).map(([key, preset]) => ({
			key,
			name: preset.name,
			description: preset.description
		}));
	};
	const getDefaultPreset = () => {
		const config = ensureConfig();
		if (config.presets.default !== void 0) return config.presets.default;
		const firstKey = Object.keys(config.presets)[0];
		if (typeof firstKey !== "string" || firstKey.length === 0) throw createConfigError("No presets defined", ErrorCodes.PRESET_NOT_FOUND);
		return config.presets[firstKey];
	};
	const getDefaults = () => {
		return ensureConfig().defaults;
	};
	return {
		setConfigPath,
		loadConfig,
		getPreset,
		listPresets,
		getDefaultPreset,
		getDefaults
	};
};
const createPresetManager = (options = {}) => {
	const state = createState(options);
	return {
		setConfigPath: state.setConfigPath,
		loadConfig: state.loadConfig,
		getPreset: state.getPreset,
		listPresets: state.listPresets,
		getDefaultPreset: state.getDefaultPreset,
		getDefaults: state.getDefaults
	};
};

//#endregion
//#region src/cli/package-version.ts
const CANDIDATE_PATHS = ["../package.json", "../../package.json"];
const isModuleNotFoundError = (error) => {
	return error instanceof Error && error.code === "MODULE_NOT_FOUND";
};
const loadPackageVersion = (requireFn) => {
	let lastNotFound;
	for (const path of CANDIDATE_PATHS) try {
		return requireFn(path).version;
	} catch (error) {
		if (isModuleNotFoundError(error)) {
			lastNotFound = error;
			continue;
		}
		throw error;
	}
	throw lastNotFound ?? /* @__PURE__ */ new Error(`Unable to resolve package version from candidates: ${CANDIDATE_PATHS.join(", ")}`);
};

//#endregion
//#region src/core/errors.ts
const createCoreError = (kind, error) => ({
	kind,
	code: error.code,
	message: error.message,
	source: error.source,
	path: error.path,
	details: error.details
});
const isCoreError = (value) => {
	if (typeof value !== "object" || value === null) return false;
	const candidate = value;
	return (candidate.kind === "compile" || candidate.kind === "plan" || candidate.kind === "emit" || candidate.kind === "execution") && typeof candidate.code === "string" && typeof candidate.message === "string";
};

//#endregion
//#region src/core/compile.ts
const compilePreset = ({ document, source }) => {
	let parsed;
	try {
		parsed = parse(document);
	} catch (error) {
		throw compileError("PRESET_PARSE_ERROR", {
			source,
			message: `Failed to parse YAML: ${error.message}`,
			details: { reason: error instanceof Error ? error.message : String(error) }
		});
	}
	return compilePresetValue({
		value: parsed,
		source
	});
};
const compilePresetFromValue = ({ value, source }) => {
	return compilePresetValue({
		value,
		source
	});
};
const FIXED_RATIO_PATTERN = /^([1-9][0-9]*)c$/;
const compilePresetValue = ({ value, source }) => {
	const parsed = value;
	if (!isRecord(parsed)) throw compileError("PRESET_INVALID_DOCUMENT", {
		source,
		message: "Preset definition is not an object",
		path: "preset"
	});
	const name = typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name : "Unnamed preset";
	const layout = parseLayoutNode(validateLayoutDefinition(parsed.layout, {
		source,
		path: "preset.layout"
	}), {
		source,
		path: "preset.layout"
	});
	return { preset: {
		name,
		version: "legacy",
		command: typeof parsed.command === "string" ? parsed.command : void 0,
		layout: layout ?? void 0,
		hooks: parseHooks(parsed.hooks),
		metadata: { source }
	} };
};
const parseHooks = (hooks) => {
	if (!isRecord(hooks)) return;
	const afterApply = typeof hooks.afterApply === "string" && hooks.afterApply.length > 0 ? hooks.afterApply : void 0;
	return afterApply === void 0 ? void 0 : { afterApply };
};
const validateLayoutDefinition = (layout, context) => {
	if (layout === void 0 || layout === null) return layout;
	if (!isRecord(layout) || !looksSplitLikeNode(layout)) return layout;
	const normalizedLayout = sanitizeLayoutForSchemaValidation(layout);
	const validated = LayoutSchema.safeParse(normalizedLayout);
	if (validated.success) return layout;
	const issue = validated.error.issues[0];
	if (!issue) throw compileError("LAYOUT_INVALID_NODE", {
		source: context.source,
		message: "Layout node is invalid",
		path: context.path,
		details: { layout }
	});
	throw convertLayoutIssueToCompileError({
		issue,
		source: context.source,
		basePath: context.path,
		layout
	});
};
const parseLayoutNode = (node, context) => {
	if (node === void 0 || node === null) return null;
	if (!isRecord(node)) throw compileError("LAYOUT_INVALID_NODE", {
		source: context.source,
		message: "Layout node is invalid",
		path: context.path,
		details: { node }
	});
	if ("type" in node || "ratio" in node || "panes" in node) return parseSplitPane(node, context);
	if (typeof node.name === "string") return parseTerminalPane(node);
	throw compileError("LAYOUT_INVALID_NODE", {
		source: context.source,
		message: "Layout node is invalid",
		path: context.path,
		details: { node }
	});
};
const parseSplitPane = (node, context) => {
	const orientation = node.type;
	const panesInput = node.panes;
	const ratioInput = node.ratio;
	if (orientation !== "horizontal" && orientation !== "vertical" || !Array.isArray(panesInput) || !Array.isArray(ratioInput)) throw compileError("LAYOUT_INVALID_NODE", {
		source: context.source,
		message: "Layout node is invalid",
		path: context.path,
		details: { node }
	});
	const panes = panesInput.map((child, index) => parseLayoutNode(child, {
		source: context.source,
		path: `${context.path}.panes[${index}]`
	}));
	const ratio = ratioInput.map((value, index) => parseRatioEntry(value, {
		source: context.source,
		path: `${context.path}.ratio[${index}]`
	}));
	if (!ratio.some((entry) => entry.kind === "weight")) throw compileError("RATIO_WEIGHT_MISSING", {
		source: context.source,
		message: "ratio must include at least one numeric weight",
		path: `${context.path}.ratio`,
		details: { ratio: ratioInput }
	});
	return {
		kind: "split",
		orientation,
		ratio,
		panes: panes.filter((pane) => pane !== null)
	};
};
const parseRatioEntry = (value, context) => {
	if (typeof value === "number" && Number.isFinite(value) && value > 0) return {
		kind: "weight",
		weight: value
	};
	if (typeof value === "string") {
		const match = value.match(FIXED_RATIO_PATTERN);
		if (match?.[1] !== void 0) {
			const parsed = Number(match[1]);
			if (Number.isInteger(parsed) && parsed > 0) return {
				kind: "fixed-cells",
				cells: parsed
			};
		}
	}
	throw compileError("RATIO_INVALID_VALUE", {
		source: context.source,
		message: "ratio value must be a positive number or \"<positive-integer>c\"",
		path: context.path,
		details: { value }
	});
};
const parseTerminalPane = (node) => {
	const name = typeof node.name === "string" ? node.name : "";
	const command = typeof node.command === "string" ? node.command : void 0;
	const cwd = typeof node.cwd === "string" ? node.cwd : void 0;
	const delay = typeof node.delay === "number" && Number.isFinite(node.delay) && node.delay > 0 ? node.delay : void 0;
	const title = typeof node.title === "string" && node.title.length > 0 ? node.title : void 0;
	const focus = node.focus === true ? true : void 0;
	const ephemeral = node.ephemeral === true ? true : void 0;
	const closeOnError = node.closeOnError === true ? true : void 0;
	return {
		kind: "terminal",
		name,
		command,
		cwd,
		env: normalizeEnv(node.env),
		delay,
		title,
		focus,
		ephemeral,
		closeOnError,
		options: collectOptions(node, new Set([
			"name",
			"command",
			"cwd",
			"env",
			"focus",
			"ephemeral",
			"closeOnError",
			"options",
			"title",
			"delay"
		]))
	};
};
const normalizeEnv = (env) => {
	if (!isRecord(env)) return;
	const entries = Object.entries(env).reduce((accumulator, [key, value]) => {
		if (typeof value === "string") accumulator[key] = value;
		return accumulator;
	}, {});
	return Object.keys(entries).length > 0 ? entries : void 0;
};
const collectOptions = (node, excludedKeys) => {
	const optionsEntries = Object.entries(node).filter(([key]) => !excludedKeys.has(key));
	if (optionsEntries.length === 0) return;
	return optionsEntries.reduce((accumulator, [key, value]) => {
		accumulator[key] = value;
		return accumulator;
	}, {});
};
const isRecord = (value) => {
	return typeof value === "object" && value !== null;
};
const looksSplitLikeNode = (value) => {
	return "type" in value || "ratio" in value || "panes" in value;
};
const sanitizeLayoutForSchemaValidation = (node) => {
	if (!isRecord(node)) return node;
	if (looksSplitLikeNode(node)) {
		const panes = Array.isArray(node.panes) ? node.panes.map((child) => sanitizeLayoutForSchemaValidation(child)) : node.panes;
		return {
			type: node.type,
			ratio: node.ratio,
			panes
		};
	}
	return {
		name: node.name,
		command: node.command,
		cwd: node.cwd,
		env: normalizeEnv(node.env),
		delay: node.delay,
		title: node.title,
		focus: node.focus,
		ephemeral: node.ephemeral,
		closeOnError: node.closeOnError
	};
};
const convertLayoutIssueToCompileError = ({ issue, source, basePath, layout }) => {
	const issueParamCode = getIssueParamCode(issue);
	if (isMissingArrayIssue(issue, "panes")) return compileError("LAYOUT_PANES_MISSING", {
		source,
		message: "panes array is missing or empty",
		path: `${basePath}.panes`
	});
	if (isMissingArrayIssue(issue, "ratio")) return compileError("LAYOUT_RATIO_MISSING", {
		source,
		message: "ratio array is missing or empty",
		path: `${basePath}.ratio`
	});
	if (issue.path.includes("type")) return compileError("LAYOUT_INVALID_ORIENTATION", {
		source,
		message: "layout.type must be horizontal or vertical",
		path: `${basePath}.type`,
		details: { type: getValueAtPath(layout, issue.path) }
	});
	if (issueParamCode === LayoutIssueParamCodes.RATIO_LENGTH_MISMATCH || issue.message.includes("Number of elements in ratio array does not match number of elements in panes array")) return compileError("LAYOUT_RATIO_MISMATCH", {
		source,
		message: "ratio and panes arrays must have the same length",
		path: basePath,
		details: getRatioLengthDetails(layout)
	});
	if (issueParamCode === LayoutIssueParamCodes.RATIO_WEIGHT_MISSING || issue.message.includes("ratio must include at least one numeric weight")) {
		const ratio = isRecord(layout) ? layout.ratio : void 0;
		return compileError("RATIO_WEIGHT_MISSING", {
			source,
			message: "ratio must include at least one numeric weight",
			path: `${basePath}.ratio`,
			details: { ratio }
		});
	}
	if (issue.path.includes("ratio")) return compileError("RATIO_INVALID_VALUE", {
		source,
		message: "ratio value must be a positive number or \"<positive-integer>c\"",
		path: formatPath(basePath, issue.path),
		details: { value: getValueAtPath(layout, issue.path) }
	});
	return compileError("LAYOUT_INVALID_NODE", {
		source,
		message: "Layout node is invalid",
		path: formatPath(basePath, issue.path),
		details: {
			issue: issue.message,
			node: getValueAtPath(layout, issue.path)
		}
	});
};
const isMissingArrayIssue = (issue, field) => {
	if (issue.path.length !== 1 || issue.path[0] !== field) return false;
	return issue.code === "invalid_type" || issue.code === "too_small" && issue.type === "array";
};
const getIssueParamCode = (issue) => {
	if (issue.code !== z.ZodIssueCode.custom) return;
	const code = issue.params?.code;
	return typeof code === "string" ? code : void 0;
};
const getRatioLengthDetails = (layout) => {
	if (!isRecord(layout)) return;
	const ratio = layout.ratio;
	const panes = layout.panes;
	if (!Array.isArray(ratio) || !Array.isArray(panes)) return;
	return {
		ratioLength: ratio.length,
		panesLength: panes.length
	};
};
const formatPath = (basePath, path) => {
	if (path.length === 0) return basePath;
	return path.reduce((accumulator, segment) => {
		if (typeof segment === "number") return `${accumulator}[${segment}]`;
		return `${accumulator}.${segment}`;
	}, basePath);
};
const getValueAtPath = (value, path) => {
	let current = value;
	for (const segment of path) {
		if (typeof segment === "number") {
			if (!Array.isArray(current)) return;
			current = current[segment];
			continue;
		}
		if (!isRecord(current)) return;
		current = current[segment];
	}
	return current;
};
const compileError = (code, error) => {
	return createCoreError("compile", {
		code,
		message: error.message,
		source: error.source,
		path: error.path,
		details: error.details
	});
};

//#endregion
//#region src/core/planner.ts
const createLayoutPlan = ({ preset }) => {
	if (!preset.layout) {
		const terminal = createTerminalNode({
			id: "root",
			terminal: {
				kind: "terminal",
				name: preset.name,
				command: preset.command
			},
			focusOverride: true
		});
		return { plan: {
			root: terminal,
			focusPaneId: terminal.id
		} };
	}
	const { node, focusPaneIds, terminalPaneIds } = buildLayoutNode(preset.layout, {
		parentId: "root",
		path: "preset.layout",
		source: preset.metadata.source
	});
	if (focusPaneIds.length > 1) throw planError("FOCUS_CONFLICT", {
		message: "Multiple panes specify focus=true",
		path: "preset.layout",
		source: preset.metadata.source,
		details: { focusPaneIds }
	});
	if (terminalPaneIds.length === 0) throw planError("NO_TERMINAL_PANES", {
		message: "No terminal panes are defined",
		path: "preset.layout",
		source: preset.metadata.source
	});
	const focusPaneId = focusPaneIds[0] ?? terminalPaneIds[0];
	return { plan: {
		root: ensureFocus(node, focusPaneId),
		focusPaneId
	} };
};
const buildLayoutNode = (node, context) => {
	if (node.kind === "split") return buildSplitNode(node, context);
	return {
		node: createTerminalNode({
			id: context.parentId,
			terminal: node
		}),
		focusPaneIds: node.focus === true ? [context.parentId] : [],
		terminalPaneIds: [context.parentId]
	};
};
const buildSplitNode = (node, context) => {
	const ratio = validateRatioEntries(node.ratio, context);
	const panes = [];
	const focusPaneIds = [];
	const terminalPaneIds = [];
	for (let index = 0; index < node.panes.length; index += 1) {
		const childContext = {
			parentId: `${context.parentId}.${index}`,
			path: `${context.path}.panes[${index}]`,
			source: context.source
		};
		const childResult = buildLayoutNode(node.panes[index], childContext);
		panes.push(childResult.node);
		focusPaneIds.push(...childResult.focusPaneIds);
		terminalPaneIds.push(...childResult.terminalPaneIds);
	}
	return {
		node: {
			kind: "split",
			id: context.parentId,
			orientation: node.orientation,
			ratio,
			panes
		},
		focusPaneIds,
		terminalPaneIds
	};
};
const createTerminalNode = ({ id, terminal, focusOverride }) => {
	return {
		kind: "terminal",
		id,
		name: terminal.name,
		command: terminal.command,
		cwd: terminal.cwd,
		env: terminal.env,
		delay: terminal.delay,
		title: terminal.title,
		options: terminal.options,
		focus: focusOverride === true ? true : terminal.focus === true,
		ephemeral: terminal.ephemeral,
		closeOnError: terminal.closeOnError
	};
};
const ensureFocus = (node, focusPaneId) => {
	if (node.kind === "terminal") return {
		...node,
		focus: node.id === focusPaneId
	};
	return {
		...node,
		panes: node.panes.map((pane) => ensureFocus(pane, focusPaneId))
	};
};
const validateRatioEntries = (ratio, context) => {
	let hasWeight = false;
	for (let index = 0; index < ratio.length; index += 1) {
		const value = ratio[index];
		if (value?.kind === "weight") {
			if (!Number.isFinite(value.weight) || value.weight <= 0) throw planError("RATIO_INVALID_VALUE", {
				message: "ratio value must be a positive number or \"<positive-integer>c\"",
				path: `${context.path}.ratio[${index}]`,
				source: context.source,
				details: { value }
			});
			hasWeight = true;
			continue;
		}
		if (value?.kind === "fixed-cells") {
			if (!Number.isInteger(value.cells) || value.cells <= 0) throw planError("RATIO_INVALID_VALUE", {
				message: "ratio value must be a positive number or \"<positive-integer>c\"",
				path: `${context.path}.ratio[${index}]`,
				source: context.source,
				details: { value }
			});
			continue;
		}
		throw planError("RATIO_INVALID_VALUE", {
			message: "ratio entry is invalid",
			path: `${context.path}.ratio[${index}]`,
			source: context.source,
			details: { value }
		});
	}
	if (!hasWeight) throw planError("RATIO_WEIGHT_MISSING", {
		message: "ratio must include at least one numeric weight",
		path: `${context.path}.ratio`,
		source: context.source,
		details: { ratio }
	});
	return ratio;
};
const planError = (code, error) => {
	return createCoreError("plan", {
		code,
		message: error.message,
		source: error.source,
		path: error.path,
		details: error.details
	});
};

//#endregion
//#region src/core/emitter.ts
const emitPlan = ({ plan }) => {
	const steps = [];
	collectSplitSteps(plan.root, steps);
	steps.push({
		id: `${plan.focusPaneId}:focus`,
		kind: "focus",
		summary: `select pane ${plan.focusPaneId}`,
		targetPaneId: plan.focusPaneId
	});
	const hash = createPlanHash(plan, steps);
	const initialPaneId = determineInitialPaneId(plan.root);
	const terminals = collectTerminals(plan.root);
	return {
		steps,
		summary: {
			stepsCount: steps.length,
			focusPaneId: plan.focusPaneId,
			initialPaneId
		},
		terminals,
		hash
	};
};
const collectSplitSteps = (node, steps) => {
	if (node.kind === "terminal") return;
	appendSplitSteps(node, steps);
	node.panes.forEach((pane) => collectSplitSteps(pane, steps));
};
const appendSplitSteps = (node, steps) => {
	const directionFlag = node.orientation === "horizontal" ? "-h" : "-v";
	const hasFixedCells = node.ratio.some((entry) => entry.kind === "fixed-cells");
	for (let index = 1; index < node.panes.length; index += 1) {
		const targetPaneId = node.panes[index - 1]?.id ?? node.id;
		const createdPaneId = node.panes[index]?.id;
		const splitSizing = hasFixedCells ? buildDynamicSplitSizing(node.ratio, index) : buildPercentSplitSizing(node.ratio, index);
		const percentage = splitSizing.mode === "percent" ? splitSizing.percentage : void 0;
		steps.push({
			id: `${node.id}:split:${index}`,
			kind: "split",
			summary: `split ${targetPaneId} (${directionFlag})`,
			targetPaneId,
			createdPaneId,
			orientation: node.orientation,
			percentage,
			splitSizing
		});
	}
};
const buildPercentSplitSizing = (ratio, index) => {
	const remainingIncludingTarget = ratio.slice(index - 1).reduce((sum, entry) => sum + (entry.kind === "weight" ? entry.weight : 0), 0);
	const remainingAfterTarget = ratio.slice(index).reduce((sum, entry) => sum + (entry.kind === "weight" ? entry.weight : 0), 0);
	return {
		mode: "percent",
		percentage: clampPercent(remainingIncludingTarget <= 0 ? 0 : remainingAfterTarget / remainingIncludingTarget * 100)
	};
};
const buildDynamicSplitSizing = (ratio, index) => {
	const target = ratio[index - 1];
	const remaining = ratio.slice(index);
	return {
		mode: "dynamic-cells",
		target,
		remainingFixedCells: remaining.reduce((sum, entry) => {
			return entry.kind === "fixed-cells" ? sum + entry.cells : sum;
		}, 0),
		remainingWeight: remaining.reduce((sum, entry) => {
			return entry.kind === "weight" ? sum + entry.weight : sum;
		}, 0),
		remainingWeightPaneCount: remaining.reduce((count, entry) => {
			return entry.kind === "weight" ? count + 1 : count;
		}, 0)
	};
};
const clampPercent = (value) => {
	return Math.min(99, Math.max(1, Math.round(value)));
};
const collectTerminals = (node) => {
	if (node.kind === "terminal") return [{
		virtualPaneId: node.id,
		command: node.command,
		cwd: node.cwd,
		env: node.env,
		delay: node.delay,
		title: node.title,
		focus: node.focus,
		name: node.name,
		ephemeral: node.ephemeral,
		closeOnError: node.closeOnError
	}];
	return node.panes.flatMap((pane) => collectTerminals(pane));
};
const determineInitialPaneId = (node) => {
	if (node.kind === "terminal") return node.id;
	let current = node;
	while (current.kind === "split") current = current.panes[0];
	return current.id;
};
const createPlanHash = (plan, steps) => {
	const digest = createHash("sha256");
	const normalized = {
		focusPaneId: plan.focusPaneId,
		root: plan.root,
		steps
	};
	digest.update(JSON.stringify(normalized));
	return digest.digest("hex");
};

//#endregion
//#region src/cli/error-handling.ts
const createCliErrorHandlers = ({ getLogger }) => {
	const handleCoreError = (error) => {
		const header = [`[${error.kind}]`, `[${error.code}]`];
		if (typeof error.path === "string" && error.path.length > 0) header.push(`[${error.path}]`);
		const lines = [`${header.join(" ")} ${error.message}`.trim()];
		if (typeof error.source === "string" && error.source.length > 0) lines.push(`source: ${error.source}`);
		const commandDetail = error.details?.command;
		if (Array.isArray(commandDetail)) {
			const parts = commandDetail.filter((segment) => typeof segment === "string");
			if (parts.length > 0) lines.push(`command: ${parts.join(" ")}`);
		} else if (typeof commandDetail === "string" && commandDetail.length > 0) lines.push(`command: ${commandDetail}`);
		const stderrDetail = error.details?.stderr;
		if (typeof stderrDetail === "string" && stderrDetail.length > 0) lines.push(`stderr: ${stderrDetail}`);
		else if (stderrDetail !== void 0) lines.push(`stderr: ${String(stderrDetail)}`);
		getLogger().error(lines.join("\n"));
		return 1;
	};
	const handleError = (error) => {
		if (error instanceof Error) getLogger().error(error.message, error);
		else getLogger().error("An unexpected error occurred");
		return 1;
	};
	const handlePipelineFailure = (error) => {
		if (isCoreError(error)) return handleCoreError(error);
		return handleError(error);
	};
	return {
		handleCoreError,
		handleError,
		handlePipelineFailure
	};
};

//#endregion
//#region src/utils/logger.ts
let LogLevel = /* @__PURE__ */ function(LogLevel) {
	LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
	LogLevel[LogLevel["WARN"] = 1] = "WARN";
	LogLevel[LogLevel["INFO"] = 2] = "INFO";
	LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
	return LogLevel;
}({});
const resolveDefaultLogLevel = () => {
	if (process.env.VDE_DEBUG === "true") return LogLevel.DEBUG;
	if (process.env.VDE_VERBOSE === "true") return LogLevel.INFO;
	return LogLevel.WARN;
};
const formatMessage = (prefix, message) => {
	return prefix ? `${prefix} ${message}` : message;
};
const createLogger = (options = {}) => {
	const level = options.level ?? resolveDefaultLogLevel();
	const prefix = options.prefix ?? "";
	const build = (nextPrefix, nextLevel) => {
		const resolvedPrefix = nextPrefix;
		return {
			level: nextLevel,
			prefix: resolvedPrefix,
			error(message, error) {
				if (nextLevel >= LogLevel.ERROR) {
					console.error(chalk.red(formatMessage(resolvedPrefix, `Error: ${message}`)));
					if (error && process.env.VDE_DEBUG === "true") console.error(chalk.gray(error.stack));
				}
			},
			warn(message) {
				if (nextLevel >= LogLevel.WARN) console.warn(chalk.yellow(formatMessage(resolvedPrefix, message)));
			},
			info(message) {
				if (nextLevel >= LogLevel.INFO) console.log(formatMessage(resolvedPrefix, message));
			},
			debug(message) {
				if (nextLevel >= LogLevel.DEBUG) console.log(chalk.gray(formatMessage(resolvedPrefix, `[DEBUG] ${message}`)));
			},
			success(message) {
				console.log(chalk.green(formatMessage(resolvedPrefix, message)));
			},
			createChild(suffix) {
				return build(resolvedPrefix ? `${resolvedPrefix} ${suffix}` : suffix, nextLevel);
			}
		};
	};
	return build(prefix, level);
};

//#endregion
//#region src/cli/runtime-and-list.ts
const applyRuntimeOptions = ({ runtimeOptions, createLogger, presetManager }) => {
	const logger = runtimeOptions.verbose === true ? createLogger({ level: LogLevel.INFO }) : createLogger();
	if (typeof runtimeOptions.config === "string" && runtimeOptions.config.length > 0 && typeof presetManager.setConfigPath === "function") presetManager.setConfigPath(runtimeOptions.config);
	return logger;
};
const listPresets = async ({ presetManager, logger, onError, output = (line) => console.log(line) }) => {
	try {
		await presetManager.loadConfig();
		const presets = presetManager.listPresets();
		if (presets.length === 0) {
			logger.warn("No presets defined");
			return 0;
		}
		output(chalk.bold("Available presets:\n"));
		const maxKeyLength = Math.max(...presets.map((preset) => preset.key.length));
		presets.forEach((preset) => {
			const paddedKey = preset.key.padEnd(maxKeyLength + 2);
			const description = preset.description ?? "";
			output(`  ${chalk.cyan(paddedKey)} ${description}`);
		});
		return 0;
	} catch (error) {
		return onError(error);
	}
};

//#endregion
//#region src/backends/pane-tracking.ts
const updatePaneSizes = ({ paneSizes, targetPaneId, createdPaneId, orientation, targetCells, createdCells }) => {
	const base = paneSizes.get(targetPaneId);
	if (base === void 0) return;
	if (orientation === "horizontal") {
		paneSizes.set(targetPaneId, {
			cols: targetCells,
			rows: base.rows
		});
		paneSizes.set(createdPaneId, {
			cols: createdCells,
			rows: base.rows
		});
		return;
	}
	paneSizes.set(targetPaneId, {
		cols: base.cols,
		rows: targetCells
	});
	paneSizes.set(createdPaneId, {
		cols: base.cols,
		rows: createdCells
	});
};

//#endregion
//#region src/executor/real-executor.ts
const parseCommand$2 = (commandOrArgs) => {
	return typeof commandOrArgs === "string" ? commandOrArgs.split(" ").filter((segment) => segment.length > 0).slice(1) : commandOrArgs;
};
const toCommandString$2 = (args) => {
	return ["tmux", ...args].join(" ");
};
const createRealExecutor = (options = {}) => {
	const logger = createLogger({
		level: options.verbose ?? false ? LogLevel.INFO : LogLevel.WARN,
		prefix: "[tmux]"
	});
	const execute = async (commandOrArgs) => {
		const args = parseCommand$2(commandOrArgs);
		const commandString = toCommandString$2(args);
		logger.info(`Executing: ${commandString}`);
		try {
			return (await execa("tmux", args)).stdout;
		} catch (error) {
			const execaError = error;
			throw createTmuxError("Failed to execute tmux command", ErrorCodes.TMUX_COMMAND_FAILED, {
				command: commandString,
				exitCode: execaError.exitCode,
				stderr: execaError.stderr
			});
		}
	};
	return {
		execute,
		async executeMany(commandsList) {
			for (const args of commandsList) await execute(args);
		},
		isDryRun() {
			return false;
		},
		logCommand(command) {
			logger.info(`Executing: ${command}`);
		}
	};
};

//#endregion
//#region src/executor/dry-run-executor.ts
const parseCommand$1 = (commandOrArgs) => {
	return typeof commandOrArgs === "string" ? commandOrArgs.split(" ").filter((segment) => segment.length > 0).slice(1) : commandOrArgs;
};
const toCommandString$1 = (args) => {
	return ["tmux", ...args].join(" ");
};
const createDryRunExecutor = (options = {}) => {
	const logger = createLogger({
		level: options.verbose ?? false ? LogLevel.INFO : LogLevel.WARN,
		prefix: "[tmux] [DRY RUN]"
	});
	const execute = async (commandOrArgs) => {
		const commandString = toCommandString$1(parseCommand$1(commandOrArgs));
		logger.info(`Would execute: ${commandString}`);
		return "";
	};
	return {
		execute,
		async executeMany(commandsList) {
			for (const args of commandsList) await execute(args);
		},
		isDryRun() {
			return true;
		},
		logCommand(command) {
			logger.info(`Would execute: ${command}`);
		}
	};
};

//#endregion
//#region src/executor/mock-executor.ts
const parseCommand = (commandOrArgs) => {
	return typeof commandOrArgs === "string" ? commandOrArgs.split(" ").filter((segment) => segment.length > 0).slice(1) : commandOrArgs;
};
const isInTmuxSession = () => {
	return Boolean(process.env.TMUX);
};
const toCommandString = (args) => {
	return ["tmux", ...args].join(" ");
};
const createMockExecutor = () => {
	let mockPaneCounter = 0;
	let mockPaneIds = ["%0"];
	let mockProtectedPaneIds = [];
	let executedCommands = [];
	const execute = async (commandOrArgs) => {
		const args = parseCommand(commandOrArgs);
		executedCommands.push(args);
		if (args[0] === "new-window") {
			mockPaneCounter = 0;
			mockPaneIds = ["%0"];
			return "%0";
		}
		if (args.includes("display-message")) {
			const parts = [];
			if (args.includes("#{pane_id}")) parts.push(mockPaneIds[0] ?? "%0");
			if (args.includes("#{pane_width}")) parts.push("200");
			if (args.includes("#{pane_height}")) parts.push("60");
			if (parts.length > 0) return parts.join(" ");
		}
		if (args.includes("list-panes") && args.includes("#{pane_id}	#{@vde_sidebar}")) {
			const protectedPaneIds = new Set(mockProtectedPaneIds);
			return mockPaneIds.map((paneId) => `${paneId}\t${protectedPaneIds.has(paneId) ? "1" : ""}`).join("\n");
		}
		if (args.includes("list-panes") && args.includes("#{pane_id}")) return mockPaneIds.join("\n");
		if (args[0] === "kill-pane" && args.includes("-a")) {
			const targetIndex = args.indexOf("-t");
			const targetPane = (targetIndex >= 0 && targetIndex + 1 < args.length ? args[targetIndex + 1] : mockPaneIds[0]) ?? "%0";
			const survivingPaneIds = mockPaneIds.filter((paneId) => paneId === targetPane || mockProtectedPaneIds.includes(paneId));
			mockPaneIds = survivingPaneIds.includes(targetPane) ? survivingPaneIds : [targetPane, ...survivingPaneIds];
			const parsedCounter = Number(targetPane.replace("%", ""));
			if (!Number.isNaN(parsedCounter)) mockPaneCounter = parsedCounter;
			return "";
		}
		if (args[0] === "kill-pane") {
			const targetIndex = args.indexOf("-t");
			const targetPane = targetIndex >= 0 && targetIndex + 1 < args.length ? args[targetIndex + 1] : void 0;
			if (typeof targetPane === "string") mockPaneIds = mockPaneIds.filter((paneId) => paneId !== targetPane);
			return "";
		}
		if (args.includes("split-window")) {
			mockPaneCounter += 1;
			const newPaneId = `%${mockPaneCounter}`;
			mockPaneIds = [...mockPaneIds, newPaneId];
			if (args.includes("-P") && args.includes("#{pane_id}")) return newPaneId;
		}
		return "";
	};
	return {
		execute,
		async executeMany(commandsList) {
			for (const args of commandsList) await execute(args);
		},
		isDryRun() {
			return true;
		},
		logCommand() {},
		getExecutedCommands() {
			return executedCommands;
		},
		clearExecutedCommands() {
			executedCommands = [];
		},
		setMockPaneIds(paneIds) {
			mockPaneIds = [...paneIds];
		},
		getPaneIds() {
			return mockPaneIds;
		},
		setMockProtectedPaneIds(paneIds) {
			mockProtectedPaneIds = [...paneIds];
		},
		isInTmuxSession,
		async verifyTmuxEnvironment() {
			if (!isInTmuxSession()) throw createEnvironmentError("Must be run inside a tmux session", ErrorCodes.NOT_IN_TMUX_SESSION, { hint: "Please start a tmux session and try again" });
		},
		getCommandString: toCommandString,
		async getCurrentSessionName() {
			return "mock-session";
		}
	};
};

//#endregion
//#region src/backends/tmux/executor.ts
const createTmuxExecutor = (options = {}) => {
	const executor = resolveExecutor(options);
	const isInTmuxSession = () => {
		return Boolean(process.env.TMUX);
	};
	const verifyTmuxEnvironment = async () => {
		if (!isInTmuxSession()) throw createEnvironmentError("Must be run inside a tmux session", ErrorCodes.NOT_IN_TMUX_SESSION, { hint: "Please start a tmux session and try again" });
		if (executor.isDryRun()) return;
		try {
			await execa("tmux", ["-V"]);
		} catch (_error) {
			throw createEnvironmentError("tmux is not installed", ErrorCodes.TMUX_NOT_FOUND, { hint: "Please install tmux" });
		}
	};
	const execute = async (commandOrArgs) => {
		return executor.execute(commandOrArgs);
	};
	const executeMany = async (commandsList) => {
		for (const command of commandsList) await execute(command);
	};
	const getCommandString = (args) => {
		return ["tmux", ...args].join(" ");
	};
	const getCurrentSessionName = async () => {
		return execute([
			"display-message",
			"-p",
			"#{session_name}"
		]);
	};
	return {
		verifyTmuxEnvironment,
		execute,
		executeMany,
		isInTmuxSession,
		getCurrentSessionName,
		getCommandString,
		getExecutor: () => executor
	};
};
const resolveExecutor = (options) => {
	if (options.executor) return options.executor;
	if (options.dryRun === true) return createDryRunExecutor({ verbose: options.verbose });
	if (isTestEnvironment()) return createMockExecutor();
	return createRealExecutor({ verbose: options.verbose });
};
const isTestEnvironment = () => {
	return process.env.VDE_TEST_MODE === "true" || process.env.NODE_ENV === "test" || process.env.VITEST === "true";
};

//#endregion
//#region src/utils/async.ts
const waitForDelay = (ms) => {
	return new Promise((resolve) => {
		setTimeout(resolve, ms);
	});
};

//#endregion
//#region src/utils/pane-map.ts
const resolvePaneMapping = (paneMap, virtualId) => {
	const direct = paneMap.get(virtualId);
	if (typeof direct === "string" && direct.length > 0) return direct;
	let ancestor = virtualId;
	while (ancestor.includes(".")) {
		ancestor = ancestor.slice(0, ancestor.lastIndexOf("."));
		const candidate = paneMap.get(ancestor);
		if (typeof candidate === "string" && candidate.length > 0) {
			paneMap.set(virtualId, candidate);
			return candidate;
		}
	}
	for (const [key, value] of paneMap.entries()) if (key.startsWith(`${virtualId}.`)) {
		if (typeof value === "string" && value.length > 0) {
			paneMap.set(virtualId, value);
			return value;
		}
	}
};

//#endregion
//#region src/executor/split-step.ts
const asSplitStep = (step, field) => {
	if (step.kind !== "split") throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: `Non-split step cannot resolve split ${field}`,
		path: step.id,
		details: { kind: step.kind }
	});
	return step;
};
const resolveSplitOrientation = (step) => {
	const splitStep = asSplitStep(step, "orientation");
	if (splitStep.orientation === "horizontal" || splitStep.orientation === "vertical") return splitStep.orientation;
	throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Split step missing orientation metadata",
		path: splitStep.id,
		details: { orientation: splitStep.orientation }
	});
};
const resolveLegacySplitSizing = (step) => {
	if (typeof step.percentage === "number" && Number.isFinite(step.percentage)) return {
		mode: "percent",
		percentage: step.percentage
	};
};
const resolveSplitSizingMetadata = (step) => {
	if (step.splitSizing !== void 0) return step.splitSizing;
	return resolveLegacySplitSizing(step);
};
const resolveSplitSize = (step, context = {}) => {
	const splitStep = asSplitStep(step, "sizing");
	const splitSizing = resolveSplitSizingMetadata(splitStep);
	if (splitSizing === void 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Split step missing sizing metadata",
		path: splitStep.id,
		details: {
			splitSizing: splitStep.splitSizing,
			percentage: splitStep.percentage
		}
	});
	if (splitSizing.mode === "percent") {
		if (Number.isFinite(splitSizing.percentage)) return {
			mode: "percent",
			percentage: String(clampSplitPercentage(splitSizing.percentage))
		};
		throw createCoreError("execution", {
			code: ErrorCodes.INVALID_PLAN,
			message: "Split step missing percentage metadata",
			path: splitStep.id,
			details: { percentage: splitSizing.percentage }
		});
	}
	const dynamic = splitSizing;
	const resolvedPaneCells = context.paneCells;
	if (typeof resolvedPaneCells !== "number" || !Number.isInteger(resolvedPaneCells) || resolvedPaneCells <= 0) throw createCoreError("execution", {
		code: ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED,
		message: "Pane size is unavailable for dynamic split sizing",
		path: splitStep.id,
		details: buildDynamicResolutionDetails(splitStep, dynamic, context)
	});
	const { remainingFixedCells, remainingWeight, remainingWeightPaneCount, target } = dynamic;
	if (!Number.isInteger(remainingFixedCells) || remainingFixedCells < 0 || !Number.isFinite(remainingWeight) || remainingWeight < 0 || !Number.isInteger(remainingWeightPaneCount) || remainingWeightPaneCount < 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Split step has invalid dynamic sizing metadata",
		path: splitStep.id,
		details: buildDynamicResolutionDetails(splitStep, dynamic, context)
	});
	const minTargetCells = target.kind === "fixed-cells" ? target.cells : 1;
	const minCreatedCells = remainingFixedCells + remainingWeightPaneCount;
	if (!Number.isInteger(minTargetCells) || minTargetCells <= 0 || !Number.isInteger(minCreatedCells) || minCreatedCells < 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Split step has invalid minimum-cell constraints",
		path: splitStep.id,
		details: buildDynamicResolutionDetails(splitStep, dynamic, context)
	});
	if (resolvedPaneCells < minTargetCells + minCreatedCells) throw createCoreError("execution", {
		code: ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED,
		message: "Pane is too small for requested fixed and weighted splits",
		path: splitStep.id,
		details: buildDynamicResolutionDetails(splitStep, dynamic, context)
	});
	let targetCells;
	if (target.kind === "fixed-cells") targetCells = target.cells;
	else {
		if (!Number.isFinite(target.weight) || target.weight <= 0) throw createCoreError("execution", {
			code: ErrorCodes.INVALID_PLAN,
			message: "Split step has invalid weight metadata",
			path: splitStep.id,
			details: buildDynamicResolutionDetails(splitStep, dynamic, context)
		});
		const availableForWeights = resolvedPaneCells - remainingFixedCells;
		const weightTotal = target.weight + remainingWeight;
		if (!Number.isFinite(weightTotal) || weightTotal <= 0) throw createCoreError("execution", {
			code: ErrorCodes.INVALID_PLAN,
			message: "Split step has invalid dynamic weight totals",
			path: splitStep.id,
			details: buildDynamicResolutionDetails(splitStep, dynamic, context)
		});
		targetCells = clamp(Math.round(availableForWeights * target.weight / weightTotal), minTargetCells, resolvedPaneCells - minCreatedCells);
	}
	const createdCells = resolvedPaneCells - targetCells;
	if (targetCells < minTargetCells || createdCells < minCreatedCells || targetCells <= 0 || createdCells <= 0) throw createCoreError("execution", {
		code: ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED,
		message: "Unable to resolve split size without violating pane minimums",
		path: splitStep.id,
		details: buildDynamicResolutionDetails(splitStep, dynamic, context, {
			targetCells,
			createdCells
		})
	});
	return {
		mode: "cells",
		cells: String(createdCells),
		targetCells,
		createdCells
	};
};
const clampSplitPercentage = (value) => {
	return Math.min(99, Math.max(1, Math.round(value)));
};
const clamp = (value, min, max) => {
	return Math.min(max, Math.max(min, value));
};
const buildDynamicResolutionDetails = (splitStep, splitSizing, context, extra = {}) => {
	return {
		stepId: splitStep.id,
		paneId: context.paneId,
		paneCells: context.paneCells,
		targetSpec: splitSizing.target,
		remainingFixedCells: splitSizing.remainingFixedCells,
		remainingWeight: splitSizing.remainingWeight,
		remainingWeightPaneCount: splitSizing.remainingWeightPaneCount,
		requiredVersion: context.requiredVersion,
		detectedVersion: context.detectedVersion,
		rawPaneRecord: context.rawPaneRecord,
		...extra
	};
};

//#endregion
//#region src/utils/template-tokens.ts
const TemplateTokenErrorImpl = function TemplateTokenError(message, tokenType, availablePanes) {
	const error = new Error(message);
	Object.setPrototypeOf(error, TemplateTokenErrorImpl.prototype);
	error.name = "TemplateTokenError";
	error.tokenType = tokenType;
	error.availablePanes = availablePanes;
	return error;
};
TemplateTokenErrorImpl.prototype = Object.create(Error.prototype);
TemplateTokenErrorImpl.prototype.constructor = TemplateTokenErrorImpl;
const TemplateTokenError = TemplateTokenErrorImpl;
/**
* Replaces template tokens in a command string with actual pane IDs.
*
* Uses a single-pass regex replacement to avoid nested token issues.
* All tokens are replaced in a single pass, preventing already-replaced
* values from being re-processed.
*
* @param input - Object containing the command and pane ID mappings
* @returns The command string with all template tokens replaced
* @throws TemplateTokenError if a referenced pane name is not found in the mapping,
*   or if {{window_id}} is used but no `windowId` was supplied
*/
const replaceTemplateTokens = ({ command, currentPaneRealId, focusPaneRealId, nameToRealIdMap, windowId }) => {
	return command.replace(/\{\{(this_pane|focus_pane|window_id|pane_id:([^}]+))\}\}/g, (match, tokenContent, paneName) => {
		if (tokenContent === "this_pane") return currentPaneRealId;
		if (tokenContent === "focus_pane") return focusPaneRealId;
		if (tokenContent === "window_id") {
			if (windowId === void 0) throw new TemplateTokenError("Window ID is not available. {{window_id}} can only be resolved in hooks.afterApply.", "window_id");
			return windowId;
		}
		if (tokenContent.startsWith("pane_id:") && paneName !== void 0) {
			const trimmedName = paneName.trim();
			const paneId = nameToRealIdMap.get(trimmedName);
			if (paneId === void 0) throw new TemplateTokenError(`Pane name "${trimmedName}" not found. Available panes: ${Array.from(nameToRealIdMap.keys()).join(", ")}`, "pane_id", Array.from(nameToRealIdMap.keys()));
			return paneId;
		}
		return match;
	});
};
/**
* Builds a mapping from pane names to real pane IDs.
*
* **Duplicate Name Handling:**
* If multiple panes share the same name, the last one in the terminals
* array wins. This follows the iteration order of the layout tree.
* It's recommended to use unique names for panes to avoid ambiguity
* in template token references.
*
* **Virtual to Real ID Resolution:**
* Only panes with successfully resolved real IDs (present in paneMap)
* are included in the resulting map. This ensures that template tokens
* only reference panes that have been properly created.
*
* @param terminals - Array of emitted terminals from the layout plan
* @param paneMap - Map from virtual pane IDs to real pane IDs (backend-specific)
* @returns A map from pane names to real pane IDs
*/
const buildNameToRealIdMap = (terminals, paneMap) => {
	const nameToRealIdMap = /* @__PURE__ */ new Map();
	for (const terminal of terminals) {
		const realId = paneMap.get(terminal.virtualPaneId);
		if (realId !== void 0) nameToRealIdMap.set(terminal.name, realId);
	}
	return nameToRealIdMap;
};

//#endregion
//#region src/executor/terminal-command-preparation.ts
const SINGLE_QUOTE$1 = "'";
const SHELL_SINGLE_QUOTE_ESCAPE$1 = `'"'"'`;
const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
const shellQuoteLiteral = (value) => {
	return `'${value.split(SINGLE_QUOTE$1).join(SHELL_SINGLE_QUOTE_ESCAPE$1)}'`;
};
const assertValidEnvKey = (key) => {
	if (!ENV_KEY_PATTERN.test(key)) throw new Error(`Invalid environment variable name: ${key}`);
};
const normalizeDelay = (delay) => {
	return typeof delay === "number" && Number.isFinite(delay) && delay > 0 ? delay : 0;
};
const applyEphemeralSuffix = (command, terminal) => {
	if (terminal.ephemeral !== true) return command;
	return terminal.closeOnError === true ? `${command}; exit` : `${command}; [ $? -eq 0 ] && exit`;
};
const prepareTerminalCommands = ({ terminals, focusPaneVirtualId, resolveRealPaneId, onTemplateTokenError }) => {
	const paneMap = /* @__PURE__ */ new Map();
	for (const terminal of terminals) paneMap.set(terminal.virtualPaneId, resolveRealPaneId(terminal.virtualPaneId));
	const focusPaneRealId = paneMap.get(focusPaneVirtualId) ?? resolveRealPaneId(focusPaneVirtualId);
	const nameToRealIdMap = buildNameToRealIdMap(terminals, paneMap);
	return {
		focusPaneRealId,
		commands: terminals.map((terminal) => {
			const realPaneId = paneMap.get(terminal.virtualPaneId);
			if (realPaneId === void 0) throw new Error(`Unknown pane: ${terminal.virtualPaneId}`);
			const cwdCommand = typeof terminal.cwd === "string" && terminal.cwd.length > 0 ? `cd -- ${shellQuoteLiteral(terminal.cwd)}` : void 0;
			const envCommands = terminal.env === void 0 ? [] : Object.entries(terminal.env).map(([key, value]) => {
				assertValidEnvKey(key);
				return {
					key,
					command: `export ${key}=${shellQuoteLiteral(String(value))}`
				};
			});
			const title = typeof terminal.title === "string" && terminal.title.length > 0 ? terminal.title : void 0;
			let command;
			if (typeof terminal.command === "string" && terminal.command.length > 0) try {
				const commandUsesFocusToken = terminal.command.includes("{{focus_pane}}");
				command = {
					text: applyEphemeralSuffix(replaceTemplateTokens({
						command: terminal.command,
						currentPaneRealId: realPaneId,
						focusPaneRealId: commandUsesFocusToken ? focusPaneRealId : "",
						nameToRealIdMap
					}), terminal),
					delayMs: normalizeDelay(terminal.delay)
				};
			} catch (error) {
				if (error instanceof TemplateTokenError) return onTemplateTokenError({
					terminal,
					error
				});
				throw error;
			}
			return {
				terminal,
				realPaneId,
				cwdCommand,
				envCommands,
				title,
				command
			};
		})
	};
};

//#endregion
//#region src/executor/plan-runner-helpers.ts
const executeSplitStep = async ({ step, executor, paneMap, detectedVersion }) => {
	const targetVirtualId = ensureNonEmpty(step.targetPaneId, () => raiseExecutionError(ErrorCodes.MISSING_TARGET, {
		message: "Split step missing target pane metadata",
		path: step.id
	}));
	const targetRealId = ensureNonEmpty(resolvePaneId(paneMap, targetVirtualId), () => raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: `Unknown target pane: ${targetVirtualId}`,
		path: step.id
	}));
	const panesBefore = await listPaneIds(executor, step);
	const splitCommand = await buildSplitCommand({
		step,
		targetRealId,
		executor,
		detectedVersion
	});
	await executeCommand(executor, splitCommand, {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: `Failed to execute split step ${step.id}`,
		path: step.id,
		details: { command: splitCommand }
	});
	const newPaneId = ensureNonEmpty(findNewPaneId$1(panesBefore, await listPaneIds(executor, step)), () => raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: "Unable to determine newly created pane",
		path: step.id
	}));
	const createdVirtualId = step.createdPaneId;
	if (typeof createdVirtualId === "string" && createdVirtualId.length > 0) registerPane(paneMap, createdVirtualId, newPaneId);
};
const executeFocusStep = async ({ step, executor, paneMap }) => {
	const targetVirtualId = ensureNonEmpty(step.targetPaneId, () => raiseExecutionError(ErrorCodes.MISSING_TARGET, {
		message: "Focus step missing target pane metadata",
		path: step.id
	}));
	const command = buildFocusCommand(ensureNonEmpty(resolvePaneId(paneMap, targetVirtualId), () => raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: `Unknown focus pane: ${targetVirtualId}`,
		path: step.id
	})));
	await executeCommand(executor, command, {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: `Failed to execute focus step ${step.id}`,
		path: step.id,
		details: { command }
	});
};
const executeTerminalCommands = async ({ terminals, executor, paneMap, focusPaneVirtualId }) => {
	if (!paneMap.has(focusPaneVirtualId)) raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: `Unknown focus pane: ${focusPaneVirtualId}`,
		path: focusPaneVirtualId
	});
	ensureNonEmpty(resolvePaneId(paneMap, focusPaneVirtualId), () => raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: `Unknown focus pane: ${focusPaneVirtualId}`,
		path: focusPaneVirtualId
	}));
	const resolveRealPaneId = (virtualPaneId) => {
		return ensureNonEmpty(resolvePaneId(paneMap, virtualPaneId), () => raiseExecutionError(ErrorCodes.INVALID_PANE, {
			message: `Unknown terminal pane: ${virtualPaneId}`,
			path: virtualPaneId
		}));
	};
	const prepared = prepareTerminalCommands({
		terminals,
		focusPaneVirtualId,
		resolveRealPaneId,
		onTemplateTokenError: ({ terminal, error }) => {
			throw createCoreError("execution", {
				code: ErrorCodes.TEMPLATE_TOKEN_ERROR,
				message: `Template token resolution failed for pane ${terminal.virtualPaneId}: ${error.message}`,
				path: terminal.virtualPaneId,
				details: {
					command: terminal.command,
					tokenType: error.tokenType,
					availablePanes: error.availablePanes
				}
			});
		}
	});
	for (const commandSet of prepared.commands) {
		const { terminal, realPaneId } = commandSet;
		if (typeof commandSet.cwdCommand === "string") await executeCommand(executor, [
			"send-keys",
			"-t",
			realPaneId,
			commandSet.cwdCommand,
			"Enter"
		], {
			code: ErrorCodes.TMUX_COMMAND_FAILED,
			message: `Failed to change directory for pane ${terminal.virtualPaneId}`,
			path: terminal.virtualPaneId,
			details: { cwd: terminal.cwd }
		});
		for (const envEntry of commandSet.envCommands) await executeCommand(executor, [
			"send-keys",
			"-t",
			realPaneId,
			envEntry.command,
			"Enter"
		], {
			code: ErrorCodes.TMUX_COMMAND_FAILED,
			message: `Failed to set environment variable ${envEntry.key}`,
			path: terminal.virtualPaneId
		});
		if (typeof commandSet.title === "string") await executeCommand(executor, [
			"select-pane",
			"-t",
			realPaneId,
			"-T",
			commandSet.title
		], {
			code: ErrorCodes.TMUX_COMMAND_FAILED,
			message: `Failed to set pane title for pane ${terminal.virtualPaneId}`,
			path: terminal.virtualPaneId,
			details: { title: commandSet.title }
		});
		if (commandSet.command !== void 0) {
			if (commandSet.command.delayMs > 0) await waitForDelay(commandSet.command.delayMs);
			await executeCommand(executor, [
				"send-keys",
				"-t",
				realPaneId,
				commandSet.command.text,
				"Enter"
			], {
				code: ErrorCodes.TMUX_COMMAND_FAILED,
				message: `Failed to execute command for pane ${terminal.virtualPaneId}`,
				path: terminal.virtualPaneId,
				details: { command: terminal.command }
			});
		}
	}
};
const executeCommand = async (executor, command, context) => {
	try {
		return await executor.execute([...command]);
	} catch (error) {
		if (error instanceof Error && "code" in error && "message" in error) {
			const candidate = error;
			throw createCoreError("execution", {
				code: typeof candidate.code === "string" ? candidate.code : context.code,
				message: candidate.message ?? context.message,
				path: context.path,
				details: candidate.details ?? context.details
			});
		}
		throw createCoreError("execution", {
			code: context.code,
			message: context.message,
			path: context.path,
			details: context.details
		});
	}
};
const resolveCurrentPaneId = async ({ executor, contextPath, isDryRun }) => {
	const envPaneId = process.env.TMUX_PANE;
	if (typeof envPaneId === "string" && envPaneId.trim().length > 0) return normalizePaneId(envPaneId);
	if (isDryRun) return "%0";
	const paneId = (await executeCommand(executor, [
		"display-message",
		"-p",
		"#{pane_id}"
	], {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to resolve current tmux pane",
		path: contextPath
	})).trim();
	if (paneId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.NOT_IN_TMUX_SESSION,
		message: "Unable to determine current tmux pane",
		path: contextPath
	});
	return normalizePaneId(paneId);
};
const listWindowPaneIds = async (executor, contextPath) => {
	return (await executeCommand(executor, [
		"list-panes",
		"-F",
		"#{pane_id}"
	], {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to list tmux panes",
		path: contextPath
	})).split("\n").map((pane) => pane.trim()).filter((pane) => pane.length > 0);
};
const listPaneIds = async (executor, step) => {
	return listWindowPaneIds(executor, step.id);
};
const findNewPaneId$1 = (before, after) => {
	const beforeSet = new Set(before);
	return after.find((id) => !beforeSet.has(id));
};
const buildSplitCommand = async ({ step, targetRealId, executor, detectedVersion }) => {
	const orientation = resolveSplitOrientation(step);
	const directionFlag = orientation === "horizontal" ? "-h" : "-v";
	if (isDynamicSplit(step)) {
		const splitSize = resolveSplitSize(step, {
			paneCells: await resolveTmuxPaneCells({
				executor,
				step,
				targetRealId,
				orientation,
				detectedVersion
			}),
			paneId: targetRealId,
			detectedVersion,
			rawPaneRecord: {
				backend: "tmux",
				paneId: targetRealId,
				sourceFormat: "tmux-format"
			}
		});
		if (splitSize.mode === "cells") return [
			"split-window",
			directionFlag,
			"-t",
			targetRealId,
			"-l",
			splitSize.cells
		];
		return raiseExecutionError(ErrorCodes.INVALID_PLAN, {
			message: "Dynamic split resolved to a non-cell sizing mode",
			path: step.id,
			details: { splitSize }
		});
	}
	const splitSize = resolveSplitSize(step, {
		paneId: targetRealId,
		detectedVersion
	});
	if (splitSize.mode === "percent") return [
		"split-window",
		directionFlag,
		"-t",
		targetRealId,
		"-p",
		splitSize.percentage
	];
	return raiseExecutionError(ErrorCodes.INVALID_PLAN, {
		message: "Percent split resolved to a non-percent sizing mode",
		path: step.id,
		details: { splitSize }
	});
};
const buildFocusCommand = (targetRealId) => {
	return [
		"select-pane",
		"-t",
		targetRealId
	];
};
const normalizePaneId = (raw) => {
	const trimmed = raw.trim();
	return trimmed.length === 0 ? "%0" : trimmed;
};
const registerPane = (paneMap, virtualId, realId) => {
	paneMap.set(virtualId, realId);
};
const resolvePaneId = (paneMap, virtualId) => {
	return resolvePaneMapping(paneMap, virtualId);
};
const resolveTmuxPaneCells = async ({ executor, step, targetRealId, orientation, detectedVersion }) => {
	const format = orientation === "horizontal" ? "#{pane_width}" : "#{pane_height}";
	const output = await executeCommand(executor, [
		"display-message",
		"-p",
		"-t",
		targetRealId,
		format
	], {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to resolve tmux pane size",
		path: step.id,
		details: { command: [
			"display-message",
			"-p",
			"-t",
			targetRealId,
			format
		] }
	});
	const value = Number.parseInt(output.trim(), 10);
	if (!Number.isInteger(value) || value <= 0) raiseExecutionError(ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED, {
		message: "Unable to parse tmux pane size",
		path: step.id,
		details: {
			paneId: targetRealId,
			orientation,
			output,
			detectedVersion
		}
	});
	return value;
};
const isDynamicSplit = (step) => {
	return step.kind === "split" && step.splitSizing?.mode === "dynamic-cells";
};
const ensureNonEmpty = (value, buildError) => {
	if (value === void 0 || value.length === 0) return buildError();
	return value;
};
const raiseExecutionError = (code, error) => {
	throw createCoreError("execution", {
		code,
		message: error.message,
		path: error.path,
		details: error.details
	});
};

//#endregion
//#region src/executor/sidebar-detection.ts
const SIDEBAR_LIST_PANES_FORMAT = "#{pane_id}	#{@vde_sidebar}";
const classifyWindowPanes = async (executor, contextPath, targetPaneId) => {
	const output = await executeCommand(executor, typeof targetPaneId === "string" && targetPaneId.length > 0 ? [
		"list-panes",
		"-t",
		targetPaneId,
		"-F",
		SIDEBAR_LIST_PANES_FORMAT
	] : [
		"list-panes",
		"-F",
		SIDEBAR_LIST_PANES_FORMAT
	], {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to list tmux panes for sidebar detection",
		path: contextPath
	});
	const sidebarPanes = [];
	const normalPanes = [];
	for (const line of output.split("\n")) {
		const [paneId, sidebarFlag] = line.split("	");
		if (typeof paneId !== "string" || paneId.trim().length === 0) continue;
		if (sidebarFlag?.trim() === "1") sidebarPanes.push(paneId.trim());
		else normalPanes.push(paneId.trim());
	}
	return {
		sidebarPanes,
		normalPanes
	};
};

//#endregion
//#region src/executor/plan-runner.ts
const executePlan = async ({ emission, executor, windowName, windowMode, onConfirmKill, detectedVersion }) => {
	const initialVirtualPaneId = emission.summary.initialPaneId;
	if (typeof initialVirtualPaneId !== "string" || initialVirtualPaneId.length === 0) raiseExecutionError(ErrorCodes.INVALID_PLAN, {
		message: "Plan emission is missing initial pane metadata",
		path: "plan.initialPaneId"
	});
	const paneMap = /* @__PURE__ */ new Map();
	const isDryRun = executor.isDryRun();
	let initialPaneId;
	if (windowMode === "current-window") {
		const currentPaneId = await resolveCurrentPaneId({
			executor,
			contextPath: initialVirtualPaneId,
			isDryRun
		});
		const { sidebarPanes, normalPanes } = await classifyWindowPanes(executor, initialVirtualPaneId, currentPaneId);
		const sidebarPaneIds = new Set(sidebarPanes);
		let originPaneId;
		if (sidebarPaneIds.has(currentPaneId)) {
			const [firstNormalPane] = normalPanes;
			originPaneId = firstNormalPane !== void 0 ? firstNormalPane : await splitPaneBesideSidebar({
				executor,
				sidebarPaneId: currentPaneId,
				contextPath: initialVirtualPaneId
			});
		} else originPaneId = currentPaneId;
		const panesToClose = normalPanes.filter((paneId) => paneId !== originPaneId);
		if (panesToClose.length > 0) {
			let confirmed = true;
			if (onConfirmKill !== void 0) confirmed = await onConfirmKill({
				panesToClose,
				dryRun: isDryRun
			});
			if (confirmed !== true) raiseExecutionError(ErrorCodes.USER_CANCELLED, {
				message: "Aborted layout application for current window",
				path: initialVirtualPaneId,
				details: { panes: panesToClose }
			});
			for (const paneId of panesToClose) await executeCommand(executor, [
				"kill-pane",
				"-t",
				paneId
			], {
				code: ErrorCodes.TMUX_COMMAND_FAILED,
				message: "Failed to close existing panes",
				path: initialVirtualPaneId,
				details: { command: [
					"kill-pane",
					"-t",
					paneId
				] }
			});
		}
		initialPaneId = normalizePaneId(originPaneId);
	} else {
		const newWindowCommand = [
			"new-window",
			"-P",
			"-F",
			"#{pane_id}"
		];
		if (typeof windowName === "string" && windowName.trim().length > 0) newWindowCommand.push("-n", windowName.trim());
		initialPaneId = normalizePaneId(await executeCommand(executor, newWindowCommand, {
			code: ErrorCodes.TMUX_COMMAND_FAILED,
			message: "Failed to create tmux window",
			path: initialVirtualPaneId
		}));
	}
	registerPane(paneMap, initialVirtualPaneId, initialPaneId);
	let executedSteps = 0;
	for (const step of emission.steps) if (step.kind === "split") {
		await executeSplitStep({
			step,
			executor,
			paneMap,
			detectedVersion
		});
		executedSteps += 1;
	} else if (step.kind === "focus") {
		await executeFocusStep({
			step,
			executor,
			paneMap
		});
		executedSteps += 1;
	} else raiseExecutionError(ErrorCodes.INVALID_PLAN, {
		message: `Unsupported step kind in emission: ${String(step.kind)}`,
		path: step.id
	});
	await executeTerminalCommands({
		terminals: emission.terminals,
		executor,
		paneMap,
		focusPaneVirtualId: emission.summary.focusPaneId
	});
	const finalRealFocus = resolvePaneId(paneMap, emission.summary.focusPaneId);
	if (typeof finalRealFocus === "string" && finalRealFocus.length > 0) await executeCommand(executor, [
		"select-pane",
		"-t",
		finalRealFocus
	], {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to restore focus",
		path: emission.summary.focusPaneId
	});
	return {
		executedSteps,
		paneMap
	};
};
/**
* Splits a fresh pane beside the sidebar so it can be used as the layout's origin
* pane. Only reached when the current window contains nothing but sidebar panes
* (no normal pane to reuse). The split direction is fixed to a horizontal split
* away from the sidebar (i.e. the new pane appears on the opposite side).
*
* Uses `-P -F "#{pane_id}"` to have tmux report the newly created pane id directly
* in the split-window output, rather than diffing a follow-up `list-panes` call
* against the pre-split pane set (which was fragile under concurrent pane changes).
*/
const splitPaneBesideSidebar = async ({ executor, sidebarPaneId, contextPath }) => {
	const command = [
		"split-window",
		"-h",
		"-P",
		"-F",
		"#{pane_id}",
		"-t",
		sidebarPaneId
	];
	const newPaneId = (await executeCommand(executor, command, {
		code: ErrorCodes.TMUX_COMMAND_FAILED,
		message: "Failed to split a pane beside the sidebar",
		path: contextPath,
		details: { command }
	})).trim();
	if (newPaneId.length === 0) return raiseExecutionError(ErrorCodes.INVALID_PANE, {
		message: "Unable to determine the pane created beside the sidebar",
		path: contextPath
	});
	return newPaneId;
};

//#endregion
//#region src/executor/step-target.ts
const getStepLabel = (step) => {
	if (step.kind === "split") return "Split";
	if (step.kind === "focus") return "Focus";
	return "Step";
};
const resolveRequiredStepTargetPaneId = (step) => {
	if (typeof step.targetPaneId === "string" && step.targetPaneId.length > 0) return step.targetPaneId;
	throw createCoreError("execution", {
		code: ErrorCodes.MISSING_TARGET,
		message: `${getStepLabel(step)} step missing target pane metadata`,
		path: step.id
	});
};

//#endregion
//#region src/executor/unsupported-step-kind.ts
const createUnsupportedStepKindError = (step) => {
	return createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: `Unsupported step kind in emission: ${String(step.kind)}`,
		path: step.id
	});
};

//#endregion
//#region src/backends/tmux/backend.ts
const TMUX_VERSION_REGEX = /^tmux\s+(.+)$/;
const createTmuxBackend = (context) => {
	const tmuxExecutor = createTmuxExecutor({
		executor: context.executor,
		verbose: context.verbose,
		dryRun: context.dryRun
	});
	let detectedVersion;
	const buildDryRunSteps = (emission) => {
		const paneSizes = /* @__PURE__ */ new Map();
		const initialPane = emission.summary.initialPaneId;
		const initialPaneSize = resolveInitialTmuxPaneSize();
		if (initialPaneSize !== void 0) paneSizes.set(initialPane, initialPaneSize);
		return emission.steps.map((step) => ({
			backend: "tmux",
			summary: step.summary,
			command: tmuxExecutor.getCommandString(buildTmuxCommand({
				step,
				paneSizes,
				detectedVersion
			}))
		}));
	};
	const verifyEnvironment = async () => {
		if (context.dryRun) return;
		await tmuxExecutor.verifyTmuxEnvironment();
		detectedVersion = await detectTmuxVersion(tmuxExecutor);
	};
	const applyPlan = async ({ emission, windowMode, windowName }) => {
		const executionResult = await executePlan({
			emission,
			executor: tmuxExecutor.getExecutor(),
			windowMode,
			windowName,
			onConfirmKill: context.prompt,
			detectedVersion
		});
		const paneMap = executionResult.paneMap ?? /* @__PURE__ */ new Map();
		const focusPaneId = paneMap.get(emission.summary.focusPaneId);
		return {
			executedSteps: executionResult.executedSteps,
			focusPaneId,
			paneNameToRealId: buildNameToRealIdMap(emission.terminals, paneMap),
			windowId: await resolveTmuxWindowId({
				tmuxExecutor,
				focusPaneId
			})
		};
	};
	return {
		verifyEnvironment,
		applyPlan,
		getDryRunSteps: buildDryRunSteps
	};
};
const resolveTmuxWindowId = async ({ tmuxExecutor, focusPaneId }) => {
	if (focusPaneId === void 0) return;
	try {
		const trimmed = (await tmuxExecutor.execute([
			"display-message",
			"-p",
			"-t",
			focusPaneId,
			"#{window_id}"
		])).trim();
		return trimmed.length > 0 ? trimmed : void 0;
	} catch {
		return;
	}
};
const buildTmuxCommand = ({ step, paneSizes, detectedVersion }) => {
	if (step.kind === "split") {
		const target = resolveRequiredStepTargetPaneId(step);
		const orientation = resolveSplitOrientation(step);
		const direction = orientation === "horizontal" ? "-h" : "-v";
		if (step.splitSizing?.mode === "dynamic-cells") {
			const paneSize = paneSizes.get(target);
			if (paneSize !== void 0) {
				const paneCells = orientation === "horizontal" ? paneSize.cols : paneSize.rows;
				try {
					const splitSize = resolveSplitSize(step, {
						paneCells,
						paneId: target,
						detectedVersion,
						rawPaneRecord: {
							backend: "tmux",
							paneId: target,
							sourceFormat: "tmux-format",
							size: {
								cols: paneSize.cols,
								rows: paneSize.rows
							}
						}
					});
					if (splitSize.mode === "cells") {
						if (typeof step.createdPaneId === "string" && step.createdPaneId.length > 0) updatePaneSizes({
							paneSizes,
							targetPaneId: target,
							createdPaneId: step.createdPaneId,
							orientation,
							targetCells: splitSize.targetCells,
							createdCells: splitSize.createdCells
						});
						return [
							"split-window",
							direction,
							"-t",
							target,
							"-l",
							splitSize.cells
						];
					}
				} catch (error) {
					if (isCoreError(error) && error.code === ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED) {} else throw error;
				}
			}
			return [
				"split-window",
				direction,
				"-t",
				target,
				"-l",
				"<dynamic>"
			];
		}
		const splitSize = resolveSplitSize(step, {
			paneId: target,
			detectedVersion
		});
		if (splitSize.mode !== "percent") throw createUnsupportedStepKindError(step);
		return [
			"split-window",
			direction,
			"-t",
			target,
			"-p",
			splitSize.percentage
		];
	}
	if (step.kind === "focus") return [
		"select-pane",
		"-t",
		resolveRequiredStepTargetPaneId(step)
	];
	throw createUnsupportedStepKindError(step);
};
const detectTmuxVersion = async (tmuxExecutor) => {
	try {
		return (await tmuxExecutor.execute(["-V"])).trim().match(TMUX_VERSION_REGEX)?.[1]?.trim();
	} catch {
		return;
	}
};
const resolveInitialTmuxPaneSize = () => {
	const tmuxPane = process.env.TMUX_PANE;
	const tmuxSession = process.env.TMUX;
	if (typeof tmuxSession !== "string" || tmuxSession.length === 0 || typeof tmuxPane !== "string" || tmuxPane.length === 0) return;
	const currentPane = queryTmuxCurrentPaneSizeAndSidebarFlag(tmuxPane);
	if (currentPane === void 0) return;
	if (!currentPane.isSidebar) return {
		cols: currentPane.cols,
		rows: currentPane.rows
	};
	const originPaneId = resolveDryRunOriginPaneId(tmuxPane);
	if (originPaneId === void 0) return;
	return queryTmuxPaneSize(originPaneId);
};
const queryTmuxCurrentPaneSizeAndSidebarFlag = (paneId) => {
	try {
		const [colsRaw = "", rowsRaw = "", sidebarFlag = ""] = execFileSync("tmux", [
			"display-message",
			"-p",
			"-t",
			paneId,
			"#{pane_width} #{pane_height} #{@vde_sidebar}"
		], {
			encoding: "utf8",
			stdio: [
				"ignore",
				"pipe",
				"ignore"
			]
		}).trim().split(/\s+/, 3);
		const cols = Number.parseInt(colsRaw, 10);
		const rows = Number.parseInt(rowsRaw, 10);
		if (!Number.isInteger(cols) || cols <= 0 || !Number.isInteger(rows) || rows <= 0) return;
		return {
			cols,
			rows,
			isSidebar: sidebarFlag === "1"
		};
	} catch {
		return;
	}
};
const resolveDryRunOriginPaneId = (targetPaneId) => {
	try {
		const output = execFileSync("tmux", [
			"list-panes",
			"-t",
			targetPaneId,
			"-F",
			SIDEBAR_LIST_PANES_FORMAT
		], {
			encoding: "utf8",
			stdio: [
				"ignore",
				"pipe",
				"ignore"
			]
		});
		for (const line of output.split("\n")) {
			const [paneId, sidebarFlag] = line.split("	");
			if (typeof paneId === "string" && paneId.trim().length > 0 && sidebarFlag?.trim() !== "1") return paneId.trim();
		}
		return;
	} catch {
		return;
	}
};
const queryTmuxPaneSize = (paneId) => {
	try {
		const [colsRaw = "", rowsRaw = ""] = execFileSync("tmux", [
			"display-message",
			"-p",
			"-t",
			paneId,
			"#{pane_width} #{pane_height}"
		], {
			encoding: "utf8",
			stdio: [
				"ignore",
				"pipe",
				"ignore"
			]
		}).trim().split(/\s+/, 2);
		const cols = Number.parseInt(colsRaw, 10);
		const rows = Number.parseInt(rowsRaw, 10);
		if (!Number.isInteger(cols) || cols <= 0 || !Number.isInteger(rows) || rows <= 0) return;
		return {
			cols,
			rows
		};
	} catch {
		return;
	}
};

//#endregion
//#region src/backends/wezterm/list-parser.ts
const toIdString = (value) => {
	if (typeof value === "string") return value;
	if (typeof value === "number") return value.toString();
};
const isNonEmptyString = (value) => {
	return typeof value === "string" && value.length > 0;
};
const toWorkspaceString = (value) => {
	if (typeof value === "string" && value.length > 0) return value;
};
const toPositiveInteger = (value) => {
	if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return;
	return value;
};
const toPaneSize = (value) => {
	if (typeof value !== "object" || value === null) return;
	const raw = value;
	const cols = toPositiveInteger(raw.cols);
	const rows = toPositiveInteger(raw.rows);
	if (cols === void 0 && rows === void 0) return;
	return {
		cols,
		rows
	};
};
const toImmutablePanes = (panes) => {
	return panes.map((pane) => ({
		paneId: pane.paneId,
		isActive: pane.isActive,
		...pane.size !== void 0 ? { size: pane.size } : {},
		rawPaneRecord: pane.rawPaneRecord
	}));
};
const toImmutableTabs = (tabs) => {
	return Array.from(tabs.values()).map((tabRecord) => ({
		tabId: tabRecord.tabId,
		isActive: tabRecord.isActive,
		panes: toImmutablePanes(tabRecord.panes)
	}));
};
const toImmutableWindows = (windows) => {
	return Array.from(windows.values()).map((windowRecord) => ({
		windowId: windowRecord.windowId,
		isActive: windowRecord.isActive,
		workspace: windowRecord.workspace,
		tabs: toImmutableTabs(windowRecord.tabs)
	}));
};
const normalizeArrayEntry = (entry) => {
	if (typeof entry !== "object" || entry === null) return;
	const listEntry = entry;
	const windowIdRaw = toIdString(listEntry.window_id);
	const paneIdRaw = toIdString(listEntry.pane_id);
	const tabIdRaw = toIdString(listEntry.tab_id) ?? windowIdRaw;
	if (!isNonEmptyString(windowIdRaw) || !isNonEmptyString(tabIdRaw) || !isNonEmptyString(paneIdRaw)) return;
	return {
		windowId: windowIdRaw,
		tabId: tabIdRaw,
		paneId: paneIdRaw,
		workspace: toWorkspaceString(listEntry.workspace),
		isActive: listEntry.is_active === true,
		size: toPaneSize(listEntry.size)
	};
};
const getOrCreateWindowRecord = (windows, entry) => {
	const existingWindow = windows.get(entry.windowId);
	if (existingWindow) {
		if (entry.workspace !== void 0 && existingWindow.workspace === void 0) existingWindow.workspace = entry.workspace;
		return existingWindow;
	}
	const createdWindow = {
		windowId: entry.windowId,
		isActive: false,
		workspace: entry.workspace,
		tabs: /* @__PURE__ */ new Map()
	};
	windows.set(entry.windowId, createdWindow);
	return createdWindow;
};
const getOrCreateTabRecord = (windowRecord, tabId) => {
	const existingTab = windowRecord.tabs.get(tabId);
	if (existingTab) return existingTab;
	const createdTab = {
		tabId,
		isActive: false,
		panes: []
	};
	windowRecord.tabs.set(tabId, createdTab);
	return createdTab;
};
const createArrayRawPaneRecord = (entry) => {
	return {
		backend: "wezterm",
		windowId: entry.windowId,
		tabId: entry.tabId,
		paneId: entry.paneId,
		isActive: entry.isActive,
		size: entry.size,
		sourceFormat: "wezterm-array"
	};
};
const createObjectRawPaneRecord = ({ rawPane, context, paneId, size }) => {
	return {
		backend: "wezterm",
		windowId: context.windowId,
		tabId: context.tabId,
		paneId,
		isActive: rawPane.is_active === true,
		size,
		sourceFormat: "wezterm-object"
	};
};
const parseArrayResponse = (parsed) => {
	if (!Array.isArray(parsed)) return;
	const windowMap = /* @__PURE__ */ new Map();
	for (const entry of parsed) {
		const normalizedEntry = normalizeArrayEntry(entry);
		if (!normalizedEntry) continue;
		const windowRecord = getOrCreateWindowRecord(windowMap, normalizedEntry);
		const tabRecord = getOrCreateTabRecord(windowRecord, normalizedEntry.tabId);
		windowRecord.isActive ||= normalizedEntry.isActive;
		tabRecord.isActive ||= normalizedEntry.isActive;
		tabRecord.panes.push({
			paneId: normalizedEntry.paneId,
			isActive: normalizedEntry.isActive,
			...normalizedEntry.size !== void 0 ? { size: normalizedEntry.size } : {},
			rawPaneRecord: createArrayRawPaneRecord(normalizedEntry)
		});
	}
	return { windows: toImmutableWindows(windowMap) };
};
const parseObjectPane = (pane, context) => {
	if (typeof pane !== "object" || pane === null) return;
	const rawPane = pane;
	const paneIdRaw = toIdString(rawPane.pane_id);
	if (!isNonEmptyString(paneIdRaw)) return;
	const size = toPaneSize(rawPane.size);
	return {
		paneId: paneIdRaw,
		isActive: rawPane.is_active === true,
		...size !== void 0 ? { size } : {},
		rawPaneRecord: createObjectRawPaneRecord({
			rawPane,
			context,
			paneId: paneIdRaw,
			size
		})
	};
};
const parseObjectTab = (tab, context) => {
	if (typeof tab !== "object" || tab === null) return;
	const rawTab = tab;
	const tabIdRaw = toIdString(rawTab.tab_id);
	if (!isNonEmptyString(tabIdRaw)) return;
	const paneRecords = Array.isArray(rawTab.panes) ? rawTab.panes : [];
	const panes = [];
	for (const pane of paneRecords) {
		const mappedPane = parseObjectPane(pane, {
			windowId: context.windowId,
			tabId: tabIdRaw
		});
		if (mappedPane) panes.push(mappedPane);
	}
	return {
		tabId: tabIdRaw,
		isActive: rawTab.is_active === true,
		panes
	};
};
const parseObjectWindow = (window) => {
	if (typeof window !== "object" || window === null) return;
	const rawWindow = window;
	const windowIdRaw = toIdString(rawWindow.window_id);
	if (!isNonEmptyString(windowIdRaw)) return;
	const tabs = [];
	const rawTabs = Array.isArray(rawWindow.tabs) ? rawWindow.tabs : [];
	for (const tab of rawTabs) {
		const mappedTab = parseObjectTab(tab, { windowId: windowIdRaw });
		if (mappedTab) tabs.push(mappedTab);
	}
	return {
		windowId: windowIdRaw,
		isActive: rawWindow.is_active === true,
		workspace: toWorkspaceString(rawWindow.workspace),
		tabs
	};
};
const parseObjectResponse = (parsed) => {
	if (typeof parsed !== "object" || parsed === null) return;
	const candidate = parsed;
	const rawWindows = Array.isArray(candidate.windows) ? candidate.windows : [];
	const windows = [];
	for (const window of rawWindows) {
		const mappedWindow = parseObjectWindow(window);
		if (mappedWindow) windows.push(mappedWindow);
	}
	return { windows };
};
const parseWeztermListResult = (stdout) => {
	try {
		const parsed = JSON.parse(stdout);
		return parseArrayResponse(parsed) ?? parseObjectResponse(parsed);
	} catch {
		return;
	}
};

//#endregion
//#region src/backends/wezterm/cli.ts
const WEZTERM_BINARY = "wezterm";
const WEZTERM_MINIMUM_VERSION = "20220624-141144-bd1b7c5d";
const VERSION_REGEX = /(\d{8})-(\d{6})-([0-9a-fA-F]+)/i;
const verifyWeztermAvailability = async () => {
	let stdout;
	try {
		stdout = (await execa(WEZTERM_BINARY, ["--version"])).stdout;
	} catch (error) {
		const execaError = error;
		if (execaError.code === "ENOENT") throw createEnvironmentError("wezterm is not installed", ErrorCodes.BACKEND_NOT_FOUND, {
			backend: "wezterm",
			binary: WEZTERM_BINARY
		});
		throw createEnvironmentError("Failed to execute wezterm --version", ErrorCodes.WEZTERM_NOT_FOUND, {
			backend: "wezterm",
			binary: WEZTERM_BINARY,
			stderr: execaError.stderr
		});
	}
	const detectedVersion = extractVersion(stdout);
	if (detectedVersion === void 0) throw createEnvironmentError("Unable to determine wezterm version", ErrorCodes.UNSUPPORTED_WEZTERM_VERSION, {
		requiredVersion: WEZTERM_MINIMUM_VERSION,
		detectedVersion: stdout.trim()
	});
	if (!isVersionSupported(detectedVersion, WEZTERM_MINIMUM_VERSION)) throw createEnvironmentError("Unsupported wezterm version", ErrorCodes.UNSUPPORTED_WEZTERM_VERSION, {
		requiredVersion: WEZTERM_MINIMUM_VERSION,
		detectedVersion
	});
	return { version: detectedVersion };
};
const runWeztermCli = async (args, errorContext) => {
	try {
		return (await execa(WEZTERM_BINARY, ["cli", ...args])).stdout;
	} catch (error) {
		const execaError = error;
		throw createCoreError("execution", {
			code: ErrorCodes.TERMINAL_COMMAND_FAILED,
			message: errorContext.message,
			path: errorContext.path,
			details: {
				command: [
					WEZTERM_BINARY,
					"cli",
					...args
				],
				stderr: execaError.stderr,
				exitCode: execaError.exitCode,
				backend: "wezterm",
				...errorContext.details ?? {}
			}
		});
	}
};
const listWeztermWindows = async () => {
	const stdout = await runWeztermCli([
		"list",
		"--format",
		"json"
	], { message: "Failed to list wezterm panes" });
	const result = parseWeztermListResult(stdout);
	if (result === void 0) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "Invalid wezterm list output",
		details: { stdout }
	});
	return result;
};
const killWeztermPane = async (paneId) => {
	await runWeztermCli([
		"kill-pane",
		"--pane-id",
		paneId
	], {
		message: `Failed to kill wezterm pane ${paneId}`,
		path: paneId
	});
};
const extractVersion = (raw) => {
	const match = raw.match(VERSION_REGEX);
	if (!match) return;
	const date = match[1];
	const time = match[2];
	const commit = match[3];
	if (date === void 0 || time === void 0 || commit === void 0) return;
	return `${date}-${time}-${commit.toLowerCase()}`;
};
const isVersionSupported = (detected, minimum) => {
	const parse = (version) => {
		const match = version.match(VERSION_REGEX);
		if (!match) return;
		const date = match[1];
		const time = match[2];
		const commit = match[3];
		if (date === void 0 || time === void 0 || commit === void 0) return;
		const build = Number(`${date}${time}`);
		if (Number.isNaN(build)) return;
		return {
			build,
			commit: commit.toLowerCase()
		};
	};
	const detectedInfo = parse(detected);
	const minimumInfo = parse(minimum);
	if (detectedInfo === void 0 || minimumInfo === void 0) return false;
	if (detectedInfo.build > minimumInfo.build) return true;
	if (detectedInfo.build < minimumInfo.build) return false;
	return detectedInfo.commit >= minimumInfo.commit;
};

//#endregion
//#region src/backends/wezterm/dry-run.ts
const SINGLE_QUOTE = "'";
const SHELL_SINGLE_QUOTE_ESCAPE = `'"'"'`;
const buildSplitArguments = (params) => {
	const directionFlag = params.horizontal ? "--right" : "--bottom";
	if (params.splitSize.mode === "percent") return [
		"split-pane",
		directionFlag,
		"--percent",
		params.splitSize.percentage,
		"--pane-id",
		params.targetPaneId
	];
	if (params.splitSize.mode === "cells") return [
		"split-pane",
		directionFlag,
		"--cells",
		params.splitSize.cells,
		"--pane-id",
		params.targetPaneId
	];
	return [
		"split-pane",
		directionFlag,
		"--cells",
		params.splitSize.cellsPlaceholder,
		"--pane-id",
		params.targetPaneId
	];
};
const buildDryRunSteps = (emission, options = {}) => {
	const steps = [];
	const paneSizes = /* @__PURE__ */ new Map();
	if (options.initialPaneId !== void 0 && options.initialPaneSize !== void 0) paneSizes.set(options.initialPaneId, options.initialPaneSize);
	for (const step of emission.steps) {
		if (step.kind === "split") {
			const target = resolveRequiredStepTargetPaneId(step);
			const horizontal = resolveSplitOrientation(step) === "horizontal";
			const args = buildSplitArguments({
				targetPaneId: target,
				splitSize: resolveDryRunSplitSize({
					step,
					targetPaneId: target,
					paneSizes,
					orientation: horizontal ? "horizontal" : "vertical",
					detectedVersion: options.detectedVersion
				}),
				horizontal
			});
			const summary = step.splitSizing?.mode === "dynamic-cells" ? `${step.summary} [dynamic-cells]` : step.summary;
			steps.push({
				backend: "wezterm",
				summary,
				command: `wezterm cli ${args.join(" ")}`
			});
			continue;
		}
		if (step.kind === "focus") {
			const target = resolveRequiredStepTargetPaneId(step);
			steps.push({
				backend: "wezterm",
				summary: step.summary,
				command: `wezterm cli activate-pane --pane-id ${target}`
			});
			continue;
		}
		throw createCoreError("execution", {
			code: ErrorCodes.INVALID_PLAN,
			message: `Unsupported step kind in emission: ${String(step.kind)}`,
			path: step.id
		});
	}
	const prepared = prepareTerminalCommands({
		terminals: emission.terminals,
		focusPaneVirtualId: emission.summary.focusPaneId,
		resolveRealPaneId: (virtualPaneId) => virtualPaneId,
		onTemplateTokenError: ({ terminal, error }) => {
			throw createCoreError("execution", {
				code: ErrorCodes.TEMPLATE_TOKEN_ERROR,
				message: `Template token resolution failed for pane ${terminal.virtualPaneId}: ${error.message}`,
				path: terminal.virtualPaneId,
				details: {
					command: terminal.command,
					tokenType: error.tokenType,
					availablePanes: error.availablePanes
				}
			});
		}
	});
	const quoteForShellDisplay = (value) => {
		return `${SINGLE_QUOTE}${value.split(SINGLE_QUOTE).join(SHELL_SINGLE_QUOTE_ESCAPE)}${SINGLE_QUOTE}`;
	};
	for (const commandSet of prepared.commands) {
		const paneId = commandSet.terminal.virtualPaneId;
		if (typeof commandSet.cwdCommand === "string") steps.push({
			backend: "wezterm",
			summary: `set cwd for ${paneId}`,
			command: `wezterm cli send-text --pane-id ${paneId} --no-paste -- ${quoteForShellDisplay(commandSet.cwdCommand)}`
		});
		for (const envEntry of commandSet.envCommands) steps.push({
			backend: "wezterm",
			summary: `set env ${envEntry.key} for ${paneId}`,
			command: `wezterm cli send-text --pane-id ${paneId} --no-paste -- ${quoteForShellDisplay(envEntry.command)}`
		});
		if (commandSet.command !== void 0) steps.push({
			backend: "wezterm",
			summary: `run command for ${paneId}`,
			command: `wezterm cli send-text --pane-id ${paneId} --no-paste -- ${quoteForShellDisplay(commandSet.command.text)}`
		});
	}
	return steps;
};
const resolveDryRunSplitSize = ({ step, targetPaneId, paneSizes, orientation, detectedVersion }) => {
	if (step.kind !== "split") throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Dry-run split sizing requested for non-split step",
		path: step.id
	});
	if (step.splitSizing?.mode === "dynamic-cells") {
		const paneSize = paneSizes.get(targetPaneId);
		const paneCells = paneSize ? orientation === "horizontal" ? paneSize.cols : paneSize.rows : void 0;
		if (typeof paneCells !== "number") return {
			mode: "cells-placeholder",
			cellsPlaceholder: "<dynamic>"
		};
		try {
			const splitSize = resolveSplitSize(step, {
				paneCells,
				paneId: targetPaneId,
				detectedVersion,
				rawPaneRecord: {
					backend: "wezterm",
					paneId: targetPaneId,
					sourceFormat: "unknown",
					size: paneSize
				}
			});
			if (splitSize.mode === "cells" && typeof step.createdPaneId === "string" && step.createdPaneId.length > 0) updatePaneSizes({
				paneSizes,
				targetPaneId,
				createdPaneId: step.createdPaneId,
				orientation,
				targetCells: splitSize.targetCells,
				createdCells: splitSize.createdCells
			});
			if (splitSize.mode === "cells") return splitSize;
		} catch (error) {
			if (isCoreError(error) && error.code === ErrorCodes.SPLIT_SIZE_RESOLUTION_FAILED) return {
				mode: "cells-placeholder",
				cellsPlaceholder: "<dynamic>"
			};
			throw error;
		}
		return {
			mode: "cells-placeholder",
			cellsPlaceholder: "<dynamic>"
		};
	}
	return resolveSplitSize(step, {
		paneId: targetPaneId,
		detectedVersion
	});
};

//#endregion
//#region src/backends/wezterm/layout-resolution.ts
const PANE_REGISTRATION_RETRIES = 5;
const PANE_REGISTRATION_DELAY_MS = 100;
const resolveCurrentWindow = async (context) => {
	const preferredPaneId = typeof context.preferredPaneId === "string" && context.preferredPaneId.length > 0 ? context.preferredPaneId : void 0;
	const preferredWindowId = preferredPaneId !== void 0 ? findWindowContainingPane(context.list, preferredPaneId) : void 0;
	const activeWindow = (preferredWindowId !== void 0 ? context.list.windows.find((window) => window.windowId === preferredWindowId) : void 0) ?? context.list.windows.find((window) => window.isActive) ?? context.list.windows[0];
	if (!activeWindow) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "No active wezterm window detected",
		details: { hint: "Launch wezterm and ensure a window is focused, or run with --new-window." }
	});
	const activeTab = (preferredPaneId !== void 0 ? activeWindow.tabs.find((tab) => tab.panes.some((pane) => pane.paneId === preferredPaneId)) : void 0) ?? activeWindow.tabs.find((tab) => tab.isActive) ?? activeWindow.tabs[0];
	if (!activeTab) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "No active wezterm tab detected",
		path: activeWindow.windowId,
		details: { hint: "Ensure a wezterm tab is focused before using --current-window." }
	});
	const activePane = (preferredPaneId !== void 0 ? activeTab.panes.find((pane) => pane.paneId === preferredPaneId) : void 0) ?? activeTab.panes.find((pane) => pane.isActive) ?? activeTab.panes[0];
	if (!activePane) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "No active wezterm pane detected",
		path: activeTab.tabId,
		details: { hint: "Ensure a wezterm pane is active before using --current-window." }
	});
	const panesToClose = activeTab.panes.filter((pane) => pane.paneId !== activePane.paneId).map((pane) => pane.paneId);
	if (panesToClose.length > 0) {
		let confirmed = true;
		if (context.prompt) confirmed = await context.prompt({
			panesToClose,
			dryRun: context.dryRun
		});
		if (confirmed !== true) throw createCoreError("execution", {
			code: ErrorCodes.USER_CANCELLED,
			message: "Aborted layout application for current wezterm window",
			path: activePane.paneId,
			details: { panes: panesToClose }
		});
		for (const paneId of panesToClose) {
			context.logCommand([
				"kill-pane",
				"--pane-id",
				paneId
			]);
			await killWeztermPane(paneId);
		}
	}
	return {
		paneId: activePane.paneId,
		windowId: activeWindow.windowId,
		panesToClose
	};
};
const findActiveWindow = (list) => {
	return list.windows.find((window) => window.isActive) ?? list.windows[0];
};
const findWindowContainingPane = (list, paneId) => {
	for (const window of list.windows) for (const tab of window.tabs) for (const pane of tab.panes) if (pane.paneId === paneId) return window.windowId;
};
const findWorkspaceForPane = (list, paneId) => {
	for (const window of list.windows) for (const tab of window.tabs) for (const pane of tab.panes) if (pane.paneId === paneId) return window.workspace;
};
const filterWindowsByWorkspace = (list, workspace) => {
	if (workspace === void 0 || workspace.length === 0) return list;
	const scoped = list.windows.filter((window) => window.workspace === workspace);
	if (scoped.length === 0) return list;
	return { windows: scoped };
};
const collectPaneIdsForWindow = (list, windowId) => {
	const targetWindow = list.windows.find((window) => window.windowId === windowId);
	if (!targetWindow) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: `Wezterm window ${windowId} not found`,
		details: { windowId }
	});
	const paneIds = targetWindow.tabs.flatMap((tab) => tab.panes.map((pane) => pane.paneId));
	return new Set(paneIds);
};
const waitForPaneRegistration = async ({ paneId, listWindows, windowHint }) => {
	for (let attempt = 0; attempt < PANE_REGISTRATION_RETRIES; attempt += 1) {
		const snapshot = await listWindows();
		if (typeof windowHint === "string") try {
			if (collectPaneIdsForWindow(snapshot, windowHint).has(paneId)) return windowHint;
		} catch {}
		const located = findWindowContainingPane(snapshot, paneId);
		if (typeof located === "string" && located.length > 0) return located;
		if (attempt < PANE_REGISTRATION_RETRIES - 1) await waitForDelay(PANE_REGISTRATION_DELAY_MS);
	}
	throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "Unable to locate spawned wezterm window",
		details: {
			paneId,
			hint: "Verify that wezterm is running and the CLI client can connect."
		}
	});
};
const extractSpawnPaneId = (output) => {
	const trimmed = output.trim();
	if (trimmed.length === 0) return "";
	const tokens = (trimmed.split("\n").pop() ?? "").split(/\s+/).filter((segment) => segment.length > 0);
	if (tokens.length === 0) return "";
	const [paneId] = tokens;
	if (typeof paneId !== "string") return "";
	return paneId.trim();
};
const resolveInitialPane = async ({ windowMode, prompt, dryRun, listWindows, runCommand, logCommand, initialCwd, workspaceHint, initialList, preferredPaneId }) => {
	const preferredPaneIdValue = typeof preferredPaneId === "string" && preferredPaneId.length > 0 ? preferredPaneId : void 0;
	if (windowMode === "current-window") return resolveCurrentWindow({
		list: filterWindowsByWorkspace(initialList ?? await listWindows(), workspaceHint),
		prompt,
		dryRun,
		logCommand,
		preferredPaneId: preferredPaneIdValue
	});
	const scopedExisting = filterWindowsByWorkspace(initialList ?? await listWindows(), workspaceHint);
	const scopedPreferredWindowId = preferredPaneIdValue !== void 0 ? findWindowContainingPane(scopedExisting, preferredPaneIdValue) : void 0;
	const activeWindow = (scopedPreferredWindowId !== void 0 ? scopedExisting.windows.find((window) => window.windowId === scopedPreferredWindowId) : void 0) ?? findActiveWindow(scopedExisting);
	if (activeWindow) {
		const args = [
			"spawn",
			"--window-id",
			activeWindow.windowId
		];
		if (typeof initialCwd === "string" && initialCwd.length > 0) args.push("--cwd", initialCwd);
		const spawnOutput = await runCommand(args, { message: "Failed to spawn wezterm tab" });
		const paneId = extractSpawnPaneId(spawnOutput);
		if (paneId.length === 0) throw createCoreError("execution", {
			code: ErrorCodes.TERMINAL_COMMAND_FAILED,
			message: "wezterm spawn did not return a pane id",
			details: { stdout: spawnOutput }
		});
		return {
			paneId,
			windowId: await waitForPaneRegistration({
				paneId,
				listWindows,
				windowHint: activeWindow.windowId
			})
		};
	}
	const args = ["spawn", "--new-window"];
	if (typeof initialCwd === "string" && initialCwd.length > 0) args.push("--cwd", initialCwd);
	if (typeof workspaceHint === "string" && workspaceHint.length > 0) args.push("--workspace", workspaceHint);
	const spawnOutput = await runCommand(args, { message: "Failed to spawn wezterm window" });
	const paneId = extractSpawnPaneId(spawnOutput);
	if (paneId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "wezterm spawn did not return a pane id",
		details: { stdout: spawnOutput }
	});
	return {
		paneId,
		windowId: await waitForPaneRegistration({
			paneId,
			listWindows
		})
	};
};

//#endregion
//#region src/backends/wezterm/pane-map.ts
const registerPaneWithAncestors = (map, virtualId, realId) => {
	map.set(virtualId, realId);
	let ancestor = virtualId;
	while (ancestor.includes(".")) {
		ancestor = ancestor.slice(0, ancestor.lastIndexOf("."));
		if (!map.has(ancestor)) map.set(ancestor, realId);
		else break;
	}
};
const resolveRealPaneId = (paneMap, virtualId, context) => {
	const resolved = resolvePaneMapping(paneMap, virtualId);
	if (typeof resolved === "string" && resolved.length > 0) return resolved;
	throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PANE,
		message: `Unknown wezterm pane mapping for ${virtualId}`,
		path: context.stepId
	});
};

//#endregion
//#region src/backends/wezterm/step-execution.ts
const findNewPaneId = (before, after) => {
	for (const paneId of after) if (!before.has(paneId)) return paneId;
};
const appendCarriageReturn = (value) => {
	return value.endsWith("\r") ? value : `${value}\r`;
};
const sendTextToPane = async ({ paneId, text, runCommand, context }) => {
	await runCommand([
		"send-text",
		"--pane-id",
		paneId,
		"--no-paste",
		"--",
		appendCarriageReturn(text)
	], context);
};
const applyFocusStep = async ({ step, paneMap, runCommand }) => {
	const targetVirtualId = step.targetPaneId;
	if (typeof targetVirtualId !== "string" || targetVirtualId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PANE,
		message: "Focus step missing target pane metadata",
		path: step.id
	});
	await runCommand([
		"activate-pane",
		"--pane-id",
		resolveRealPaneId(paneMap, targetVirtualId, { stepId: step.id })
	], {
		message: `Failed to execute focus step ${step.id}`,
		path: step.id
	});
};
const applySplitStep = async ({ step, paneMap, windowId, runCommand, listWindows, logPaneMapping, detectedVersion }) => {
	const targetVirtualId = step.targetPaneId;
	if (typeof targetVirtualId !== "string" || targetVirtualId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PANE,
		message: "Split step missing target pane metadata",
		path: step.id
	});
	const targetRealId = resolveRealPaneId(paneMap, targetVirtualId, { stepId: step.id });
	const beforeList = await listWindows();
	const beforePaneIds = collectPaneIdsForWindow(beforeList, windowId);
	const orientation = resolveSplitOrientation(step);
	const targetPane = findPaneById(beforeList, targetRealId);
	await runCommand(buildSplitArguments({
		targetPaneId: targetRealId,
		splitSize: resolveSplitSize(step, {
			paneCells: resolvePaneCellsForOrientation(targetPane?.size, orientation),
			paneId: targetRealId,
			requiredVersion: WEZTERM_MINIMUM_VERSION,
			detectedVersion,
			rawPaneRecord: targetPane?.rawPaneRecord
		}),
		horizontal: orientation === "horizontal"
	}), {
		message: `Failed to execute split step ${step.id}`,
		path: step.id
	});
	const newPaneId = findNewPaneId(beforePaneIds, collectPaneIdsForWindow(await listWindows(), windowId));
	if (typeof newPaneId !== "string" || newPaneId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.TERMINAL_COMMAND_FAILED,
		message: "Unable to determine newly created wezterm pane",
		path: step.id
	});
	if (typeof step.createdPaneId === "string" && step.createdPaneId.length > 0) {
		registerPaneWithAncestors(paneMap, step.createdPaneId, newPaneId);
		logPaneMapping(step.createdPaneId, newPaneId);
	}
};
const findPaneById = (list, paneId) => {
	for (const window of list.windows) for (const tab of window.tabs) for (const pane of tab.panes) if (pane.paneId === paneId) return {
		size: pane.size,
		rawPaneRecord: pane.rawPaneRecord
	};
};
const resolvePaneCellsForOrientation = (size, orientation) => {
	if (orientation === "horizontal") return size?.cols;
	return size?.rows;
};
const applyTerminalCommands = async ({ terminals, paneMap, runCommand, focusPaneVirtualId }) => {
	if (!paneMap.has(focusPaneVirtualId)) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PANE,
		message: `Unknown focus pane: ${focusPaneVirtualId}`,
		path: focusPaneVirtualId
	});
	const prepared = prepareTerminalCommands({
		terminals,
		focusPaneVirtualId,
		resolveRealPaneId: (virtualPaneId) => resolveRealPaneId(paneMap, virtualPaneId, { stepId: virtualPaneId }),
		onTemplateTokenError: ({ terminal, error }) => {
			throw createCoreError("execution", {
				code: ErrorCodes.TEMPLATE_TOKEN_ERROR,
				message: `Template token resolution failed for pane ${terminal.virtualPaneId}: ${error.message}`,
				path: terminal.virtualPaneId,
				details: {
					command: terminal.command,
					tokenType: error.tokenType,
					availablePanes: error.availablePanes
				}
			});
		}
	});
	for (const commandSet of prepared.commands) {
		const { terminal, realPaneId } = commandSet;
		if (typeof commandSet.cwdCommand === "string") await sendTextToPane({
			paneId: realPaneId,
			text: commandSet.cwdCommand,
			runCommand,
			context: {
				message: `Failed to change directory for pane ${terminal.virtualPaneId}`,
				path: terminal.virtualPaneId,
				details: { cwd: terminal.cwd }
			}
		});
		for (const envEntry of commandSet.envCommands) await sendTextToPane({
			paneId: realPaneId,
			text: envEntry.command,
			runCommand,
			context: {
				message: `Failed to set environment variable ${envEntry.key}`,
				path: terminal.virtualPaneId
			}
		});
		if (commandSet.command !== void 0) {
			if (commandSet.command.delayMs > 0) await waitForDelay(commandSet.command.delayMs);
			await sendTextToPane({
				paneId: realPaneId,
				text: commandSet.command.text,
				runCommand,
				context: {
					message: `Failed to execute command for pane ${terminal.virtualPaneId}`,
					path: terminal.virtualPaneId,
					details: { command: terminal.command }
				}
			});
		}
	}
};

//#endregion
//#region src/backends/wezterm/backend.ts
const ensureVirtualPaneId = (emission) => {
	const { initialPaneId } = emission.summary;
	if (typeof initialPaneId !== "string" || initialPaneId.length === 0) throw createCoreError("execution", {
		code: ErrorCodes.INVALID_PLAN,
		message: "Plan emission is missing initial pane metadata",
		path: "plan.initialPaneId"
	});
	return initialPaneId;
};
const createWeztermBackend = (context) => {
	let detectedVersion;
	const formatCommand = (args) => {
		return `wezterm cli ${args.join(" ")}`;
	};
	const logCommand = (args) => {
		const message = `[wezterm] ${formatCommand(args)}`;
		if (context.verbose) context.logger.info(message);
		else context.logger.debug(message);
	};
	const logPaneMapping = (virtualId, realId) => {
		const message = `[wezterm] pane ${virtualId} -> ${realId}`;
		if (context.verbose) context.logger.info(message);
		else context.logger.debug(message);
	};
	const runCommand = async (args, errorContext) => {
		const commandArgs = [...args];
		logCommand(commandArgs);
		return runWeztermCli(commandArgs, errorContext);
	};
	const listWindows = async () => {
		logCommand([
			"list",
			"--format",
			"json"
		]);
		return listWeztermWindows();
	};
	const verifyEnvironment = async () => {
		if (context.dryRun) return;
		detectedVersion = (await verifyWeztermAvailability()).version;
	};
	const applyPlan = async ({ emission, windowMode }) => {
		const initialVirtualPaneId = ensureVirtualPaneId(emission);
		const paneMap = /* @__PURE__ */ new Map();
		const initialTerminal = emission.terminals.find((terminal) => terminal.virtualPaneId === initialVirtualPaneId);
		const initialCwd = typeof initialTerminal?.cwd === "string" && initialTerminal.cwd.length > 0 ? initialTerminal.cwd : context.cwd;
		let cachedInitialList;
		let workspaceHint;
		if (typeof context.paneId === "string" && context.paneId.length > 0) try {
			cachedInitialList = await listWindows();
			workspaceHint = findWorkspaceForPane(cachedInitialList, context.paneId);
		} catch {
			cachedInitialList = void 0;
			workspaceHint = void 0;
		}
		const { paneId: initialPaneId, windowId } = await resolveInitialPane({
			windowMode,
			prompt: context.prompt,
			dryRun: context.dryRun,
			listWindows,
			runCommand,
			logCommand,
			initialCwd,
			workspaceHint,
			initialList: cachedInitialList,
			preferredPaneId: context.paneId
		});
		registerPaneWithAncestors(paneMap, initialVirtualPaneId, initialPaneId);
		logPaneMapping(initialVirtualPaneId, initialPaneId);
		let executedSteps = 0;
		for (const step of emission.steps) if (step.kind === "split") {
			await applySplitStep({
				step,
				paneMap,
				windowId,
				runCommand,
				listWindows,
				logPaneMapping,
				detectedVersion
			});
			executedSteps += 1;
		} else if (step.kind === "focus") {
			await applyFocusStep({
				step,
				paneMap,
				runCommand
			});
			executedSteps += 1;
		} else throw createCoreError("execution", {
			code: ErrorCodes.INVALID_PLAN,
			message: `Unsupported step kind in emission: ${String(step.kind)}`,
			path: step.id
		});
		await applyTerminalCommands({
			terminals: emission.terminals,
			paneMap,
			runCommand,
			focusPaneVirtualId: emission.summary.focusPaneId
		});
		const focusVirtual = emission.summary.focusPaneId;
		const focusPaneId = typeof focusVirtual === "string" ? paneMap.get(focusVirtual) : void 0;
		return {
			executedSteps,
			focusPaneId,
			paneNameToRealId: buildNameToRealIdMap(emission.terminals, paneMap),
			windowId
		};
	};
	return {
		verifyEnvironment,
		applyPlan,
		getDryRunSteps: (emission) => {
			const initialPaneSize = resolveInitialWeztermPaneSize({
				paneId: typeof context.paneId === "string" && context.paneId.length > 0 ? context.paneId : process.env.WEZTERM_PANE,
				logger: context.logger
			});
			return buildDryRunSteps(emission, {
				initialPaneId: emission.summary.initialPaneId,
				initialPaneSize,
				detectedVersion
			});
		}
	};
};
const resolveInitialWeztermPaneSize = ({ paneId, logger }) => {
	if (typeof paneId !== "string" || paneId.length === 0) return;
	try {
		const parsed = parseWeztermListResult(execFileSync("wezterm", [
			"cli",
			"list",
			"--format",
			"json"
		], {
			encoding: "utf8",
			stdio: [
				"ignore",
				"pipe",
				"ignore"
			]
		}));
		if (parsed === void 0) {
			logger.debug(`[wezterm] Failed to parse pane list while resolving initial pane size for pane ${paneId}`);
			return;
		}
		for (const window of parsed.windows) for (const tab of window.tabs) for (const pane of tab.panes) if (pane.paneId === paneId) {
			if (typeof pane.size?.cols === "number" && typeof pane.size?.rows === "number") return {
				cols: pane.size.cols,
				rows: pane.size.rows
			};
			return;
		}
		return;
	} catch (error) {
		const reason = error instanceof Error ? error.message : String(error);
		logger.debug(`[wezterm] Failed to resolve initial pane size for pane ${paneId}: ${reason}`);
		return;
	}
};

//#endregion
//#region src/executor/backend-factory.ts
function createTerminalBackend(kind, context) {
	if (kind === "tmux") {
		if (!("executor" in context)) throw new Error("tmux backend requires executor context");
		return createTmuxBackend(context);
	}
	if (kind === "wezterm") return createWeztermBackend(context);
	throw new Error(`Unsupported backend "${kind}"`);
}

//#endregion
//#region src/executor/backend-resolver.ts
const KNOWN_BACKENDS = ["tmux", "wezterm"];
const resolveTerminalBackendKind = ({ cliFlag, presetBackend, env }) => {
	const selectedBackend = cliFlag ?? presetBackend;
	if (selectedBackend !== void 0) {
		if (!KNOWN_BACKENDS.includes(selectedBackend)) throw new Error(`Unknown backend "${selectedBackend}"`);
		return selectedBackend;
	}
	if (typeof env.TMUX === "string" && env.TMUX.trim().length > 0) return "tmux";
	return "tmux";
};

//#endregion
//#region src/cli/after-apply-hook.ts
const AFTER_APPLY_HOOK_TIMEOUT_MS = 3e4;
const FOCUS_PANE_TOKEN_PATTERN = /\{\{(?:this_pane|focus_pane)\}\}/;
const WINDOW_ID_TOKEN_PATTERN = /\{\{window_id\}\}/;
/**
* Runs the `hooks.afterApply` preset command once, after a preset has been applied
* successfully. Failures (token resolution or command execution) are logged as
* warnings and never rejected/thrown, so they cannot affect the CLI's exit code -
* the preset apply itself has already succeeded by the time this runs.
*/
const runAfterApplyHook = async ({ hookCommand, context, logger, runHostCommand = createDefaultRunHostCommand() }) => {
	if (typeof hookCommand !== "string" || hookCommand.length === 0) return;
	if (context.focusPaneId === void 0 && FOCUS_PANE_TOKEN_PATTERN.test(hookCommand)) {
		logger.warn("hooks.afterApply skipped: failed to resolve template tokens ({{this_pane}}/{{focus_pane}} require a focus pane id, but none was available)");
		return;
	}
	if (context.windowId === void 0 && WINDOW_ID_TOKEN_PATTERN.test(hookCommand)) {
		logger.warn("hooks.afterApply skipped: failed to resolve template tokens ({{window_id}} requires a window id, but none was available)");
		return;
	}
	let resolvedCommand;
	try {
		resolvedCommand = replaceTemplateTokens({
			command: hookCommand,
			currentPaneRealId: context.focusPaneId ?? "",
			focusPaneRealId: context.focusPaneId ?? "",
			nameToRealIdMap: context.paneNameToRealId ?? /* @__PURE__ */ new Map(),
			windowId: context.windowId
		});
	} catch (error) {
		const reason = error instanceof TemplateTokenError ? error.message : String(error);
		logger.warn(`hooks.afterApply skipped: failed to resolve template tokens (${reason})`);
		return;
	}
	logger.info(`Executing: ${resolvedCommand}`);
	try {
		await runHostCommand(resolvedCommand, { cwd: context.cwd });
	} catch (error) {
		const reason = error instanceof Error ? error.message : String(error);
		logger.warn(`hooks.afterApply failed: ${reason}`);
	}
};
/**
* Executes the resolved hook command through the host shell (equivalent to
* `sh -c <command>`), rather than splitting it into argv the way tmux commands
* are executed. hooks.afterApply commands are user-authored, free-form shell
* text (e.g. "vde-tmux-sidebar open {{pane_id:sidebar}} | logger") that may rely
* on pipes, redirection, or shell expansion, so argv-style execution would break
* common use cases.
*/
const createDefaultRunHostCommand = () => {
	return async (command, { cwd }) => {
		await execa(command, {
			shell: true,
			cwd,
			timeout: AFTER_APPLY_HOOK_TIMEOUT_MS
		});
	};
};

//#endregion
//#region src/cli/command-helpers.ts
const renderDryRun = (steps, output = (message) => console.log(message)) => {
	output(chalk.bold("\nPlanned terminal steps (dry-run)"));
	steps.forEach((step, index) => {
		output(` ${index + 1}. [${step.backend}] ${step.summary}: ${step.command}`);
	});
};
const renderDryRunHook = (afterApply, output = (message) => console.log(message)) => {
	if (typeof afterApply !== "string" || afterApply.length === 0) return;
	output(chalk.bold("\nPlanned hooks (dry-run)"));
	output(` 1. [afterApply] ${afterApply}`);
};
const buildPresetSource = (presetName) => {
	return typeof presetName === "string" && presetName.length > 0 ? `preset://${presetName}` : "preset://default";
};
const determineCliWindowMode = (options) => {
	if (options.currentWindow === true && options.newWindow === true) throw new Error("Cannot use --current-window and --new-window at the same time");
	if (options.currentWindow === true) return "current-window";
	if (options.newWindow === true) return "new-window";
};

//#endregion
//#region src/cli/user-prompt.ts
const createPaneKillPrompter = (logger) => {
	return async ({ panesToClose, dryRun }) => {
		if (panesToClose.length === 0) return true;
		const paneList = panesToClose.join(", ");
		if (dryRun) {
			logger.warn(`[DRY RUN] Would close panes: ${paneList}`);
			return true;
		}
		logger.warn(`This operation will close the following panes: ${paneList}`);
		if (stdin.isTTY !== true || stdout.isTTY !== true) {
			logger.error("Cannot prompt for confirmation because the terminal is not interactive");
			return false;
		}
		const rl = createInterface({
			input: stdin,
			output: stdout
		});
		try {
			const normalized = (await rl.question("Continue? [y/N]: ")).trim().toLowerCase();
			return normalized === "y" || normalized === "yes";
		} finally {
			rl.close();
		}
	};
};

//#endregion
//#region src/cli/window-mode.ts
const resolveWindowMode = ({ cli, preset, defaults }) => {
	if (cli !== void 0) return {
		mode: cli,
		source: "cli"
	};
	if (preset !== void 0) return {
		mode: preset,
		source: "preset"
	};
	if (defaults !== void 0) return {
		mode: defaults,
		source: "defaults"
	};
	return {
		mode: "new-window",
		source: "fallback"
	};
};

//#endregion
//#region src/cli/preset-execution.ts
const executePreset = async ({ presetName, options, skipLoadConfig = false, presetManager, createCommandExecutor, core, logger, handleError, handlePipelineFailure, output = (line) => console.log(line), cwd = process.cwd(), env = process.env }) => {
	try {
		if (skipLoadConfig !== true) await presetManager.loadConfig();
		const preset = typeof presetName === "string" && presetName.length > 0 ? presetManager.getPreset(presetName) : presetManager.getDefaultPreset();
		const windowModeResolution = resolveWindowModeForPreset({
			presetManager,
			options,
			presetWindowMode: preset.windowMode
		});
		const windowMode = windowModeResolution.mode;
		logger.info(`Window mode: ${windowMode} (source: ${windowModeResolution.source})`);
		const confirmPaneClosure = createPaneKillPrompter(logger);
		const executor = createCommandExecutor({
			verbose: options.verbose,
			dryRun: options.dryRun
		});
		const backendKind = resolveTerminalBackendKind({
			cliFlag: options.backend,
			presetBackend: preset.backend,
			env
		});
		logger.info(`Terminal backend: ${backendKind}`);
		const backendContextBase = {
			logger,
			dryRun: options.dryRun,
			verbose: options.verbose,
			prompt: confirmPaneClosure,
			cwd,
			paneId: env.WEZTERM_PANE
		};
		const backend = backendKind === "tmux" ? createTerminalBackend("tmux", {
			...backendContextBase,
			executor
		}) : createTerminalBackend("wezterm", backendContextBase);
		await backend.verifyEnvironment();
		if (options.dryRun === true) output("[DRY RUN] No actual commands will be executed");
		let emission;
		let compiledPreset;
		try {
			const built = buildPlanEmission({
				core,
				preset,
				presetName
			});
			emission = built.emission;
			compiledPreset = built.compiledPreset;
		} catch (error) {
			return handlePipelineFailure(error);
		}
		if (options.dryRun === true) {
			renderDryRun(backend.getDryRunSteps(emission), output);
			renderDryRunHook(compiledPreset.hooks?.afterApply, output);
		} else try {
			const executionResult = await backend.applyPlan({
				emission,
				windowMode,
				windowName: resolveWindowName({
					presetName,
					presetDisplayName: preset.name
				})
			});
			logger.info(`Executed ${executionResult.executedSteps} ${backendKind} steps`);
			const afterApplyCommand = compiledPreset.hooks?.afterApply;
			if (afterApplyCommand !== void 0) await runAfterApplyHook({
				hookCommand: afterApplyCommand,
				context: {
					cwd,
					focusPaneId: executionResult.focusPaneId,
					paneNameToRealId: executionResult.paneNameToRealId,
					windowId: executionResult.windowId
				},
				logger
			});
		} catch (error) {
			return handlePipelineFailure(error);
		}
		logger.success(`Applied preset "${preset.name}"`);
		return 0;
	} catch (error) {
		return handleError(error);
	}
};
const buildPlanEmission = ({ core, preset, presetName }) => {
	const compileResult = core.compilePresetFromValue({
		value: preset,
		source: buildPresetSource(presetName)
	});
	const planResult = core.createLayoutPlan({ preset: compileResult.preset });
	return {
		emission: core.emitPlan({ plan: planResult.plan }),
		compiledPreset: compileResult.preset
	};
};
const resolveWindowName = ({ presetName, presetDisplayName }) => {
	return presetDisplayName ?? presetName ?? "vde-layout";
};
const resolveWindowModeForPreset = ({ presetManager, options, presetWindowMode }) => {
	return resolveWindowMode({
		cli: determineCliWindowMode({
			currentWindow: options.currentWindow,
			newWindow: options.newWindow
		}),
		preset: presetWindowMode,
		defaults: presetManager.getDefaults()?.windowMode
	});
};

//#endregion
//#region src/cli/select-args.ts
const selectUiModes = SELECT_UI_MODES;
const selectSurfaceModes = SELECT_SURFACE_MODES;
const selectUiModeSet = new Set(selectUiModes);
const selectSurfaceModeSet = new Set(selectSurfaceModes);
const isSelectUiMode = (value) => {
	return selectUiModeSet.has(value);
};
const isSelectSurfaceMode = (value) => {
	return selectSurfaceModeSet.has(value);
};
const normalizeSelectArgs = (args) => {
	const normalized = [];
	for (let index = 0; index < args.length; index += 1) {
		const token = args[index];
		if (typeof token !== "string") continue;
		if (token === "--") {
			normalized.push(...args.slice(index));
			break;
		}
		if (typeof token === "string" && token.startsWith("--select=")) {
			const mode = token.slice(9);
			normalized.push("--select");
			if (mode.length > 0) normalized.push("--select-ui", mode);
			continue;
		}
		if (token === "--select") {
			const nextToken = args[index + 1];
			if (typeof nextToken === "string" && isSelectUiMode(nextToken)) {
				normalized.push("--select", "--select-ui", nextToken);
				index += 1;
				continue;
			}
		}
		normalized.push(token);
	}
	return normalized;
};
const resolveSelectUiMode = (uiValue) => {
	if (uiValue === void 0) return "auto";
	if (isSelectUiMode(uiValue)) return uiValue;
	throw new Error(`Invalid value for --select-ui: "${uiValue}". Expected one of: ${selectUiModes.join(", ")}`);
};
const resolveSelectSurfaceMode = (surfaceValue) => {
	if (surfaceValue === void 0) return "auto";
	if (isSelectSurfaceMode(surfaceValue)) return surfaceValue;
	throw new Error(`Invalid value for --select-surface: "${surfaceValue}". Expected one of: ${selectSurfaceModes.join(", ")}`);
};

//#endregion
//#region src/cli/preset-selector.ts
const FZF_BINARY = "fzf";
const FZF_CHECK_TIMEOUT_MS = 5e3;
const MAX_PREVIEW_BASE64_LENGTH = 64 * 1024;
const RESERVED_FZF_ARGS = new Set([
	"delimiter",
	"with-nth",
	"ansi",
	"preview",
	"preview-window",
	"tmux"
]);
const selectorChalk = new Chalk({ level: 1 });
const sanitizeTsvCell = (value) => {
	return (value ?? "").replace(/[\t\r\n]+/g, " ");
};
const padDisplayCell = (value, width) => {
	const paddingLength = Math.max(0, width - stringWidth(value));
	return `${value}${" ".repeat(paddingLength)}`;
};
const buildPresetPreviewYaml = ({ presetKey, preset }) => {
	return YAML.stringify({ presets: { [presetKey]: preset } });
};
const defaultCheckFzfAvailability = async () => {
	try {
		await execa(FZF_BINARY, ["--version"], { timeout: FZF_CHECK_TIMEOUT_MS });
		return true;
	} catch (error) {
		const execaError = error;
		if (execaError.code === "ENOENT" || execaError.code === "ETIMEDOUT" || execaError.code === "ERR_EXECA_TIMEOUT" || execaError.timedOut === true) return false;
		throw error;
	}
};
const defaultRunFzf = async ({ args, input, cwd, env }) => {
	return { stdout: (await execa(FZF_BINARY, args, {
		input,
		cwd,
		env,
		stderr: "inherit"
	})).stdout };
};
const ensureFzfAvailable = async (checkFzfAvailability) => {
	if (await checkFzfAvailability()) return;
	throw createEnvironmentError("fzf is required for preset selection UI", ErrorCodes.BACKEND_NOT_FOUND, {
		backend: "fzf",
		binary: FZF_BINARY
	});
};
const isTmuxSession = (env) => {
	return typeof env.TMUX === "string" && env.TMUX.length > 0;
};
const resolveSurfaceMode = ({ surfaceMode, env }) => {
	if (surfaceMode === "auto") return isTmuxSession(env) ? "tmux-popup" : "inline";
	return surfaceMode;
};
const validateExtraFzfArgs = (fzfExtraArgs) => {
	for (const arg of fzfExtraArgs) {
		if (typeof arg !== "string" || arg.length === 0) throw new Error("Empty value is not allowed for --fzf-arg");
		if (!arg.startsWith("--")) continue;
		const withoutPrefix = arg.slice(2);
		if (withoutPrefix.length === 0) continue;
		const optionName = withoutPrefix.split("=")[0];
		if (optionName !== void 0 && RESERVED_FZF_ARGS.has(optionName)) throw new Error(`--fzf-arg cannot override reserved fzf option: --${optionName}`);
	}
};
const buildFzfArgs = ({ surfaceMode, tmuxPopupOptions, fzfExtraArgs, env }) => {
	validateExtraFzfArgs(fzfExtraArgs);
	const resolvedSurfaceMode = resolveSurfaceMode({
		surfaceMode,
		env
	});
	if (resolvedSurfaceMode === "tmux-popup" && isTmuxSession(env) !== true) throw new Error("tmux popup selector surface requires running inside tmux");
	return [
		"--delimiter=\\t",
		"--ansi",
		"--with-nth=2",
		"--prompt=preset> ",
		"--layout=reverse",
		"--height=80%",
		"--border",
		"--preview=node -e 'process.stdout.write(Buffer.from(process.argv[1], \"base64\").toString(\"utf8\"))' {3}",
		"--preview-window=right,60%,border-left,wrap",
		...resolvedSurfaceMode === "tmux-popup" ? [tmuxPopupOptions !== void 0 ? `--tmux=${tmuxPopupOptions}` : "--tmux"] : [],
		...fzfExtraArgs
	];
};
const toPresetRows = ({ presetInfos, presetManager }) => {
	const rows = presetInfos.map((presetInfo) => {
		const key = sanitizeTsvCell(presetInfo.key);
		const name = sanitizeTsvCell(presetInfo.name);
		const description = sanitizeTsvCell(presetInfo.description);
		const preset = presetManager.getPreset(presetInfo.key);
		const previewYaml = buildPresetPreviewYaml({
			presetKey: presetInfo.key,
			preset
		});
		const previewBase64 = Buffer.from(previewYaml, "utf8").toString("base64");
		if (previewBase64.length > MAX_PREVIEW_BASE64_LENGTH) throw new Error(`Preset preview is too large for fzf inline preview payload: "${presetInfo.key}" (${previewBase64.length} bytes)`);
		return {
			key,
			name,
			description,
			previewBase64
		};
	});
	const keyColumnWidth = rows.reduce((maxWidth, row) => Math.max(maxWidth, stringWidth(row.key)), 0);
	const nameColumnWidth = rows.reduce((maxWidth, row) => Math.max(maxWidth, stringWidth(row.name)), 0);
	return rows.map((row) => {
		const key = padDisplayCell(row.key, keyColumnWidth);
		const name = padDisplayCell(row.name, nameColumnWidth);
		const description = row.description.length > 0 ? row.description : selectorChalk.gray("(no description)");
		const display = `${selectorChalk.cyan(key)}  ${selectorChalk.bold(name)}  ${selectorChalk.dim(description)}`;
		return {
			key: row.key,
			name: row.name,
			description: row.description,
			display,
			previewBase64: row.previewBase64
		};
	});
};
const buildFzfInput = (rows) => {
	return rows.map((row, index) => {
		return [
			String(index),
			row.display,
			row.previewBase64
		].join("	");
	}).join("\n");
};
const parseSelectedPresetName = ({ selectedLine, rows }) => {
	const trimmed = selectedLine.trim();
	if (trimmed.length === 0) return null;
	const idCell = trimmed.split("	")[0];
	const id = Number(idCell);
	if (!Number.isInteger(id) || id < 0 || id >= rows.length) throw new Error("Invalid selection returned from fzf");
	return rows[id]?.key ?? null;
};
const runFzfSelector = async ({ rows, fzfArgs, runFzf, cwd, env }) => {
	try {
		const presetName = parseSelectedPresetName({
			selectedLine: (await runFzf({
				input: buildFzfInput(rows),
				args: [...fzfArgs],
				cwd,
				env
			})).stdout,
			rows
		});
		if (presetName === null) return { status: "cancelled" };
		return {
			status: "selected",
			presetName
		};
	} catch (error) {
		if (error.exitCode === 130) return { status: "cancelled" };
		throw error;
	}
};
const selectPreset = async ({ uiMode, surfaceMode, tmuxPopupOptions, fzfExtraArgs = [], presetManager, logger, skipLoadConfig = false, cwd = process.cwd(), env = process.env, isInteractive = () => process.stdin.isTTY === true && process.stdout.isTTY === true && process.stderr.isTTY === true, checkFzfAvailability = defaultCheckFzfAvailability, runFzf = defaultRunFzf }) => {
	if (isInteractive() !== true) throw new Error("Preset selection requires an interactive terminal");
	await ensureFzfAvailable(checkFzfAvailability);
	if (skipLoadConfig !== true) await presetManager.loadConfig();
	const presetInfos = presetManager.listPresets();
	if (presetInfos.length === 0) throw new Error("No presets defined");
	const fzfArgs = buildFzfArgs({
		surfaceMode,
		tmuxPopupOptions,
		fzfExtraArgs,
		env
	});
	logger.debug(`Preset selection UI: ${uiMode}`);
	return runFzfSelector({
		rows: toPresetRows({
			presetInfos,
			presetManager
		}),
		fzfArgs,
		runFzf,
		cwd,
		env
	});
};

//#endregion
//#region src/cli/index.ts
const backendValues = ["tmux", "wezterm"];
const listCommandName = "list";
const EXIT_CODE_CANCELLED = 130;
const optionNamesAllowOptionLikeValue = new Set(["fzfArg", "fzf-arg"]);
const toKebabCase = (value) => {
	return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
};
const toOptionSpec = (kind, optionName) => {
	return {
		kind,
		allowOptionLikeValue: optionNamesAllowOptionLikeValue.has(optionName)
	};
};
const buildOptionSpecs = (argsDef) => {
	const longOptions = /* @__PURE__ */ new Map();
	const shortOptions = /* @__PURE__ */ new Map();
	for (const [argName, arg] of Object.entries(argsDef)) {
		if (arg.type === "positional") continue;
		const valueKind = arg.type === "boolean" ? "boolean" : "value";
		const kebabName = toKebabCase(argName);
		longOptions.set(argName, toOptionSpec(valueKind, argName));
		longOptions.set(kebabName, toOptionSpec(valueKind, kebabName));
		const aliases = "alias" in arg ? Array.isArray(arg.alias) ? arg.alias : typeof arg.alias === "string" ? [arg.alias] : [] : [];
		for (const alias of aliases) {
			if (alias.length === 1) {
				shortOptions.set(alias, toOptionSpec(valueKind, alias));
				continue;
			}
			longOptions.set(alias, toOptionSpec(valueKind, alias));
			const kebabAlias = toKebabCase(alias);
			longOptions.set(kebabAlias, toOptionSpec(valueKind, kebabAlias));
		}
	}
	return {
		longOptions,
		shortOptions
	};
};
const validateRawOptions = (args, optionSpecs) => {
	for (let index = 0; index < args.length; index += 1) {
		const token = args[index];
		if (typeof token !== "string") continue;
		if (token === "--") break;
		if (!token.startsWith("-") || token === "-") continue;
		if (token.startsWith("--")) {
			const value = token.slice(2);
			if (value.length === 0) continue;
			const separatorIndex = value.indexOf("=");
			const rawOptionName = separatorIndex >= 0 ? value.slice(0, separatorIndex) : value;
			const optionName = rawOptionName.startsWith("no-") ? rawOptionName.slice(3) : rawOptionName;
			const optionSpec = optionSpecs.longOptions.get(optionName);
			const kind = optionSpec?.kind;
			if (kind === void 0) throw new Error(`Unknown option: --${rawOptionName}`);
			if (kind === "value") if (separatorIndex >= 0) {
				if (value.slice(separatorIndex + 1).length === 0) throw new Error(`Missing value for option: --${optionName}`);
			} else {
				const nextToken = args[index + 1];
				if (typeof nextToken !== "string" || nextToken.length === 0) throw new Error(`Missing value for option: --${optionName}`);
				if (nextToken.startsWith("-") && optionSpec?.allowOptionLikeValue !== true) throw new Error(`Missing value for option: --${optionName}`);
				index += 1;
			}
			continue;
		}
		const shortFlags = token.slice(1);
		for (let flagIndex = 0; flagIndex < shortFlags.length; flagIndex += 1) {
			const option = shortFlags[flagIndex];
			if (typeof option !== "string" || option.length === 0) continue;
			const optionSpec = optionSpecs.shortOptions.get(option);
			const kind = optionSpec?.kind;
			if (kind === void 0) throw new Error(`Unknown option: -${option}`);
			if (kind === "value") {
				if (flagIndex < shortFlags.length - 1) break;
				const nextToken = args[index + 1];
				if (typeof nextToken !== "string" || nextToken.length === 0) throw new Error(`Missing value for option: -${option}`);
				if (nextToken.startsWith("-") && optionSpec?.allowOptionLikeValue !== true) throw new Error(`Missing value for option: -${option}`);
				index += 1;
				break;
			}
		}
	}
};
const getPositionals = (args) => {
	return args._.filter((value) => typeof value === "string");
};
const collectOptionValues = ({ args, optionNames }) => {
	const values = [];
	const optionNameSet = new Set(optionNames);
	for (let index = 0; index < args.length; index += 1) {
		const token = args[index];
		if (typeof token !== "string") continue;
		if (token === "--") break;
		if (!token.startsWith("--")) continue;
		const eqIndex = token.indexOf("=");
		const rawName = eqIndex >= 0 ? token.slice(2, eqIndex) : token.slice(2);
		if (optionNameSet.has(rawName) !== true) continue;
		if (eqIndex >= 0) {
			values.push(token.slice(eqIndex + 1));
			continue;
		}
		const nextToken = args[index + 1];
		if (typeof nextToken === "string") {
			values.push(nextToken);
			index += 1;
		}
	}
	return values;
};
const createCli = (options = {}) => {
	const presetManager = options.presetManager ?? createPresetManager();
	const createCommandExecutor = options.createCommandExecutor ?? ((opts) => {
		if (opts.dryRun) return createDryRunExecutor({ verbose: opts.verbose });
		return createRealExecutor({ verbose: opts.verbose });
	});
	const selectPreset$1 = options.selectPreset ?? selectPreset;
	const core = options.core ?? {
		compilePreset,
		compilePresetFromValue,
		createLayoutPlan,
		emitPlan
	};
	const version = loadPackageVersion(createRequire(import.meta.url));
	let logger = createLogger();
	const errorHandlers = createCliErrorHandlers({ getLogger: () => logger });
	const rootArgsDef = {
		preset: {
			type: "positional",
			description: "Preset name (defaults to \"default\" preset when omitted)",
			required: false
		},
		verbose: {
			type: "boolean",
			description: "Show detailed logs"
		},
		dryRun: {
			type: "boolean",
			description: "Display commands without executing"
		},
		backend: {
			type: "enum",
			options: [...backendValues],
			description: "Select terminal backend (tmux or wezterm)"
		},
		config: {
			type: "string",
			valueHint: "path",
			description: "Path to configuration file"
		},
		currentWindow: {
			type: "boolean",
			description: "Use the current tmux window for layout (kills other panes)"
		},
		newWindow: {
			type: "boolean",
			description: "Always create a new tmux window for layout"
		},
		select: {
			type: "boolean",
			description: "Select preset from interactive UI"
		},
		selectUi: {
			type: "enum",
			options: [...selectUiModes],
			description: "Select preset UI backend (auto or fzf)"
		},
		selectSurface: {
			type: "enum",
			options: [...selectSurfaceModes],
			description: "Select selector surface mode (auto, inline, or tmux-popup)"
		},
		selectTmuxPopupOpts: {
			type: "string",
			valueHint: "opts",
			description: "tmux popup options used for fzf --tmux=<opts> (example: 80%,70%)"
		},
		fzfArg: {
			type: "string",
			valueHint: "arg",
			description: "Additional argument passed to fzf selector (repeatable)"
		},
		help: {
			type: "boolean",
			alias: "h",
			description: "Show help"
		},
		version: {
			type: "boolean",
			alias: "v",
			description: "Show version"
		}
	};
	const listCommand = defineCommand({ meta: {
		name: listCommandName,
		description: "List available presets"
	} });
	const rootCommand = defineCommand({
		meta: {
			name: "vde-layout",
			description: "VDE (Vibrant Development Environment) Layout Manager - tmux pane layout management tool",
			version
		},
		args: rootArgsDef,
		subCommands: { [listCommandName]: listCommand }
	});
	const optionSpecs = buildOptionSpecs(rootArgsDef);
	const run = async (args = process.argv.slice(2)) => {
		logger = createLogger();
		try {
			const normalizedArgs = normalizeSelectArgs(args);
			validateRawOptions(normalizedArgs, optionSpecs);
			const parsedArgs = parseArgs(normalizedArgs, rootArgsDef);
			const fzfCliArgs = collectOptionValues({
				args: normalizedArgs,
				optionNames: ["fzf-arg", "fzfArg"]
			});
			const positionals = getPositionals(parsedArgs);
			const headPositional = positionals[0];
			if (parsedArgs.help === true) {
				const usage = headPositional === listCommandName ? await renderUsage(listCommand, rootCommand) : await renderUsage(rootCommand);
				console.log(`${usage}\n`);
				return 0;
			}
			if (parsedArgs.version === true) {
				console.log(version);
				return 0;
			}
			logger = applyRuntimeOptions({
				runtimeOptions: {
					verbose: parsedArgs.verbose === true,
					config: typeof parsedArgs.config === "string" ? parsedArgs.config : void 0
				},
				createLogger,
				presetManager
			});
			if (headPositional === listCommandName) {
				const extraArgs = positionals.slice(1);
				if (extraArgs.length > 0) throw new Error(`too many arguments for '${listCommandName}'. Expected 0 arguments but got ${extraArgs.length}.`);
				return await listPresets({
					presetManager,
					logger,
					onError: errorHandlers.handleError
				});
			}
			if (positionals.length > 1) throw new Error(`too many arguments. Expected at most 1 argument but got ${positionals.length}.`);
			if (parsedArgs.selectUi !== void 0 && parsedArgs.select !== true) throw new Error("--select-ui requires --select");
			if (parsedArgs.selectSurface !== void 0 && parsedArgs.select !== true) throw new Error("--select-surface requires --select");
			if (parsedArgs.selectTmuxPopupOpts !== void 0 && parsedArgs.select !== true) throw new Error("--select-tmux-popup-opts requires --select");
			if (fzfCliArgs.length > 0 && parsedArgs.select !== true) throw new Error("--fzf-arg requires --select");
			if (parsedArgs.select === true && typeof headPositional === "string" && headPositional.length > 0) throw new Error("Cannot use preset argument with --select");
			let resolvedPresetName = headPositional;
			let configLoaded = false;
			if (parsedArgs.select === true) {
				await presetManager.loadConfig();
				configLoaded = true;
				const selectorDefaults = presetManager.getDefaults()?.selector;
				const selection = await selectPreset$1({
					uiMode: resolveSelectUiMode(typeof parsedArgs.selectUi === "string" ? parsedArgs.selectUi : selectorDefaults?.ui),
					surfaceMode: resolveSelectSurfaceMode(typeof parsedArgs.selectSurface === "string" ? parsedArgs.selectSurface : selectorDefaults?.surface),
					tmuxPopupOptions: typeof parsedArgs.selectTmuxPopupOpts === "string" ? parsedArgs.selectTmuxPopupOpts : selectorDefaults?.tmuxPopupOpts,
					fzfExtraArgs: [...selectorDefaults?.fzf?.extraArgs ?? [], ...fzfCliArgs],
					presetManager,
					logger,
					skipLoadConfig: true
				});
				if (selection.status === "cancelled") return EXIT_CODE_CANCELLED;
				resolvedPresetName = selection.presetName;
			}
			return await executePreset({
				presetName: resolvedPresetName,
				skipLoadConfig: configLoaded,
				options: {
					verbose: parsedArgs.verbose === true,
					dryRun: parsedArgs.dryRun === true,
					currentWindow: parsedArgs.currentWindow === true,
					newWindow: parsedArgs.newWindow === true,
					backend: typeof parsedArgs.backend === "string" ? parsedArgs.backend : void 0
				},
				presetManager,
				createCommandExecutor,
				core,
				logger,
				handleError: errorHandlers.handleError,
				handlePipelineFailure: errorHandlers.handlePipelineFailure
			});
		} catch (error) {
			return errorHandlers.handleError(error);
		}
	};
	return { run };
};

//#endregion
//#region src/index.ts
/**
* Main entry point
* Launches the CLI application
*/
const main = async () => {
	const cli = createCli();
	try {
		const exitCode = await cli.run(process.argv.slice(2));
		if (typeof exitCode === "number" && exitCode !== 0) process.exit(exitCode);
	} catch (error) {
		if (error instanceof Error) {
			console.error("Error:", error.message);
			if (process.env.VDE_DEBUG === "true") console.error(error.stack);
		} else console.error("An unexpected error occurred:", String(error));
		process.exit(1);
	}
};
main();

//#endregion
export {  };
//# sourceMappingURL=index.mjs.map