UNPKG

@prisma/cli

Version:

Command-line interface for the Prisma Developer Platform.

14,605 lines 514 kB
#!/usr/bin/env node
import { createRequire } from "node:module";
import process$1 from "node:process";
import { SERVICE_TOKEN_ENV_VAR, claimedExpiresAt, claimedIdentity, createCli, credentialWorkspaceId, credentialWorkspaceMismatchError, credentialsRequiredError, defineCommand, defineCommandFamily, defineConfigSection, defineSessionCommand, detectCI, emptyServiceTokenError, flag, loadConfig, noSessionForWorkspaceError, positional, readActiveAccessToken, telemetryCommandGroup } from "@prisma/cli-engine";
import { createComposerFamily } from "@prisma/composer-cli/family";
import { ormCommandFamily } from "@prisma/orm-toolchain/cli";
import { CliStructuredError, notOk, ok } from "@prisma/cli-engine/protocol";
import { AuthError, createManagementApiClient, createManagementApiSdk } from "@prisma/management-api-sdk";
import os from "node:os";
import path from "node:path";
import { randomBytes, randomUUID } from "node:crypto";
import fs, { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
import { mkdir as mkdir$1, readFile as readFile$1, writeFile as writeFile$1 } from "fs/promises";
import path$1 from "path";
import events from "node:events";
import http from "node:http";
import readline from "node:readline/promises";
import open from "open";
import { existsSync, fstatSync, readFileSync, statSync } from "node:fs";
import { PnpmTool, YarnTool } from "@manypkg/tools";
import { Result, TaggedError, UnhandledException, matchError } from "better-result";
import { execFile, fork, spawn } from "node:child_process";
import { promisify } from "node:util";
import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
import { parse } from "dotenv";
import { fileURLToPath } from "node:url";
import { execa } from "execa";
import { Writable } from "node:stream";
import { pipeline } from "node:stream/promises";
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
var __copyProps = (to, from, except, desc) => {
	if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
		key = keys[i];
		if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
			get: ((k) => from[k]).bind(null, key),
			enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
		});
	}
	return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
	value: mod,
	enumerable: true
}) : target, mod));
var __require = /* @__PURE__ */ createRequire(import.meta.url);
//#endregion
//#region src/cli-name.ts
/**
* The CLI's user-facing identity, in one place: the binary on PATH is
* `prisma`, published by the `prisma` package, and every user-facing
* command string and notice consumes this constant rather than
* restating the name. The `@prisma/cli` package installs the same shell
* under the name `prisma-cli`; what a user is told to type is the
* unified binary's name.
*/
const CLI_NAME = "prisma";
/** The unified CLI's docs section (also the update-check fallback
*  instruction URL). */
const CLI_DOCS_URL = "https://www.prisma.io/docs/cli";
/**
* Base URL for structured-error documentation links. The engine composes
* each diagnostic's docsUrl as base + code; every code is documented at
* this page (registry: docs/reference/error-reference.md).
*/
const DOCS_ERRORS_BASE_URL = "https://www.prisma.io/docs/cli/error-reference/";
//#endregion
//#region src/auth/client.ts
const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
const AUTH_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
/**
* The redirect the OAuth client is registered with. `performLogin`
* replaces it with its own ephemeral callback server's port; the
* refreshing client never reads it.
*/
const DEFAULT_REDIRECT_URI = "http://localhost/auth/callback";
function getApiBaseUrl(env = process.env) {
	return env.PRISMA_MANAGEMENT_API_URL?.trim() || "https://api.prisma.io";
}
function getAuthBaseUrl(env = process.env) {
	return env.PRISMA_AUTH_BASE_URL?.trim() || "https://auth.prisma.io";
}
function getAuthFilePath(env = process.env) {
	const configured = env[AUTH_FILE_ENV_VAR];
	if (configured?.trim()) return path.resolve(configured);
	return defaultAuthFilePath(env);
}
function defaultAuthFilePath(env = process.env) {
	if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "prisma", "auth.json");
	if (process.platform === "win32") {
		const appData = env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
		return path.join(appData, "prisma", "auth.json");
	}
	const xdgConfigHome = env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
	return path.join(xdgConfigHome, "prisma", "auth.json");
}
//#endregion
//#region ../../node_modules/.pnpm/xdg-app-paths@8.3.0/node_modules/xdg-app-paths/dist/cjs/lib/XDGAppPaths.js
var require_XDGAppPaths = /* @__PURE__ */ __commonJSMin(((exports) => {
	exports.__esModule = true;
	exports.Adapt = void 0;
	function isBoolean(t) {
		return typeOf(t) === "boolean";
	}
	function isObject(t) {
		return typeOf(t) === "object";
	}
	function isString(t) {
		return typeOf(t) === "string";
	}
	function typeOf(t) {
		return typeof t;
	}
	function Adapt(adapter_) {
		var meta = adapter_.meta, path = adapter_.path, xdg = adapter_.xdg;
		return { XDGAppPaths: new (function() {
			function XDGAppPaths_(options_) {
				if (options_ === void 0) options_ = {};
				var _a, _b, _c;
				function XDGAppPaths(options) {
					if (options === void 0) options = {};
					return new XDGAppPaths_(options);
				}
				var options = isObject(options_) ? options_ : { name: options_ };
				var suffix = (_a = options.suffix) !== null && _a !== void 0 ? _a : "";
				var isolated_ = (_b = options.isolated) !== null && _b !== void 0 ? _b : true;
				var namePriorityList = [
					options.name,
					meta.pkgMainFilename(),
					meta.mainFilename()
				];
				var name = path.parse(((_c = namePriorityList.find(function(e) {
					return isString(e);
				})) !== null && _c !== void 0 ? _c : "$eval") + suffix).name;
				XDGAppPaths.$name = function $name() {
					return name;
				};
				XDGAppPaths.$isolated = function $isolated() {
					return isolated_;
				};
				function isIsolated(dirOptions) {
					var _a;
					dirOptions = dirOptions !== null && dirOptions !== void 0 ? dirOptions : { isolated: isolated_ };
					return isBoolean(dirOptions) ? dirOptions : (_a = dirOptions.isolated) !== null && _a !== void 0 ? _a : isolated_;
				}
				function finalPathSegment(dirOptions) {
					return isIsolated(dirOptions) ? name : "";
				}
				XDGAppPaths.cache = function cache(dirOptions) {
					return path.join(xdg.cache(), finalPathSegment(dirOptions));
				};
				XDGAppPaths.config = function config(dirOptions) {
					return path.join(xdg.config(), finalPathSegment(dirOptions));
				};
				XDGAppPaths.data = function data(dirOptions) {
					return path.join(xdg.data(), finalPathSegment(dirOptions));
				};
				XDGAppPaths.runtime = function runtime(dirOptions) {
					return xdg.runtime() ? path.join(xdg.runtime(), finalPathSegment(dirOptions)) : void 0;
				};
				XDGAppPaths.state = function state(dirOptions) {
					return path.join(xdg.state(), finalPathSegment(dirOptions));
				};
				XDGAppPaths.configDirs = function configDirs(dirOptions) {
					return xdg.configDirs().map(function(s) {
						return path.join(s, finalPathSegment(dirOptions));
					});
				};
				XDGAppPaths.dataDirs = function dataDirs(dirOptions) {
					return xdg.dataDirs().map(function(s) {
						return path.join(s, finalPathSegment(dirOptions));
					});
				};
				return XDGAppPaths;
			}
			return XDGAppPaths_;
		}())() };
	}
	exports.Adapt = Adapt;
}));
//#endregion
//#region ../../node_modules/.pnpm/xdg-portable@10.6.0/node_modules/xdg-portable/dist/cjs/lib/XDG.js
var require_XDG = /* @__PURE__ */ __commonJSMin(((exports) => {
	var __spreadArray = exports && exports.__spreadArray || function(to, from) {
		for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i];
		return to;
	};
	exports.__esModule = true;
	exports.Adapt = void 0;
	function Adapt(adapter_) {
		var env = adapter_.env, osPaths = adapter_.osPaths, path = adapter_.path;
		var isMacOS = /^darwin$/i.test(adapter_.process.platform);
		var isWinOS = /^win/i.test(adapter_.process.platform);
		function baseDir() {
			return osPaths.home() || osPaths.temp();
		}
		function valOrPath(val, pathSegments) {
			return val || path.join.apply(path, pathSegments);
		}
		var linux = function() {
			var cache = function() {
				return valOrPath(env.get("XDG_CACHE_HOME"), [baseDir(), ".cache"]);
			};
			var config = function() {
				return valOrPath(env.get("XDG_CONFIG_HOME"), [baseDir(), ".config"]);
			};
			var data = function() {
				return valOrPath(env.get("XDG_DATA_HOME"), [
					baseDir(),
					".local",
					"share"
				]);
			};
			var runtime = function() {
				return env.get("XDG_RUNTIME_DIR") || void 0;
			};
			var state = function() {
				return valOrPath(env.get("XDG_STATE_HOME"), [
					baseDir(),
					".local",
					"state"
				]);
			};
			return {
				cache,
				config,
				data,
				runtime,
				state
			};
		};
		var macos = function() {
			var cache = function() {
				return valOrPath(env.get("XDG_CACHE_HOME"), [
					baseDir(),
					"Library",
					"Caches"
				]);
			};
			var config = function() {
				return valOrPath(env.get("XDG_CONFIG_HOME"), [
					baseDir(),
					"Library",
					"Preferences"
				]);
			};
			var data = function() {
				return valOrPath(env.get("XDG_DATA_HOME"), [
					baseDir(),
					"Library",
					"Application Support"
				]);
			};
			var runtime = function() {
				return env.get("XDG_RUNTIME_DIR") || void 0;
			};
			var state = function() {
				return valOrPath(env.get("XDG_STATE_HOME"), [
					baseDir(),
					"Library",
					"State"
				]);
			};
			return {
				cache,
				config,
				data,
				runtime,
				state
			};
		};
		var windows = function() {
			function appData() {
				return valOrPath(env.get("APPDATA"), [
					baseDir(),
					"AppData",
					"Roaming"
				]);
			}
			function localAppData() {
				return valOrPath(env.get("LOCALAPPDATA"), [
					baseDir(),
					"AppData",
					"Local"
				]);
			}
			var cache = function() {
				return valOrPath(env.get("XDG_CACHE_HOME"), [localAppData(), "xdg.cache"]);
			};
			var config = function() {
				return valOrPath(env.get("XDG_CONFIG_HOME"), [appData(), "xdg.config"]);
			};
			var data = function() {
				return valOrPath(env.get("XDG_DATA_HOME"), [appData(), "xdg.data"]);
			};
			var runtime = function() {
				return env.get("XDG_RUNTIME_DIR") || void 0;
			};
			var state = function() {
				return valOrPath(env.get("XDG_STATE_HOME"), [localAppData(), "xdg.state"]);
			};
			return {
				cache,
				config,
				data,
				runtime,
				state
			};
		};
		return { XDG: new (function() {
			function XDG_() {
				function XDG() {
					return new XDG_();
				}
				var extension = isMacOS ? macos() : isWinOS ? windows() : linux();
				XDG.cache = extension.cache;
				XDG.config = extension.config;
				XDG.data = extension.data;
				XDG.runtime = extension.runtime;
				XDG.state = extension.state;
				XDG.configDirs = function configDirs() {
					var pathList = env.get("XDG_CONFIG_DIRS");
					return __spreadArray([extension.config()], pathList ? pathList.split(path.delimiter) : []);
				};
				XDG.dataDirs = function dataDirs() {
					var pathList = env.get("XDG_DATA_DIRS");
					return __spreadArray([extension.data()], pathList ? pathList.split(path.delimiter) : []);
				};
				return XDG;
			}
			return XDG_;
		}())() };
	}
	exports.Adapt = Adapt;
}));
//#endregion
//#region ../../node_modules/.pnpm/os-paths@7.4.0/node_modules/os-paths/dist/cjs/lib/OSPaths.js
var require_OSPaths = /* @__PURE__ */ __commonJSMin(((exports) => {
	var __spreadArray = exports && exports.__spreadArray || function(to, from) {
		for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i];
		return to;
	};
	exports.__esModule = true;
	exports.Adapt = void 0;
	function isEmpty(s) {
		return !s;
	}
	function Adapt(adapter_) {
		var env = adapter_.env, os = adapter_.os, path = adapter_.path;
		var isWinOS = /^win/i.test(adapter_.process.platform);
		function normalizePath(path_) {
			return path_ ? adapter_.path.normalize(adapter_.path.join(path_, ".")) : void 0;
		}
		function home() {
			var posix = function() {
				return normalizePath((typeof os.homedir === "function" ? os.homedir() : void 0) || env.get("HOME"));
			};
			var windows = function() {
				return normalizePath([
					typeof os.homedir === "function" ? os.homedir() : void 0,
					env.get("USERPROFILE"),
					env.get("HOME"),
					env.get("HOMEDRIVE") || env.get("HOMEPATH") ? path.join(env.get("HOMEDRIVE") || "", env.get("HOMEPATH") || "") : void 0
				].find(function(v) {
					return !isEmpty(v);
				}));
			};
			return isWinOS ? windows() : posix();
		}
		function temp() {
			function joinPathToBase(base, segments) {
				return base ? path.join.apply(path, __spreadArray([base], segments)) : void 0;
			}
			function posix() {
				return normalizePath([
					typeof os.tmpdir === "function" ? os.tmpdir() : void 0,
					env.get("TMPDIR"),
					env.get("TEMP"),
					env.get("TMP")
				].find(function(v) {
					return !isEmpty(v);
				})) || "/tmp";
			}
			function windows() {
				var fallback = "C:\\Temp";
				var v = [
					typeof os.tmpdir === "function" ? os.tmpdir : function() {},
					function() {
						return env.get("TEMP");
					},
					function() {
						return env.get("TMP");
					},
					function() {
						return joinPathToBase(env.get("LOCALAPPDATA"), ["Temp"]);
					},
					function() {
						return joinPathToBase(home(), [
							"AppData",
							"Local",
							"Temp"
						]);
					},
					function() {
						return joinPathToBase(env.get("ALLUSERSPROFILE"), ["Temp"]);
					},
					function() {
						return joinPathToBase(env.get("SystemRoot"), ["Temp"]);
					},
					function() {
						return joinPathToBase(env.get("windir"), ["Temp"]);
					},
					function() {
						return joinPathToBase(env.get("SystemDrive"), ["\\", "Temp"]);
					}
				].find(function(v) {
					return v && !isEmpty(v());
				});
				return v && normalizePath(v()) || fallback;
			}
			return isWinOS ? windows() : posix();
		}
		return { OSPaths: new (function() {
			function OSPaths_() {
				function OSPaths() {
					return new OSPaths_();
				}
				OSPaths.home = home;
				OSPaths.temp = temp;
				return OSPaths;
			}
			return OSPaths_;
		}())() };
	}
	exports.Adapt = Adapt;
}));
//#endregion
//#region ../../node_modules/.pnpm/os-paths@7.4.0/node_modules/os-paths/dist/cjs/platform-adapters/node.js
var require_node$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
	var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		Object.defineProperty(o, k2, {
			enumerable: true,
			get: function() {
				return m[k];
			}
		});
	}) : (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		o[k2] = m[k];
	}));
	var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
		Object.defineProperty(o, "default", {
			enumerable: true,
			value: v
		});
	}) : function(o, v) {
		o["default"] = v;
	});
	var __importStar = exports && exports.__importStar || function(mod) {
		if (mod && mod.__esModule) return mod;
		var result = {};
		if (mod != null) {
			for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
		}
		__setModuleDefault(result, mod);
		return result;
	};
	exports.__esModule = true;
	exports.adapter = void 0;
	exports.adapter = {
		atImportPermissions: { env: true },
		env: { get: function(s) {
			return process.env[s];
		} },
		os: __importStar(__require("os")),
		path: __importStar(__require("path")),
		process
	};
}));
//#endregion
//#region ../../node_modules/.pnpm/os-paths@7.4.0/node_modules/os-paths/dist/cjs/mod.cjs.js
var require_mod_cjs$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
	var OSPaths_js_1 = require_OSPaths();
	var node_js_1 = require_node$2();
	module.exports = OSPaths_js_1.Adapt(node_js_1.adapter).OSPaths;
}));
//#endregion
//#region ../../node_modules/.pnpm/xdg-portable@10.6.0/node_modules/xdg-portable/dist/cjs/platform-adapters/node.js
var require_node$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
	var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		Object.defineProperty(o, k2, {
			enumerable: true,
			get: function() {
				return m[k];
			}
		});
	}) : (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		o[k2] = m[k];
	}));
	var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
		Object.defineProperty(o, "default", {
			enumerable: true,
			value: v
		});
	}) : function(o, v) {
		o["default"] = v;
	});
	var __importStar = exports && exports.__importStar || function(mod) {
		if (mod && mod.__esModule) return mod;
		var result = {};
		if (mod != null) {
			for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
		}
		__setModuleDefault(result, mod);
		return result;
	};
	var __importDefault = exports && exports.__importDefault || function(mod) {
		return mod && mod.__esModule ? mod : { "default": mod };
	};
	exports.__esModule = true;
	exports.adapter = void 0;
	var path$3 = __importStar(__require("path"));
	exports.adapter = {
		atImportPermissions: { env: true },
		env: { get: function(s) {
			return process.env[s];
		} },
		osPaths: __importDefault(require_mod_cjs$2())["default"],
		path: path$3,
		process
	};
}));
//#endregion
//#region ../../node_modules/.pnpm/xdg-portable@10.6.0/node_modules/xdg-portable/dist/cjs/mod.cjs.js
var require_mod_cjs$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
	var XDG_js_1 = require_XDG();
	var node_js_1 = require_node$1();
	module.exports = XDG_js_1.Adapt(node_js_1.adapter).XDG;
}));
//#endregion
//#region ../../node_modules/.pnpm/xdg-app-paths@8.3.0/node_modules/xdg-app-paths/dist/cjs/platform-adapters/node.js
var require_node = /* @__PURE__ */ __commonJSMin(((exports) => {
	var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		Object.defineProperty(o, k2, {
			enumerable: true,
			get: function() {
				return m[k];
			}
		});
	}) : (function(o, m, k, k2) {
		if (k2 === void 0) k2 = k;
		o[k2] = m[k];
	}));
	var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
		Object.defineProperty(o, "default", {
			enumerable: true,
			value: v
		});
	}) : function(o, v) {
		o["default"] = v;
	});
	var __importStar = exports && exports.__importStar || function(mod) {
		if (mod && mod.__esModule) return mod;
		var result = {};
		if (mod != null) {
			for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
		}
		__setModuleDefault(result, mod);
		return result;
	};
	var __importDefault = exports && exports.__importDefault || function(mod) {
		return mod && mod.__esModule ? mod : { "default": mod };
	};
	exports.__esModule = true;
	exports.adapter = void 0;
	var path$2 = __importStar(__require("path"));
	var xdg_portable_1 = __importDefault(require_mod_cjs$1());
	exports.adapter = {
		atImportPermissions: {
			env: true,
			read: true
		},
		meta: {
			mainFilename: function() {
				var requireMainFilename = (typeof __require !== "undefined" && __require !== null && __require.main ? __require.main : { filename: void 0 }).filename;
				return (requireMainFilename !== process.execArgv[0] ? requireMainFilename : void 0) || (typeof process._eval === "undefined" ? process.argv[1] : void 0);
			},
			pkgMainFilename: function() {
				return process.pkg ? process.execPath : void 0;
			}
		},
		path: path$2,
		process,
		xdg: xdg_portable_1["default"]
	};
}));
var mod_esm_default = (/* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
	var XDGAppPaths_js_1 = require_XDGAppPaths();
	var node_js_1 = require_node();
	module.exports = XDGAppPaths_js_1.Adapt(node_js_1.adapter).XDGAppPaths;
})))())).default;
//#endregion
//#region ../../node_modules/.pnpm/@prisma+credentials-store@7.8.0/node_modules/@prisma/credentials-store/dist/index.mjs
var CredentialsStore = class {
	loadedCredentials;
	authFilePath;
	/**
	* @param authFilePathOverride - Override for the path to the auth file. Can also be set via the PRISMA_PLATFORM_AUTH_FILE environment variable. Useful for testing.
	*/
	constructor(authFilePathOverride) {
		this.authFilePath = process.env.PRISMA_PLATFORM_AUTH_FILE || authFilePathOverride || path$1.join(mod_esm_default({ name: "prisma-platform" }).config(), "auth.json");
	}
	async reloadCredentialsFromDisk() {
		try {
			const content = await readFile$1(this.authFilePath, "utf-8");
			const data = JSON.parse(content);
			this.loadedCredentials = data.tokens || [];
		} catch (error) {
			this.loadedCredentials = [];
		}
	}
	async storeCredentials(credentials) {
		await this.reloadCredentialsFromDisk();
		const updatedCredentials = [...(this.loadedCredentials || []).filter((c) => c.workspaceId !== credentials.workspaceId), credentials];
		this.loadedCredentials = updatedCredentials;
		await this.writeCredentialsToDisk(updatedCredentials);
	}
	async deleteCredentials(workspaceId) {
		await this.reloadCredentialsFromDisk();
		const updatedCredentials = (this.loadedCredentials || []).filter((c) => c.workspaceId !== workspaceId);
		this.loadedCredentials = updatedCredentials;
		await this.writeCredentialsToDisk(updatedCredentials);
	}
	async getCredentials() {
		if (this.loadedCredentials === void 0) await this.reloadCredentialsFromDisk();
		return this.loadedCredentials || [];
	}
	async getCredentialsForWorkspace(workspaceId) {
		return (await this.getCredentials()).filter((c) => c.workspaceId === workspaceId)[0];
	}
	async writeCredentialsToDisk(credentials) {
		const data = { tokens: credentials };
		await mkdir$1(path$1.dirname(this.authFilePath), { recursive: true });
		await writeFile$1(this.authFilePath, JSON.stringify(data, null, 2));
	}
};
//#endregion
//#region src/lib/workspace-id.ts
/**
* A workspace reaches the CLI under two ids for the same workspace: a
* credential's `workspace_id` claim carries the bare id, while the
* management API and anything derived from it carry the same id behind
* a `wksp_` prefix. Comparing the two forms directly silently matches
* nothing, so every comparison between workspace ids of different
* origin goes through here.
*/
const WORKSPACE_ID_PREFIX = "wksp_";
function stripWorkspacePrefix(value) {
	return value.startsWith(WORKSPACE_ID_PREFIX) ? value.slice(5) : value;
}
function sameWorkspaceId(left, right) {
	return stripWorkspacePrefix(left) === stripWorkspacePrefix(right);
}
//#endregion
//#region src/auth/token-storage.ts
const REFRESH_LOCK_RETRY_MS$1 = 100;
const REFRESH_LOCK_STALE_MS$1 = 3e4;
const REFRESH_LOCK_WAIT_TIMEOUT_MS$1 = 25e3;
const EMPTY_AUTH_CONTEXT = {
	activeWorkspaceId: null,
	workspaces: {}
};
const UNKNOWN_WORKSPACE_NAME = "Unknown workspace";
function getAuthContextFilePath(authFilePath) {
	const extension = path.extname(authFilePath);
	if (!extension) return `${authFilePath}.context.json`;
	return `${authFilePath.slice(0, -extension.length)}.context${extension}`;
}
function findLatestValidTokens(allCredentials) {
	for (let i = allCredentials.length - 1; i >= 0; i -= 1) {
		const credential = allCredentials[i];
		if (!credential) continue;
		if (typeof credential.workspaceId !== "string" || credential.workspaceId.length === 0 || typeof credential.token !== "string" || credential.token.length === 0 || typeof credential.refreshToken !== "string" || credential.refreshToken.length === 0) continue;
		return {
			workspaceId: credential.workspaceId,
			accessToken: credential.token,
			refreshToken: credential.refreshToken
		};
	}
	return null;
}
function storedCredentialToTokens(credential) {
	if (!credential) return null;
	if (typeof credential.workspaceId !== "string" || credential.workspaceId.length === 0 || typeof credential.token !== "string" || credential.token.length === 0 || typeof credential.refreshToken !== "string" || credential.refreshToken.length === 0) return null;
	return {
		workspaceId: credential.workspaceId,
		accessToken: credential.token,
		refreshToken: credential.refreshToken
	};
}
function findTokensForWorkspace(allCredentials, workspaceId) {
	return storedCredentialToTokens(allCredentials.find((credential) => credential?.workspaceId === workspaceId)) ?? null;
}
function tokensEqual(a, b) {
	return a?.workspaceId === b?.workspaceId && a?.accessToken === b?.accessToken && a?.refreshToken === b?.refreshToken;
}
function sleep$2(ms, signal) {
	signal?.throwIfAborted();
	return new Promise((resolve, reject) => {
		const onAbort = () => {
			clearTimeout(timeout);
			reject(signal?.reason);
		};
		const timeout = setTimeout(() => {
			signal?.removeEventListener("abort", onAbort);
			resolve();
		}, ms);
		signal?.addEventListener("abort", onAbort, { once: true });
	});
}
var FileTokenStorage = class {
	credentialsStore;
	authFilePath;
	contextFilePath;
	lockFilePath;
	constructor(env = process.env, signal, options = {}) {
		this.signal = signal;
		this.options = options;
		const authFilePath = getAuthFilePath(env);
		this.authFilePath = authFilePath;
		this.contextFilePath = getAuthContextFilePath(authFilePath);
		this.credentialsStore = new CredentialsStore(authFilePath);
		this.lockFilePath = `${authFilePath}.lock`;
	}
	async getTokens() {
		this.signal?.throwIfAborted();
		try {
			const credentials = await this.readCredentialsFromDisk();
			if (this.options.pinnedWorkspaceId) return findTokensForWorkspace(credentials, this.options.pinnedWorkspaceId);
			const context = await this.readAuthContext();
			if (context.state.activeWorkspaceId) return findTokensForWorkspace(credentials, context.state.activeWorkspaceId);
			const latest = findLatestValidTokens(credentials);
			if (!latest) return null;
			if (!context.exists) {
				await this.setActiveWorkspaceId(latest.workspaceId);
				return latest;
			}
			return null;
		} catch (_error) {
			if (this.signal?.aborted) throw this.signal.reason;
			return null;
		}
	}
	async setTokens(tokens) {
		if (this.options.lockSetTokens === false) {
			await this.setTokensUnlocked(tokens);
			return;
		}
		await this.withRefreshLock(() => this.setTokensUnlocked(tokens));
	}
	async setTokensUnlocked(tokens) {
		this.signal?.throwIfAborted();
		await this.credentialsStore.storeCredentials({
			workspaceId: tokens.workspaceId,
			token: tokens.accessToken,
			refreshToken: tokens.refreshToken
		});
		this.signal?.throwIfAborted();
		await this.rememberWorkspaceUnlocked(tokens.workspaceId, {
			id: tokens.workspaceId,
			name: tokens.workspaceId
		});
		await this.maybeActivateWorkspaceId(tokens.workspaceId);
	}
	async clearTokens() {
		await this.withRefreshLock(() => this.clearTokensUnlocked());
	}
	async clearTokensUnlocked() {
		this.signal?.throwIfAborted();
		await fs.mkdir(path.dirname(this.authFilePath), { recursive: true });
		this.signal?.throwIfAborted();
		await fs.writeFile(this.authFilePath, `${JSON.stringify({ tokens: [] }, null, 2)}\n`, {
			encoding: "utf8",
			signal: this.signal
		});
		await this.clearAuthContext();
		await this.credentialsStore.reloadCredentialsFromDisk();
		this.signal?.throwIfAborted();
	}
	async clearTokensIfCurrent(tokens) {
		this.signal?.throwIfAborted();
		if (!tokensEqual(await this.getTokens(), tokens)) return;
		await this.credentialsStore.deleteCredentials(tokens.workspaceId);
		await this.removeRememberedWorkspace(tokens.workspaceId, { preserveActivePointer: true });
		this.signal?.throwIfAborted();
	}
	async listWorkspaces() {
		this.signal?.throwIfAborted();
		const credentials = await this.readCredentialsFromDisk();
		const context = await this.ensureMigratedAuthContext(credentials);
		return this.workspacesFromState(credentials, context);
	}
	workspacesFromState(credentials, context) {
		return credentials.map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null).map((tokens) => {
			const cached = context.state.workspaces[tokens.workspaceId];
			const id = typeof cached?.id === "string" && cached.id.trim().length > 0 ? cached.id.trim() : tokens.workspaceId;
			const name = typeof cached?.name === "string" && cached.name.trim().length > 0 ? workspaceDisplayName(cached.name.trim(), tokens.workspaceId) : UNKNOWN_WORKSPACE_NAME;
			const lastSeenAt = typeof cached?.lastSeenAt === "string" && cached.lastSeenAt.trim().length > 0 ? cached.lastSeenAt.trim() : null;
			return {
				id,
				name,
				credentialWorkspaceId: tokens.workspaceId,
				active: context.state.activeWorkspaceId === tokens.workspaceId,
				lastSeenAt
			};
		});
	}
	async listWorkspaceTokens() {
		this.signal?.throwIfAborted();
		return (await this.readCredentialsFromDisk()).map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null);
	}
	async useWorkspace(workspaceRef) {
		return this.withRefreshLock(() => this.useWorkspaceUnlocked(workspaceRef));
	}
	/**
	* Resolve a workspace ref (id, credential workspace id, or cached name)
	* against the locally stored sessions without changing the active
	* workspace. Read-only counterpart of useWorkspace: it reads the auth
	* context as-is and never runs the legacy-state migration, so it writes
	* nothing.
	*/
	async resolveWorkspace(workspaceRef) {
		const ref = workspaceRef.trim();
		if (!ref) throw new WorkspaceSelectionError("missing");
		this.signal?.throwIfAborted();
		const credentials = await this.readCredentialsFromDisk();
		const context = await this.readAuthContext();
		const matches = this.workspacesFromState(credentials, context).filter((workspace) => workspaceMatchesRef(workspace, ref));
		if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
		if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
		return matches[0];
	}
	async useWorkspaceUnlocked(workspaceRef) {
		const ref = workspaceRef.trim();
		if (!ref) throw new WorkspaceSelectionError("missing");
		const workspaces = await this.listWorkspaces();
		const context = await this.readAuthContext();
		const previous = workspaces.find((workspace) => workspace.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null;
		const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
		if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
		if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
		const selected = matches[0];
		await this.setActiveWorkspaceId(selected.credentialWorkspaceId);
		return {
			previous,
			selected
		};
	}
	async logoutWorkspace(workspaceRef) {
		return this.withRefreshLock(() => this.logoutWorkspaceUnlocked(workspaceRef));
	}
	async logoutWorkspaceUnlocked(workspaceRef) {
		const ref = workspaceRef.trim();
		if (!ref) throw new WorkspaceSelectionError("missing");
		const workspaces = await this.listWorkspaces();
		const context = await this.readAuthContext();
		const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
		if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
		if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
		const workspace = matches[0];
		const wasActive = context.state.activeWorkspaceId === workspace.credentialWorkspaceId;
		await this.credentialsStore.deleteCredentials(workspace.credentialWorkspaceId);
		this.signal?.throwIfAborted();
		const remainingWorkspaces = workspaces.filter((candidate) => candidate.credentialWorkspaceId !== workspace.credentialWorkspaceId);
		if (remainingWorkspaces.length === 0) {
			await this.clearAuthContext();
			return {
				workspace,
				wasActive,
				activeWorkspace: null
			};
		}
		delete context.state.workspaces[workspace.credentialWorkspaceId];
		if (wasActive) context.state.activeWorkspaceId = null;
		await this.writeAuthContext(context.state);
		return {
			workspace,
			wasActive,
			activeWorkspace: context.state.activeWorkspaceId === null ? null : remainingWorkspaces.find((candidate) => candidate.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null
		};
	}
	async rememberWorkspace(credentialWorkspaceId, workspace) {
		await this.withRefreshLock(() => this.rememberWorkspaceUnlocked(credentialWorkspaceId, workspace));
	}
	async rememberWorkspaceUnlocked(credentialWorkspaceId, workspace) {
		const context = await this.readAuthContext();
		context.state.workspaces[credentialWorkspaceId] = {
			id: workspace.id,
			name: workspace.name,
			lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		await this.writeAuthContext(context.state);
	}
	async withRefreshLock(fn) {
		const lockId = await this.acquireRefreshLock();
		try {
			return await fn();
		} finally {
			await this.releaseRefreshLock(lockId);
		}
	}
	async acquireRefreshLock() {
		const lockId = randomUUID();
		const startedAt = Date.now();
		const retryMs = this.options.lockRetryMs ?? REFRESH_LOCK_RETRY_MS$1;
		const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? REFRESH_LOCK_WAIT_TIMEOUT_MS$1;
		this.signal?.throwIfAborted();
		await fs.mkdir(path.dirname(this.lockFilePath), { recursive: true });
		while (true) {
			this.signal?.throwIfAborted();
			if (await this.tryCreateRefreshLock(lockId)) return lockId;
			if (await this.releaseStaleRefreshLock()) continue;
			this.throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs);
			await sleep$2(retryMs, this.signal);
		}
	}
	async tryCreateRefreshLock(lockId) {
		let lockFileCreated = false;
		try {
			const handle = await fs.open(this.lockFilePath, "wx");
			lockFileCreated = true;
			try {
				this.signal?.throwIfAborted();
				await handle.writeFile(lockId, { encoding: "utf8" });
				this.signal?.throwIfAborted();
			} finally {
				await handle.close();
			}
			return true;
		} catch (error) {
			if (lockFileCreated) await fs.unlink(this.lockFilePath).catch(() => void 0);
			if (error.code === "EEXIST") return false;
			throw error;
		}
	}
	async releaseStaleRefreshLock() {
		const staleLockId = await this.getStaleRefreshLockId();
		if (!staleLockId) return false;
		await this.releaseRefreshLock(staleLockId);
		return true;
	}
	throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs) {
		if (Date.now() - startedAt < waitTimeoutMs) return;
		throw new RefreshLockTimeoutError(this.lockFilePath, waitTimeoutMs);
	}
	async getStaleRefreshLockId() {
		this.signal?.throwIfAborted();
		const lockId = await fs.readFile(this.lockFilePath, {
			encoding: "utf8",
			signal: this.signal
		}).catch((error) => {
			if (this.signal?.aborted) throw error;
			return null;
		});
		if (lockId === null) return null;
		this.signal?.throwIfAborted();
		const stats = await fs.stat(this.lockFilePath).catch(() => null);
		this.signal?.throwIfAborted();
		if (!stats) return null;
		const staleMs = this.options.lockStaleMs ?? REFRESH_LOCK_STALE_MS$1;
		return Date.now() - stats.mtimeMs > staleMs ? lockId : null;
	}
	async releaseRefreshLock(lockId) {
		if (await fs.readFile(this.lockFilePath, { encoding: "utf8" }).catch(() => null) !== lockId) return;
		await fs.unlink(this.lockFilePath).catch(() => {});
	}
	async readCredentialsFromDisk() {
		this.signal?.throwIfAborted();
		await this.credentialsStore.reloadCredentialsFromDisk();
		this.signal?.throwIfAborted();
		return await this.credentialsStore.getCredentials();
	}
	async ensureMigratedAuthContext(credentials) {
		const context = await this.readAuthContext();
		if (context.state.activeWorkspaceId || context.exists) return context;
		const latest = findLatestValidTokens(credentials);
		if (!latest) return context;
		context.state.activeWorkspaceId = latest.workspaceId;
		context.state.workspaces[latest.workspaceId] ??= {
			id: latest.workspaceId,
			name: latest.workspaceId,
			lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		await this.writeAuthContext(context.state);
		return {
			exists: true,
			state: context.state
		};
	}
	async setActiveWorkspaceId(workspaceId) {
		const context = await this.readAuthContext();
		context.state.activeWorkspaceId = workspaceId;
		context.state.workspaces[workspaceId] ??= {
			id: workspaceId,
			name: workspaceId,
			lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		await this.writeAuthContext(context.state);
	}
	async maybeActivateWorkspaceId(workspaceId) {
		if (this.options.pinnedWorkspaceId) return;
		const context = await this.readAuthContext();
		if (this.options.activateOnSetTokens === false && context.exists && context.state.activeWorkspaceId && context.state.activeWorkspaceId !== workspaceId) return;
		context.state.activeWorkspaceId = workspaceId;
		context.state.workspaces[workspaceId] ??= {
			id: workspaceId,
			name: workspaceId,
			lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		await this.writeAuthContext(context.state);
	}
	async removeRememberedWorkspace(workspaceId, options) {
		const context = await this.readAuthContext();
		delete context.state.workspaces[workspaceId];
		if (!options.preserveActivePointer && context.state.activeWorkspaceId === workspaceId) context.state.activeWorkspaceId = null;
		await this.writeAuthContext(context.state);
	}
	async readAuthContext() {
		this.signal?.throwIfAborted();
		const raw = await fs.readFile(this.contextFilePath, {
			encoding: "utf8",
			signal: this.signal
		}).catch((error) => {
			if (this.signal?.aborted) throw error;
			if (error.code === "ENOENT") return null;
			throw error;
		});
		if (raw === null) return {
			exists: false,
			state: structuredClone(EMPTY_AUTH_CONTEXT)
		};
		try {
			const parsed = JSON.parse(raw);
			return {
				exists: true,
				state: {
					activeWorkspaceId: typeof parsed.activeWorkspaceId === "string" && parsed.activeWorkspaceId.trim().length > 0 ? parsed.activeWorkspaceId.trim() : null,
					workspaces: parsed.workspaces && typeof parsed.workspaces === "object" && !Array.isArray(parsed.workspaces) ? parsed.workspaces : {}
				}
			};
		} catch {
			return {
				exists: false,
				state: structuredClone(EMPTY_AUTH_CONTEXT)
			};
		}
	}
	async writeAuthContext(state) {
		this.signal?.throwIfAborted();
		await fs.mkdir(path.dirname(this.contextFilePath), { recursive: true });
		this.signal?.throwIfAborted();
		await fs.writeFile(this.contextFilePath, `${JSON.stringify(state, null, 2)}\n`, {
			encoding: "utf8",
			signal: this.signal
		});
		this.signal?.throwIfAborted();
	}
	async clearAuthContext() {
		await fs.unlink(this.contextFilePath).catch((error) => {
			if (error.code === "ENOENT") return;
			throw error;
		});
	}
};
var WorkspaceSelectionError = class extends Error {
	constructor(reason, workspaceRef, matches = []) {
		super(reason);
		this.reason = reason;
		this.workspaceRef = workspaceRef;
		this.matches = matches;
		this.name = "WorkspaceSelectionError";
	}
};
var RefreshLockTimeoutError = class extends Error {
	constructor(lockFilePath, waitTimeoutMs) {
		super(`Timed out waiting ${waitTimeoutMs}ms for auth refresh lock at ${lockFilePath}`);
		this.name = "RefreshLockTimeoutError";
	}
};
function workspaceMatchesRef(workspace, ref) {
	return sameWorkspaceId(workspace.credentialWorkspaceId, ref) || sameWorkspaceId(workspace.id, ref) || workspace.name.toLowerCase() === ref.toLowerCase();
}
function workspaceDisplayName(name, credentialWorkspaceId) {
	return name === credentialWorkspaceId ? UNKNOWN_WORKSPACE_NAME : name;
}
//#endregion
//#region src/auth/login.ts
var AuthError$1 = class extends Error {
	constructor(message) {
		super(message);
		this.name = "AuthError";
	}
};
async function login(options = {}) {
	const hostname = options.hostname ?? "localhost";
	const port = options.port ?? 0;
	const input = options.input ?? process.stdin;
	const output = options.output ?? process.stderr;
	const interactive = input.isTTY === true;
	const server = http.createServer();
	server.listen({
		host: hostname,
		port
	});
	const pasteAbort = new AbortController();
	try {
		const state = new LoginState({
			hostname,
			port: (await events.once(server, "listening").then(() => server.address())).port,
			tokenStorage: options.tokenStorage,
			clientId: options.clientId,
			apiBaseUrl: options.apiBaseUrl,
			authBaseUrl: options.authBaseUrl,
			openUrl: options.openUrl,
			onVerificationUrl: options.onVerificationUrl,
			env: options.env,
			signal: options.signal,
			output
		});
		let completed = false;
		let completion;
		const completeOnce = (url) => {
			if (!completion) completion = state.handleCallback(url).then(() => {
				completed = true;
			}, (error) => {
				completion = void 0;
				throw error;
			});
			return completion;
		};
		const httpResult = new Promise((resolve, reject) => {
			const onAbort = () => {
				reject(options.signal?.reason);
			};
			options.signal?.addEventListener("abort", onAbort, { once: true });
			const settle = (callback) => {
				options.signal?.removeEventListener("abort", onAbort);
				callback();
			};
			server.on("request", async (req, res) => {
				const url = new URL(`http://${state.host}${req.url}`);
				if (url.pathname !== "/auth/callback") {
					res.statusCode = 404;
					res.end("Not found");
					return;
				}
				if (completed) {
					const workspaceName = await state.resolveWorkspaceName();
					res.setHeader("Content-Type", "text/html; charset=utf-8");
					res.end(renderSuccessPage(workspaceName));
					return;
				}
				try {
					await completeOnce(url);
					const workspaceName = await state.resolveWorkspaceName();
					res.setHeader("Content-Type", "text/html; charset=utf-8");
					res.end(renderSuccessPage(workspaceName));
					settle(resolve);
				} catch (error) {
					res.statusCode = 400;
					res.setHeader("Content-Type", "text/plain; charset=utf-8");
					res.end("Sign-in could not be completed. Return to your terminal.");
					settle(() => reject(error));
					return;
				}
			});
		});
		options.signal?.throwIfAborted();
		const callbackResult = interactive ? Promise.race([httpResult, consumePastedCallback({
			input,
			output,
			signal: pasteAbort.signal,
			complete: completeOnce
		})]) : httpResult;
		await Promise.all([state.openLoginPage(interactive), callbackResult]);
	} finally {
		pasteAbort.abort();
		if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
	}
}
async function consumePastedCallback(options) {
	if (!options.input.isTTY) return;
	const rl = readline.createInterface({
		input: options.input,
		output: options.output
	});
	try {
		for (;;) {
			const url = await readPastedCallbackUrl(rl, options);
			if (url === null) return;
			if (url === void 0) continue;
			if (await tryCompletePastedCallback(url, options)) return;
		}
	} finally {
		rl.close();
	}
}
async function readPastedCallbackUrl(rl, options) {
	let answer;
	try {
		answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
	} catch (error) {
		if (error?.name === "AbortError") return null;
		throw error;
	}
	const trimmed = answer.trim().replace(/^["']|["']$/g, "");
	try {
		if (!trimmed) throw new Error("empty input");
		return new URL(trimmed);
	} catch {
		options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
		return;
	}
}
async function tryCompletePastedCallback(url, options) {
	try {
		await options.complete(url);
		return true;
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
		return false;
	}
}
var LoginState = class {
	latestVerifier;
	latestState;
	sdk;
	openUrl;
	tokenStorage;
	output;
	constructor(options) {
		this.options = options;
		this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal, { activateOnSetTokens: true });
		this.sdk = createManagementApiSdk({
			clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
			redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
			tokenStorage: this.tokenStorage,
			apiBaseUrl: options.apiBaseUrl ?? getApiBaseUrl(options.env),
			authBaseUrl: options.authBaseUrl
		});
		this.openUrl = options.openUrl ?? open;
		this.output = options.output;
	}
	async openLoginPage(interactive) {
		this.options.signal?.throwIfAborted();
		const { url, state, verifier } = await this.sdk.getLoginUrl({
			scope: "workspace:admin offline_access",
			additionalParams: {
				utm_source: "prisma-cli",
				utm_medium: "command-login",
				utm_campaign: "prisma-cli"
			}
		});
		this.latestState = state;
		this.latestVerifier = verifier;
		try {
			this.options.onVerificationUrl?.(url);
		} catch {}
		this.options.signal?.throwIfAborted();
		if (interactive) this.printLoginInstructions(url);
		try {
			await this.openUrl(url);
		} catch (error) {
			if (!interactive) throw error;
		}
		this.options.signal?.throwIfAborted();
	}
	printLoginInstructions(url) {
		const output = this.output;
		if (!output) return;
		output.write(`\nOpen this URL to sign in: ${url}\n\nIf the browser opens on another machine, finish sign-in there. When it\nredirects to localhost, copy the full localhost URL from the address bar\nand paste it here.\n\n`);
	}
	async handleCallback(url) {
		if (url.pathname !== "/auth/callback") throw new AuthError$1("Not a callback URL");
		const params = url.searchParams;
		const error = params.get("error");
		if (error) {
			const desc = params.get("error_description");
			throw new AuthError$1(desc ? `${error}: ${desc}` : error);
		}
		if (!this.latestVerifier) throw new AuthError$1("No verifier found");
		if (!this.latestState) throw new AuthError$1("No state found");
		try {
			await this.sdk.handleCallback({
				callbackUrl: url,
				verifier: this.latestVerifier,
				expectedState: this.latestState
			});
		} catch (error) {
			if (error instanceof AuthError) throw new AuthError$1(error.message);
			throw new AuthError$1(error instanceof Error ? error.message : "Unknown error during login");
		}
	}
	async resolveWorkspaceName() {
		try {
			const tokens = await this.tokenStorage.getTokens();
			if (!tokens?.workspaceId) return null;
			const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", {
				params: { path: { id: tokens.workspaceId } },
				signal: this.options.signal
			});
			const name = data?.data?.name;
			return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
		} catch {
			this.options.signal?.throwIfAborted();
			return null;
		}
	}
	get host() {
		return `${this.options.hostname}:${this.options.port}`;
	}
};
function renderSuccessPage(workspaceName) {
	return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Prisma Developer Platform</title>
  <style>
    :root {
      color-scheme: light dark;
      --background: #ffffff;
      --foreground: #1f2430;
      --muted: #4f5665;
      --mark-color: #050812;
      --surface: #f6f7fb;
      --border: #e4e7ee;
      --success: #15803d;
    }

    @media (prefers-color-scheme: dark) {
      :root {
        --background: #050812;
        --foreground: #f6f7fb;
        --muted: #c5cad6;
        --mark-color: #ffffff;
        --surface: #0d1322;
        --border: #232a3d;
        --success: #4ade80;
      }
    }

    * {
      box-sizing: border-box;
    }

    body {
      min-height: 100vh;
      margin: 0;
      background: var(--background);
      color: var(--foreground);
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      display: grid;
      grid-template-rows: 128px 1fr;
    }

    .mark {
      align-self: end;
      justify-self: center;
      width: 36px;
      height: 36px;
      color: var(--mark-color);
    }

    .mark path {
      fill: currentColor !important;
    }

    main {
      align-self: center;
      justify-self: center;
      width: min(520px, calc(100vw - 48px));
      margin-top: -128px;
      text-align: center;
    }

    h1 {
      margin: 0 0 12px;
      font-size: 26px;
      line-height: 1.2;
      font-weight: 700;
      letter-spacing: 0;
    }

    p {
      margin: 0 auto;
      max-width: 480px;
      color: var(--muted);
      font-size: 15px;
      line-height: 1.55;
      letter-spacing: 0;
    }

    .skills {
      margin-top: 40px;
      padding-top: 28px;
      border-top: 1px solid var(--border);
      text-align: left;
    }

    .skills-lead {
      display: flex;
      align-items: center;
      gap: 8px;
      margin: 0 0 12px;
      font-size: 15px;
      color: var(--foreground);
    }

    .skills-lead svg {
      flex: none;
      color: var(--muted);
    }

    .command {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 10px 8px 10px 16px;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 8px;
    }

    .command code {
      flex: 1;
      overflow-x: auto;
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
      font-size: 13.5px;
      white-space: nowrap;
    }

    .command .prompt {
      color: var(--muted);
      user-select: none;
    }

    .copy {
      flex: none;
      display: grid;
      place-items: center;
      padding: 6px;
      border: 0;
      border-radius: 6px;
      background: transparent;
      color: var(--muted);
      cursor: pointer;
    }

    .copy:hover {
      background: var(--border);
      color: var(--foreground);
    }

    .copy .icon-check,
    .copy.copied .icon-copy {
      display: none;
    }

    .copy.copied .icon-check {
      display: block;
    }

    .copy.copied,
    .copy.copied:hover {
      color: var(--success);
    }

    .visually-hidden {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip: rect(0 0 0 0);
      white-space: nowrap;
      border: 0;
    }
  </style>
</head>
<body>
  <svg class="mark" width="36" height="36" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M25.21,24.21,12.739,27.928a.525.525,0,0,1-.667-.606L16.528,5.811a.43.43,0,0,1,.809-.094l8.249,17.661A.6.6,0,0,1,25.21,24.21Zm2.139-.878L17.8,2.883h0A1.531,1.531,0,0,0,16.491,2a1.513,1.513,0,0,0-1.4.729L4.736,19.648a1.592,1.592,0,0,0,.018,1.7l5.064,7.909a1.628,1.628,0,0,0,1.83.678l14.7-4.383a1.6,1.6,0,0,0,1-2.218Z" style="fill:#0c344b;fill-rule:evenodd"/></svg>
  <main>
    <h1>You're all set.</h1>
    <p>${workspaceName ? `Your terminal is now connected to your ${escapeHtml(workspaceName)} workspace. Head back to your terminal to continue.` : "Your terminal is now connected to your Prisma workspace. Head back to your terminal to continue."}</p>
    <section class="skills">
      <div class="skills-lead">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/></svg>
        Using an AI coding agent? Add the Prisma skills:
      </div>
      <div class="command">
        <code><span class="prompt">$ </span><span class="command-text">npx skills add prisma/skills</span></code>
        <button class="copy" type="button" aria-label="Copy command to clipboard">
          <svg class="icon-copy" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
          <svg class="icon-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
        </button>
      </div>
      <span class="visually-hidden skills-status" role="status"></span>
    </section>
  </main>
  <script>
    (() => {
      const command = "npx skills add prisma/skills";
      const button = document.querySelector(".copy");
      const status = document.querySelector(".skills-status");
      let timer;
      button.addEventListener("click", async () => {
        try {
          await navigator.clipboard.writeText(command);
        } catch {
          // Clipboard access denied: select the command so Cmd/Ctrl+C works.
          const range = document.createRange();
          range.selectNodeContents(document.querySelector(".command-text"));
          const selection = window.getSelection();
          selection.removeAllRanges();
          selection.addRange(range);
          status.textContent =
            "Copying failed. The command is selected; press Ctrl+C or Cmd+C to copy it.";
          return;
        }
        button.classList.add("copied");
        status.textContent = "Command copied to clipboard.";
        clearTimeout(timer);
        timer = setTimeout(() => {
          button.classList.remove("copied");
          status.textContent = "";
        }, 2000);
      });
    })();
  <\/script>
</body>
</html>`;
}
function escapeHtml(value) {
	return value.replace(/[&<>"']/g, (char) => {
		switch (char) {
			case "&": return "&amp;";
			case "<": return "&lt;";
			case ">": return "&gt;";
			case "\"": return "&quot;";
			case "'": return "&#39;";
			default: return char;
		}
	});
}
//#endregion
//#region src/auth/operations.ts
/** Holds the tokens the SDK writes at callback time and nothing else:
*  minting and custody stay separate, so a login never writes through
*  the credential manager. */
var ThrowawayTokenStorage = class {
	tokens = null;
	async getTokens() {
		return this.tokens;
	}
	async setTokens(tokens) {
		this.tokens = tokens;
	}
	async clearTokens() {
		this.tokens = null;
	}
};
/** Runs the browser consent flow and RETURNS the minted credential.
*  Storing it is the caller's job. */
async function performLogin(env, signal, options) {
	const tokenStorage = new ThrowawayTokenStorage();
	await login({
		tokenStorage,
		env,
		signal,
		onVerificationUrl: options?.onVerificationUrl
	});
	const tokens = tokenStorage.tokens;
	if (!tokens) throw new AuthError$1("Sign-in finished without producing a credential.");
	return {
		token: tokens.accessToken,
		refreshToken: tokens.refreshToken,
		expiresAt: claimedExpiresAt(tokens.accessToken)
	};
}
//#endregion
//#region src/auth/service-token.ts
/**
* The env-supplied service token, trimmed — or undefined when the var
* is not set. A blank or whitespace value is never "not set" and never
* an override: it raises the single blank-token error, identically
* everywhere the environment credential would be consulted.
*/
function environmentServiceToken(env) {
	const raw = env[SERVICE_TOKEN_ENV_VAR];
	if (raw === void 0) return void 0;
	if (raw.trim().length === 0) throw emptyServiceTokenError({ envVar: SERVICE_TOKEN_ENV_VAR });
	return raw.trim();
}
/** Whether the environment credential is the one this process
*  authenticates as. It does not change stored state (design §11.7) —
*  this is a display fact. Blank raises. */
function environmentCredentialInForce(env) {
	return environmentServiceToken(env) !== void 0;
}
const PRISMA_CLI_PACKAGE_SPEC = `@prisma/cli@next`;
const DEFAULT_PRISMA_CLI_PACKAGE_RUNNER = ["npx", "-y"];
const PRISMA_CLI_BINARY = CLI_NAME;
function formatPrismaCliCommand(args, options = {}) {
	return [...getPrismaCliCommandPrefix(options), ...args].join(" ");
}
function getPrismaCliCommandPrefix({ invocation = "package", packageRunner = DEFAULT_PRISMA_CLI_PACKAGE_RUNNER }) {
	if (invocation === "binary") return [PRISMA_CLI_BINARY];
	return [...packageRunner, PRISMA_CLI_PACKAGE_SPEC];
}
//#endregion
//#region src/lib/agent/package-manager.ts
const LOCKFILE_PACKAGE_MANAGERS = [
	{
		packageManager: "bun",
		fileNames: ["bun.lock", "bun.lockb"]
	},
	{
		packageManager: "pnpm",
		fileNames: ["pnpm-lock.yaml", "pnpm-workspace.yaml"]
	},
	{
		packageManager: "yarn",
		fileNames: ["yarn.lock"]
	},
	{
		packageManager: "npm",
		fileNames: ["package-lock.json", "npm-shrinkwrap.json"]
	}
];
async function resolvePackageRunner(options) {
	options.signal.throwIfAborted();
	const packageManager = detectPackageManagerSync(options.cwd, options.signal) ?? "npm";
	options.signal.throwIfAborted();
	return packageRunnerForPackageManager(packageManager);
}
function detectPackageManagerSync(cwd, signal) {
	let directory = path.resolve(cwd);
	while (true) {
		signal?.throwIfAborted();
		const packageJsonManager = readPackageJsonPackageManager(directory);
		if (packageJsonManager) return packageJsonManager;
		const lockfileManager = readLockfilePackageManager(directory, signal);
		if (lockfileManager) return lockfileManager;
		const parent = path.dirname(directory);
		if (parent === directory) return null;
		directory = parent;
	}
}
function readPackageJsonPackageManager(directory) {
	const packageJsonPath = path.join(directory, "package.json");
	let content;
	try {
		content = readFileSync(packageJsonPath, "utf8");
	} catch (error) {
		if (isMissingFileError(error)) return null;
		throw error;
	}
	try {
		return parsePackageManager(JSON.parse(content).packageManager);
	} catch {
		return null;
	}
}
function readLockfilePackageManager(directory, signal) {
	for (const candidate of LOCKFILE_PACKAGE_MANAGERS) for (const fileName of candidate.fileNames) {
		signal?.throwIfAborted();
		if (fileExists(path.join(directory, fileName))) return candidate.packageManager;
	}
	return null;
}
function fileExists(filePath) {
	try {
		return statSync(filePath).isFile();
	} catch (error) {
		if (isMissingFileError(error)) return false;
		throw error;
	}
}
function parsePackageManager(value) {
	if (typeof value !== "string") return null;
	const normalized = value.trim().toLowerCase();
	if (normalized === "bun" || normalized.startsWith("bun@")) return "bun";
	if (normalized === "pnpm" || normalized.startsWith("pnpm@")) return "pnpm";
	if (normalized === "yarn" || normalized.startsWith("yarn@")) return "yarn";
	if (normalized === "npm" || normalized.startsWith("npm@")) return "npm";
	return null;
}
/** The command that installs what package.json already declares —
*  init writes the version into the manifest, so a plain install is
*  right for every manager. */
function resolveInstallCommandSync(cwd) {
	return installCommandForPackageManager(detectPackageManagerSync(cwd) ?? "npm");
}
function installCommandForPackageManager(packageManager) {
	switch (packageManager) {
		case "bun": return "bun install";
		case "pnpm": return "pnpm install";
		case "yarn": return "yarn install";
		case "npm": return "npm install";
	}
}
function packageRunnerForPackageManager(packageManager) {
	switch (packageManager) {
		case "bun": return ["bunx"];
		case "pnpm": return ["pnpm", "dlx"];
		case "yarn": return ["yarn", "dlx"];
		case "npm": return ["npx", "-y"];
	}
}
function isMissingFileError(error) {
	const code = error.code;
	return code === "ENOENT" || code === "ENOTDIR";
}
//#endregion
//#region src/lib/agent/cli-command.ts
async function resolvePrismaCliPackageCommandFormatter(options) {
	return createPrismaCliPackageCommandFormatter(await resolvePackageRunner(options));
}
async function resolvePrismaCliPackageCommand(options) {
	return (await resolvePrismaCliPackageCommandFormatter(options))(options.args);
}
function createPrismaCliPackageCommandFormatter(packageRunner) {
	return (args) => formatPrismaCliCommand(args, { packageRunner });
}
//#endregion
//#region src/lib/semver-order.ts
function parseVersion(version) {
	const match = VERSION_PATTERN.exec(version);
	if (!match) return null;
	return {
		major: Number(match[1]),
		minor: Number(match[2]),
		patch: Number(match[3]),
		prerelease: match[4]?.split(".") ?? []
	};
}
const VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
const NUMERIC_PART = /^\d+$/;
/** Negative when left is older, positive when newer, 0 when equal, and
*  null when either side is not a version this understands. */
function compareVersionStrings(left, right) {
	const parsedLeft = parseVersion(left);
	const parsedRight = parseVersion(right);
	if (!parsedLeft || !parsedRight) return null;
	return compareVersions(parsedLeft, parsedRight);
}
function compareVersions(left, right) {
	for (const key of [
		"major",
		"minor",
		"patch"
	]) {
		const diff = left[key] - right[key];
		if (diff !== 0) return diff;
	}
	return comparePrerelease(left.prerelease, right.prerelease);
}
function comparePrerelease(left, right) {
	if (left.length === 0 && right.length === 0) return 0;
	if (left.length === 0) return 1;
	if (right.length === 0) return -1;
	const count = Math.max(left.length, right.length);
	for (let index = 0; index < count; index += 1) {
		const leftPart = left[index];
		const rightPart = right[index];
		if (leftPart === void 0) return -1;
		if (rightPart === void 0) return 1;
		const diff = comparePrereleasePart(leftPart, rightPart);
		if (diff !== 0) return diff;
	}
	return 0;
}
function comparePrereleasePart(left, right) {
	const leftNumber = NUMERIC_PART.test(left) ? Number(left) : null;
	const rightNumber = NUMERIC_PART.test(right) ? Number(right) : null;
	if (leftNumber !== null && rightNumber !== null) return leftNumber - rightNumber;
	if (leftNumber !== null) return -1;
	if (rightNumber !== null) return 1;
	return left.localeCompare(right);
}
//#endregion
//#region src/lib/skills/allowlist.ts
/**
* SECURITY INVARIANT — read before changing this list.
*
* Skill content only ever comes from the packages named here. The sync
* command never scans node_modules, or any other directory, looking for
* skills to install, and no discovery mode may be added: a skill is
* instructions an agent will follow, so installing one from an
* arbitrary transitive dependency hands that dependency's author
* influence over the user's agent. Resolving these names keeps the
* trust boundary identical to the code's — if you run
* `@prisma/orm-postgres`, you already trust its author.
*
* Adding a package here is a deliberate decision about a package Prisma
* publishes. This is permanent.
*/
const SKILL_SOURCE_PACKAGES = [
	"@prisma/orm-postgres",
	"@prisma/orm-sqlite",
	"@prisma/orm-mongo",
	"@prisma/composer"
];
/** The directory inside a source package's tarball that holds its skill
*  trees, one directory per skill. */
const PACKAGE_SKILLS_DIR = "skills";
/**
* The agent harnesses this CLI can install skills for, each mapped to
* the project-relative directory that harness reads its skills from.
* There is no harness detection anywhere: which of these a project uses
* comes from `skills: { agents: [...] }` in prisma.config.ts, and every
* one of them when the config says nothing.
*/
const AGENT_SKILL_DIRS = {
	claude: ".claude/skills",
	cursor: ".cursor/skills",
	agents: ".agents/skills",
	devin: ".devin/skills"
};
const KNOWN_AGENTS = Object.keys(AGENT_SKILL_DIRS);
/** Without a config, sync writes every known agent's directory, so a
*  harness adopted later finds the skills already there. */
const DEFAULT_AGENTS = KNOWN_AGENTS;
function isKnownAgent(name) {
	return name in AGENT_SKILL_DIRS;
}
function agentSkillDirs(agents) {
	return agents.map((agent) => AGENT_SKILL_DIRS[agent]);
}
function isSkillSourcePackage(name) {
	return SKILL_SOURCE_PACKAGES.includes(name);
}
//#endregion
//#region src/lib/skills/unquote.ts
const QUOTED = /^(["'])(.*)\1$/;
/** Strips one layer of matching single or double quotes. */
function unquote(value) {
	return QUOTED.exec(value)?.[2] ?? value;
}
//#endregion
//#region src/lib/skills/frontmatter.ts
const LINE_BREAK = /\r?\n/;
const INDENTED = /^[ \t]/;
const EMPTY_STAMP = {
	library: null,
	libraryVersion: null
};
const METADATA_KEY = "metadata";
const STAMP_KEYS = new Map([["library", "library"], ["library_version", "libraryVersion"]]);
/**
* The `library` and `library_version` entries of a SKILL.md's
* `metadata` map. The Agent Skills spec defines no custom top-level
* frontmatter keys — extensions live under `metadata`, a map of strings
* — so the stamp is read there and nowhere else. A file without
* frontmatter, without a `metadata` map, or without those entries
* reports nulls rather than failing.
*
* Contract: this reads only the stamp this CLI writes. A file it
* cannot read classifies as unmanaged, which sync refuses to touch, so
* a parse bug can never delete user files.
*/
function parseSkillStamp(source) {
	const lines = source.split(LINE_BREAK);
	if (lines[0]?.trim() !== "---") return EMPTY_STAMP;
	const stamp = {
		library: null,
		libraryVersion: null
	};
	let inMetadata = false;
	for (const line of lines.slice(1)) {
		if (line.trim() === "---") break;
		if (line.trim() === "") continue;
		if (!INDENTED.test(line)) {
			inMetadata = keyOf(line) === METADATA_KEY;
			continue;
		}
		if (!inMetadata) continue;
		const field = STAMP_KEYS.get(keyOf(line) ?? "");
		if (field) stamp[field] = valueAfterKey(line);
	}
	return stamp;
}
async function readSkillStamp(path) {
	try {
		return parseSkillStamp(await readFile(path, "utf8"));
	} catch {
		return null;
	}
}
function keyOf(line) {
	const separator = line.indexOf(":");
	return separator === -1 ? null : line.slice(0, separator).trim();
}
function valueAfterKey(line) {
	const separator = line.indexOf(":");
	return unquote(line.slice(separator + 1).trim());
}
//#endregion
//#region src/lib/skills/opt-out.ts
/**
* The project's persisted answer to the staleness check, written by
* `skills sync --disable` and read by the check on every command. It
* sits at the project root beside the CLI's other local state, so the
* opt-out follows the project rather than one machine's environment.
*/
const SKILLS_STATE_FILE = path.join(".prisma", "skills.json");
function skillsStatePath(projectRoot) {
	return path.join(projectRoot, SKILLS_STATE_FILE);
}
async function readSkillsCheckDisabled(projectRoot) {
	try {
		return JSON.parse(await readFile(skillsStatePath(projectRoot), "utf8")).check === false;
	} catch {
		return false;
	}
}
async function writeSkillsCheckDisabled(projectRoot, disabled) {
	const target = skillsStatePath(projectRoot);
	await mkdir(path.dirname(target), { recursive: true });
	await writeFile(target, `${JSON.stringify({ check: !disabled }, null, 2)}\n`, "utf8");
}
//#endregion
//#region src/lib/skills/resolve.ts
/**
* Standard module resolution of one named package from one directory.
* Under Yarn PnP this goes through the PnP resolver and answers a path
* inside a zip, which the patched filesystem reads like any other.
*/
async function resolvePackage(fromDir, packageName) {
	const dir = resolvePackageDir(fromDir, packageName);
	if (dir === null) return null;
	const version = await readPackageVersion(path.join(dir, "package.json"));
	return version === null ? null : {
		name: packageName,
		version,
		dir,
		resolvedFrom: fromDir
	};
}
function resolvePackageDir(fromDir, packageName) {
	const requireFrom = createRequire(path.join(fromDir, "package.json"));
	try {
		return path.dirname(requireFrom.resolve(`${packageName}/package.json`));
	} catch {}
	try {
		return packageRootOf(requireFrom.resolve(packageName), packageName);
	} catch {
		return null;
	}
}
/** Walks up from a resolved entry point to the directory named by the
*  package specifier — the last `node_modules/<name>` segment on the
*  path, or the first ancestor holding a package.json with that name. */
function packageRootOf(entry, packageName) {
	const marker = `${path.sep}node_modules${path.sep}${packageName.split("/").join(path.sep)}`;
	const at = entry.lastIndexOf(marker);
	if (at !== -1) return entry.slice(0, at + marker.length);
	let dir = path.dirname(entry);
	for (;;) {
		if (path.basename(dir) === path.basename(packageName)) return dir;
		const parent = path.dirname(dir);
		if (parent === dir) return null;
		dir = parent;
	}
}
async function readPackageVersion(manifestPath) {
	try {
		const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
		return typeof manifest.version === "string" ? manifest.version : null;
	} catch {
		return null;
	}
}
//#endregion
//#region src/lib/skills/workspace-members.ts
/**
* The workspace member directories declared by the root's workspace
* config, expanded from its globs by @manypkg/tools. Enumeration reads
* the declared globs from plain files (pnpm-workspace.yaml or
* package.json) and its glob expansion ignores node_modules, so a
* package is resolvable from a member directory only because the user
* declared that member — never because something was found by scanning.
*
* PnpmTool covers pnpm; YarnTool reads the package.json `workspaces`
* field in both its array and `{ packages }` forms, which is how npm,
* Yarn (Plug'n'Play included — the globs live in plain files), and bun
* all declare their members. Only directories holding a package.json
* come back.
*/
async function workspaceMemberDirs(root) {
	const dirs = /* @__PURE__ */ new Set();
	if (await hasPnpmWorkspace(root)) for (const dir of await memberDirsVia(PnpmTool, root)) dirs.add(dir);
	if (await hasPackageJsonWorkspaces(root)) for (const dir of await memberDirsVia(YarnTool, root)) dirs.add(dir);
	dirs.delete(path.resolve(root));
	return [...dirs].sort();
}
async function memberDirsVia(tool, root) {
	try {
		const { packages } = await tool.getPackages(root);
		return packages.map((pkg) => path.resolve(pkg.dir));
	} catch {
		return [];
	}
}
async function hasPnpmWorkspace(root) {
	try {
		await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8");
		return true;
	} catch {
		return false;
	}
}
async function hasPackageJsonWorkspaces(root) {
	try {
		return JSON.parse(await readFile(path.join(root, "package.json"), "utf8")).workspaces !== void 0;
	} catch {
		return false;
	}
}
//#endregion
//#region src/lib/skills/status.ts
async function readSkillsStatus(cwd, options) {
	const projectRoot = path.resolve(cwd);
	const dirs = agentSkillDirs(options?.agents ?? DEFAULT_AGENTS);
	const checkDisabled = options?.checkDisabled ?? await readSkillsCheckDisabled(projectRoot);
	const packages = await findInstalledSourcePackages(projectRoot);
	const sources = await collectSkillSources(packages);
	const skills = [];
	for (const source of sources.values()) skills.push(await readSkillStatus(projectRoot, dirs, source));
	skills.sort((left, right) => left.skill.localeCompare(right.skill));
	return {
		projectRoot,
		checkDisabled,
		packages,
		skills,
		orphans: options?.orphans === false ? [] : await findOrphanedSkills(projectRoot, dirs, new Set(sources.keys())),
		upToDate: skills.every((skill) => skill.upToDate)
	};
}
/**
* The allowlisted packages installed in this project, resolved by name
* from the project root and from each declared workspace member. Never
* a directory scan.
*/
async function findInstalledSourcePackages(projectRoot) {
	const searchDirs = [projectRoot, ...await workspaceMemberDirs(projectRoot)];
	const found = [];
	for (const name of SKILL_SOURCE_PACKAGES) {
		const resolutions = [];
		for (const dir of searchDirs) {
			const resolved = await resolvePackage(dir, name);
			if (resolved !== null) resolutions.push(resolved);
		}
		if (resolutions.length === 0) continue;
		const highest = resolutions.reduce((best, candidate) => (compareVersionStrings(candidate.version, best.version) ?? 0) > 0 ? candidate : best);
		const versions = [...new Set(resolutions.map((one) => one.version))].sort();
		found.push({
			name,
			version: highest.version,
			dir: highest.dir,
			conflictingVersions: versions.length > 1 ? versions : []
		});
	}
	return found;
}
/** Every skill tree the installed source packages ship, keyed by skill
*  name. When two packages ship the same skill, the higher version
*  wins — the public packages version in lockstep, so this only
*  arbitrates a half-finished upgrade. */
async function collectSkillSources(packages) {
	const sources = /* @__PURE__ */ new Map();
	for (const installed of packages) {
		const skillsDir = path.join(installed.dir, PACKAGE_SKILLS_DIR);
		for (const skill of await skillDirectories(skillsDir)) {
			const existing = sources.get(skill);
			if (existing !== void 0 && (compareVersionStrings(installed.version, existing.version) ?? 0) <= 0) continue;
			sources.set(skill, {
				skill,
				library: installed.name,
				version: installed.version,
				dir: path.join(skillsDir, skill)
			});
		}
	}
	return sources;
}
async function readSkillStatus(projectRoot, dirs, source) {
	const targets = [];
	for (const dir of dirs) {
		const skillFile = path.join(projectRoot, dir, source.skill, "SKILL.md");
		const stamp = await readSkillStamp(skillFile);
		targets.push({
			dir,
			syncedVersion: stamp?.libraryVersion ?? null,
			state: await targetState(skillFile, stamp, source.version)
		});
	}
	return {
		skill: source.skill,
		library: source.library,
		version: source.version,
		sourceDir: source.dir,
		targets,
		upToDate: targets.every((target) => target.state === "synced" || target.state === "unmanaged")
	};
}
async function targetState(skillFile, stamp, sourceVersion) {
	if (stamp === null) return await missingFromDisk(skillFile) ? "absent" : "unmanaged";
	if (stamp.library === null || !isSkillSourcePackage(stamp.library)) return "unmanaged";
	return stamp.libraryVersion === sourceVersion ? "synced" : "stale";
}
async function missingFromDisk(target) {
	try {
		await stat(target);
		return false;
	} catch (error) {
		return error.code === "ENOENT";
	}
}
/**
* Copies in the harness directories that this CLI installed — their
* SKILL.md names an allowlisted package as its `library` — and that no
* installed package still provides. A skill from anywhere else is
* someone else's file and is never touched.
*/
async function findOrphanedSkills(projectRoot, dirs, provided) {
	const orphans = /* @__PURE__ */ new Map();
	for (const dir of dirs) {
		const harnessDir = path.join(projectRoot, dir);
		for (const skill of await skillDirectories(harnessDir)) {
			if (provided.has(skill)) continue;
			const stamp = await readSkillStamp(path.join(harnessDir, skill, "SKILL.md"));
			if (stamp?.library === null || stamp === null) continue;
			if (!isSkillSourcePackage(stamp.library)) continue;
			const entry = orphans.get(skill) ?? {
				library: stamp.library,
				dirs: []
			};
			entry.dirs.push(dir);
			orphans.set(skill, entry);
		}
	}
	return [...orphans.entries()].map(([skill, entry]) => ({
		skill,
		library: entry.library,
		dirs: entry.dirs
	}));
}
/** The subdirectories of `dir` that hold a SKILL.md. */
async function skillDirectories(dir) {
	let entries;
	try {
		entries = (await readdir(dir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
	} catch {
		return [];
	}
	const skills = [];
	for (const name of entries.sort()) if (await isFile(path.join(dir, name, "SKILL.md"))) skills.push(name);
	return skills;
}
async function isFile(target) {
	try {
		return (await stat(target)).isFile();
	} catch {
		return false;
	}
}
//#endregion
//#region src/commands/skills/config.ts
const DEFAULT = {
	check: true,
	agents: DEFAULT_AGENTS,
	agentsConfigured: false
};
const SKILLS_CONFIG_SECTION_NAME = "skills";
function invalidSection(value) {
	return {
		code: "SKILLS.CONFIG_INVALID",
		severity: "error",
		summary: `The 'skills' config section must be an object, and is ${describe(value)}.`,
		nextActions: [{
			kind: "user-choice",
			label: "Write skills: { check: false } to silence the skills check."
		}]
	};
}
function invalidCheck(value) {
	return {
		code: "SKILLS.CONFIG_INVALID",
		severity: "error",
		summary: `skills.check must be true or false, and is ${describe(value)}.`,
		nextActions: [{
			kind: "user-choice",
			label: "Set skills.check to true or false, or remove it."
		}]
	};
}
function invalidAgents(value) {
	return {
		code: "SKILLS.CONFIG_INVALID",
		severity: "error",
		summary: `skills.agents must be an array of agent names, and is ${describe(value)}.`,
		nextActions: [{
			kind: "user-choice",
			label: `List the agents to install skills for (${KNOWN_AGENTS.join(", ")}), or remove skills.agents to install for all of them.`
		}]
	};
}
function unknownAgent(name) {
	return {
		code: "SKILLS.CONFIG_INVALID",
		severity: "error",
		summary: `skills.agents names '${name}', which this CLI does not know. The known agents are ${KNOWN_AGENTS.join(", ")}.`,
		nextActions: [{
			kind: "user-choice",
			label: `Remove '${name}' from skills.agents, or update the CLI if a newer version knows it.`
		}]
	};
}
function describe(value) {
	return value === null ? "null" : typeof value;
}
function validateAgents(raw) {
	if (raw === void 0) return {
		ok: true,
		agents: DEFAULT_AGENTS
	};
	if (!Array.isArray(raw)) return {
		ok: false,
		diagnostic: invalidAgents(raw)
	};
	const agents = [];
	for (const entry of raw) {
		if (typeof entry !== "string" || !isKnownAgent(entry)) return {
			ok: false,
			diagnostic: typeof entry === "string" ? unknownAgent(entry) : invalidAgents(entry)
		};
		if (!agents.includes(entry)) agents.push(entry);
	}
	return {
		ok: true,
		agents
	};
}
/**
* The validated skills section of an already-loaded config, or null
* when the section does not validate. Used by code that runs outside a
* command handler (the staleness check, the post-login tip), which has
* no ctx.config.
*
* Null deliberately collapses "no config" and "config invalid": both
* callers fall back to the default agent set, so a broken config never
* silences the check. The commands that consume the config surface the
* validation error themselves.
*/
function readSkillsConfig(loaded) {
	const section = skillsConfigSection.validate(loaded.sections[SKILLS_CONFIG_SECTION_NAME]);
	return section.ok ? section.value : null;
}
/**
* The project's skills settings from prisma.config.ts, or null when no
* config file exists — decided with one stat, so a project without a
* config never pays the file's TypeScript transpile — or the section
* does not validate.
*/
async function readProjectSkillsConfig(cwd, configPath) {
	if (!existsSync(configPath === void 0 ? path.join(cwd, "prisma.config.ts") : path.resolve(cwd, configPath))) return null;
	return readSkillsConfig(await loadConfig(cwd, configPath));
}
const skillsConfigSection = defineConfigSection({
	name: SKILLS_CONFIG_SECTION_NAME,
	validate: (raw) => {
		if (raw === void 0) return {
			ok: true,
			value: DEFAULT,
			diagnostics: []
		};
		if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {
			ok: false,
			diagnostics: [invalidSection(raw)]
		};
		let check;
		let rawAgents;
		try {
			check = raw.check;
			rawAgents = raw.agents;
		} catch {
			return {
				ok: false,
				diagnostics: [invalidSection(raw)]
			};
		}
		if (check !== void 0 && typeof check !== "boolean") return {
			ok: false,
			diagnostics: [invalidCheck(check)]
		};
		const agents = validateAgents(rawAgents);
		if (!agents.ok) return {
			ok: false,
			diagnostics: [agents.diagnostic]
		};
		return {
			ok: true,
			value: {
				check: check ?? true,
				agents: agents.agents,
				agentsConfigured: rawAgents !== void 0
			},
			diagnostics: []
		};
	}
});
//#endregion
//#region src/commands/auth/agent-setup-tip.ts
/**
* The post-login skills tip. Login is the moment a developer sets a
* project up, so it points at `skills sync` when the project's synced
* agent skills do not match its installed Prisma packages. Silent in
* CI, in a directory with no skill-bearing Prisma packages, when the
* copies are current, and when the check is opted out.
*/
const SKILLS_SYNC_ARGS = ["skills", "sync"];
async function resolveAgentSetupTipCommand(ctx) {
	if (ctx.env.CI) return null;
	try {
		const config = await readProjectSkillsConfig(ctx.cwd);
		if (config !== null && !config.check) return null;
		const status = await readSkillsStatus(ctx.cwd, {
			orphans: false,
			agents: config?.agents
		});
		if (status.packages.length === 0 || status.upToDate || status.checkDisabled) return null;
		return await resolvePrismaCliPackageCommand({
			cwd: ctx.cwd,
			signal: ctx.signal,
			args: SKILLS_SYNC_ARGS
		});
	} catch {
		return null;
	}
}
//#endregion
//#region src/commands/auth/credential-card.ts
const ENVIRONMENT_CREDENTIAL_NOTICE = `${SERVICE_TOKEN_ENV_VAR} supplies the credential in force; unset it to use your stored workspace sessions.`;
/** The card rows for the active credential, or the signed-out row when
*  there is none. A credential nothing names — an environment token
*  whose claims carry no workspace — has no workspace row at all. */
function credentialFieldRows(spec) {
	const credential = spec.credential;
	if (credential === null) return [{
		label: "status",
		value: "signed out"
	}];
	const rows = [{
		label: "status",
		value: "signed in"
	}];
	if (spec.identity?.email !== void 0) rows.push({
		label: "user",
		value: spec.identity.email
	});
	if (credential.workspaceId !== void 0) rows.push({
		label: "workspace",
		value: credential.workspaceName ?? credential.workspaceId
	});
	if (credential.origin.source === "environment") rows.push({
		label: "environment variable",
		value: SERVICE_TOKEN_ENV_VAR
	});
	return rows;
}
//#endregion
//#region src/commands/auth/session-ref.ts
/**
* Command-side resolution of a user-typed workspace reference against
* the sessions the credential manager holds. The manager resolves no
* user input: the commands match the ref and pass the matched session's
* workspace id. This is also where a workspace the user never had is
* caught, which is why removal being idempotent still leaves a mistyped
* ref with a useful error.
*/
/** Exact workspace id first, then case-insensitive workspace name. */
function resolveSessionRef(sessions, ref) {
	const wanted = ref.trim();
	const byId = sessions.find((session) => session.workspaceId === wanted);
	if (byId !== void 0) return {
		kind: "matched",
		session: byId
	};
	const byName = sessions.filter((session) => session.workspaceName !== void 0 && session.workspaceName.toLowerCase() === wanted.toLowerCase());
	if (byName.length === 1) return {
		kind: "matched",
		session: byName[0]
	};
	if (byName.length > 1) return {
		kind: "ambiguous",
		matches: byName
	};
	return { kind: "no-match" };
}
function ambiguousSessionRefError(ref, matches) {
	return new CliStructuredError("AUTH.WORKSPACE_AMBIGUOUS", `More than one workspace session is named '${ref}'.`, {
		why: `Matching workspaces: ${matches.map((match) => match.workspaceId).join(", ")}.`,
		meta: { workspaceIds: matches.map((match) => match.workspaceId) },
		nextActions: [{
			kind: "run-command",
			label: "List your workspace sessions and pass a workspace id",
			command: `${CLI_NAME} auth workspace list`
		}]
	});
}
/**
* Resolves the ref or throws the structured error for its failure —
* the ruled "no session for X" error for a ref that matches nothing.
*/
function requireSession(sessions, ref) {
	const resolution = resolveSessionRef(sessions, ref);
	if (resolution.kind === "ambiguous") throw ambiguousSessionRefError(ref, resolution.matches);
	if (resolution.kind === "no-match") throw noSessionForWorkspaceError(ref);
	return resolution.session;
}
/** How a session is named to users: its workspace name, or its id
*  when no name was ever fetched. */
function sessionLabel(session) {
	return session.workspaceName ?? session.workspaceId;
}
//#endregion
//#region src/commands/auth/login.ts
const TITLE$14 = "Starting an authenticated CLI session.";
const LOGIN_STEP = "Sign in via your browser";
/** The minted credential names no workspace, so no session can be
*  keyed by one. */
function loginWorkspaceUnknownError() {
	return new CliStructuredError("AUTH.LOGIN_WORKSPACE_UNKNOWN", "Sign-in produced a credential that names no workspace.", {
		why: "A workspace session is keyed by the credential's workspace_id claim, and this credential carries none.",
		nextActions: [{
			kind: "run-command",
			label: "Sign in again and pick a workspace in the browser",
			command: `${CLI_NAME} auth login`
		}]
	});
}
function nextActionsFor$1(agentSetupTipCommand) {
	return [
		{
			kind: "run-command",
			label: "Show the signed-in identity",
			command: `${CLI_NAME} auth whoami`
		},
		{
			kind: "run-command",
			label: "List projects",
			command: `${CLI_NAME} project list`
		},
		...agentSetupTipCommand === null ? [] : [{
			kind: "run-command",
			label: "Install Prisma skills for this project",
			command: agentSetupTipCommand
		}]
	];
}
function presentationsFor$2(spec, result) {
	const rows = [{
		label: "status",
		value: "signed in"
	}, {
		label: "workspace",
		value: sessionLabel(spec.session)
	}];
	return {
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$14
			},
			{
				kind: "fields",
				rows
			},
			...spec.environmentCredentialInForce ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : [],
			...spec.agentSetupTipCommand === null ? [] : [{
				kind: "summary",
				status: "info",
				text: `Install Prisma skills for this project with ${spec.agentSetupTipCommand}.`
			}]
		],
		stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
		next: () => nextActionsFor$1(spec.agentSetupTipCommand)
	};
}
const authLoginCommand = defineCommand({
	managesCredentials: true,
	help: {
		summary: "Log in to your Prisma platform account",
		examples: ["auth login"]
	},
	handler: async (_args, ctx) => {
		const environmentSession = environmentCredentialInForce(ctx.env);
		ctx.report({
			kind: "step-started",
			step: LOGIN_STEP
		});
		let session;
		try {
			const credential = await performLogin(ctx.env, ctx.signal, { onVerificationUrl: (url) => ctx.report({
				kind: "endpoint",
				name: "verification",
				url
			}) });
			const workspaceId = credentialWorkspaceId(credential.token);
			if (workspaceId === void 0) throw loginWorkspaceUnknownError();
			session = await ctx.credentialManager.createSession(credential, workspaceId);
		} catch (error) {
			ctx.report({
				kind: "step-finished",
				step: LOGIN_STEP,
				outcome: "failed"
			});
			throw error;
		}
		ctx.report({
			kind: "step-finished",
			step: LOGIN_STEP,
			outcome: "ok"
		});
		const agentSetupTipCommand = await resolveAgentSetupTipCommand(ctx);
		const result = {
			workspace: {
				id: session.workspaceId,
				name: session.workspaceName ?? null
			},
			environmentCredentialInForce: environmentSession
		};
		return ok(ctx.present({ data: result }, presentationsFor$2({
			session,
			environmentCredentialInForce: environmentSession,
			agentSetupTipCommand
		}, result)));
	}
});
//#endregion
//#region src/commands/auth/logout.ts
const SIGN_IN$1 = {
	kind: "run-command",
	label: "Sign in",
	command: `${CLI_NAME} auth login`
};
function presentationsFor$1(result, environmentInForce) {
	const summary = result.endedCount === 0 ? "No workspace sessions to end." : `Ended ${result.endedCount} workspace ${result.endedCount === 1 ? "session" : "sessions"}.`;
	const rows = [{
		label: "ended",
		value: String(result.endedCount)
	}];
	return {
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: "Clearing your stored workspace sessions."
			},
			{
				kind: "fields",
				rows
			},
			{
				kind: "summary",
				status: "ok",
				text: summary
			},
			...environmentInForce ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : []
		],
		stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
		next: () => [SIGN_IN$1]
	};
}
const authLogoutCommand = defineCommand({
	managesCredentials: true,
	help: {
		summary: "Clear stored authentication credentials",
		examples: ["auth logout"]
	},
	handler: async (_args, ctx) => {
		const stored = await ctx.credentialManager.sessions();
		await ctx.credentialManager.endAllSessions();
		const result = {
			endedCount: stored.sessions.length,
			workspaceIds: stored.sessions.map((session) => session.workspaceId)
		};
		return ok(ctx.present({ data: result }, presentationsFor$1(result, environmentCredentialInForce(ctx.env))));
	}
});
//#endregion
//#region src/commands/auth/whoami.ts
const TITLE$13 = "Showing the active authenticated identity.";
const SIGN_IN = {
	kind: "run-command",
	label: "Sign in",
	command: `${CLI_NAME} auth login`
};
/** whoami answers from the credential's own claims, so the lookup is
*  worth a moment and no more. ctx.signal only fires on Ctrl-C, and
*  nothing else bounds a request: a host that accepts the connection
*  and never answers would otherwise hold the command for minutes. */
const ENRICHMENT_TIMEOUT_MS = 3e3;
/** Best-effort online enrichment: whoami works offline, so any failure
*  leaves the identity as whatever the credential's own claims said. */
async function fetchedIdentity(api, signal) {
	const bounded = AbortSignal.any([signal, AbortSignal.timeout(ENRICHMENT_TIMEOUT_MS)]);
	try {
		const { data } = await api.GET("/v1/me", { signal: bounded });
		const user = data?.data?.user;
		if (!user) return;
		return {
			userId: user.id ?? void 0,
			email: user.email ?? void 0,
			name: user.name ?? void 0
		};
	} catch {
		signal.throwIfAborted();
		return;
	}
}
/**
* `/v1/me` wins field by field where it disagrees with the claims, and
* the claims are the offline fallback — but only while both describe
* the same person. The two are read at different moments, so another
* process replacing the session in between can leave the claims
* describing one user and the lookup another; filling a gap in one from
* the other would then invent a person who does not exist. When the two
* name different users, the lookup is taken whole.
*/
function mergedIdentity(claimed, fetched) {
	if (fetched === void 0) return claimed ?? null;
	if (claimed === void 0) return fetched;
	if (!(fetched.userId === void 0 || claimed.userId === void 0 || fetched.userId === claimed.userId)) return fetched;
	return {
		userId: fetched.userId ?? claimed.userId,
		email: fetched.email ?? claimed.email,
		name: fetched.name ?? claimed.name
	};
}
function presentationsFor(spec, result) {
	const rows = credentialFieldRows(spec);
	const fromEnvironment = spec.credential?.origin.source === "environment";
	return {
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$13
			},
			{
				kind: "fields",
				rows
			},
			...fromEnvironment ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : []
		],
		stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
		next: () => spec.credential === null ? [SIGN_IN] : []
	};
}
const authWhoamiCommand = defineCommand({
	help: {
		summary: "Show the authenticated user and accessible workspace",
		examples: ["auth whoami", "auth whoami --json"]
	},
	handler: async (_args, ctx) => {
		const credential = await ctx.activeCredential();
		const identity = credential === null ? null : mergedIdentity(credential.identity, await fetchedIdentity(ctx.api, ctx.signal));
		const result = {
			authenticated: credential !== null,
			workspace: credential === null || credential.workspaceId === void 0 ? null : {
				id: credential.workspaceId,
				name: credential.workspaceName ?? null
			},
			user: identity === null ? null : {
				id: identity.userId ?? null,
				email: identity.email ?? null,
				name: identity.name ?? null
			},
			source: credential?.origin.source ?? null,
			expiresAt: credential?.expiresAt?.toISOString() ?? null
		};
		return ok(ctx.present({ data: result }, presentationsFor({
			credential,
			identity
		}, result)));
	}
});
//#endregion
//#region src/commands/auth/workspace-list.ts
/** The `auth workspace list` command. */
const LOGIN_NEXT_ACTION = {
	kind: "run-command",
	label: "Sign in",
	command: `${CLI_NAME} auth login`
};
function serializeWorkspaceList(result) {
	return {
		context: {
			environmentCredentialInForce: result.environmentCredentialInForce,
			currentWorkspaceId: result.selectedWorkspaceId ?? null
		},
		items: result.sessions.map((session) => ({
			workspaceId: session.workspaceId,
			workspaceName: session.workspaceName ?? null,
			current: session.workspaceId === result.selectedWorkspaceId,
			expiresAt: session.expiresAt?.toISOString() ?? null
		})),
		count: result.sessions.length
	};
}
function listPresentations$9(result) {
	const columns = [
		"name",
		"id",
		"status"
	];
	const rows = result.sessions.map((session) => [
		sessionLabel(session),
		session.workspaceId,
		session.workspaceId === result.selectedWorkspaceId ? "current" : ""
	]);
	return {
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: "Listing your workspace sessions on this machine."
			},
			...result.environmentCredentialInForce ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : [],
			...result.sessions.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No workspace sessions found."
			}] : [{
				kind: "table",
				columns,
				rows
			}]
		],
		stdout: () => rows.map((row) => row.join("  ").trimEnd()),
		json: () => serializeWorkspaceList(result),
		next: () => result.sessions.length === 0 ? [LOGIN_NEXT_ACTION] : []
	};
}
const authWorkspaceListCommand = defineCommand({
	managesCredentials: true,
	help: {
		summary: "List your workspace sessions",
		examples: ["auth workspace list", "auth workspace list --json"]
	},
	handler: async (_args, ctx) => {
		const stored = await ctx.credentialManager.sessions();
		const result = {
			sessions: stored.sessions,
			selectedWorkspaceId: stored.selectedWorkspaceId,
			environmentCredentialInForce: environmentCredentialInForce(ctx.env)
		};
		return ok(ctx.present({ data: result }, listPresentations$9(result)));
	}
});
//#endregion
//#region src/commands/auth/workspace-logout.ts
/** The `auth workspace logout` command: ends one workspace session. */
function logoutPresentations(spec, result) {
	const rows = [{
		label: "workspace",
		value: spec.label
	}];
	return {
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: "Ending a workspace session."
			},
			{
				kind: "fields",
				rows
			},
			{
				kind: "summary",
				status: "ok",
				text: spec.wasSelected ? "Ended the current workspace session; no replacement was selected." : "Ended the workspace session."
			},
			...spec.environmentCredentialInForce ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : []
		],
		stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
		next: () => [{
			kind: "run-command",
			label: "List your workspace sessions",
			command: `${CLI_NAME} auth workspace list`
		}, ...spec.wasSelected ? [{
			kind: "run-command",
			label: "Make another session current",
			command: `${CLI_NAME} auth workspace use <id>`
		}] : []]
	};
}
const authWorkspaceLogoutCommand = defineCommand({
	managesCredentials: true,
	args: { positionals: { workspace: positional.string({
		brief: "Workspace id or name",
		placeholder: "id-or-name"
	}) } },
	help: {
		summary: "End one workspace session",
		examples: ["auth workspace logout my-workspace"]
	},
	handler: async (args, ctx) => {
		const stored = await ctx.credentialManager.sessions();
		const session = requireSession(stored.sessions, args.positionals.workspace);
		const wasSelected = session.workspaceId === stored.selectedWorkspaceId;
		await ctx.credentialManager.endSession(session.workspaceId);
		const result = {
			workspace: {
				id: session.workspaceId,
				name: session.workspaceName ?? null
			},
			wasSelected
		};
		return ok(ctx.present({ data: result }, logoutPresentations({
			label: sessionLabel(session),
			wasSelected,
			environmentCredentialInForce: environmentCredentialInForce(ctx.env)
		}, result)));
	}
});
//#endregion
//#region src/commands/auth/workspace-use.ts
/** The `auth workspace use` command: it SELECTS among the sessions you
*  have — it never creates one, and never opens a browser. */
function noWorkspaceSessionsError() {
	return new CliStructuredError("AUTH.NO_WORKSPACE_SESSIONS", "You have no workspace sessions to select from.", { nextActions: [{
		kind: "run-command",
		label: "Sign in and pick a workspace in the browser",
		command: `${CLI_NAME} auth login`
	}] });
}
function usePresentations(spec, result) {
	const rows = [...spec.previous === void 0 ? [] : [{
		label: "previous",
		value: sessionLabel(spec.previous)
	}], {
		label: "workspace",
		value: sessionLabel(spec.session)
	}];
	return {
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: "Switching the current workspace session."
			},
			{
				kind: "fields",
				rows
			},
			{
				kind: "summary",
				status: "ok",
				text: "Current workspace session updated."
			},
			...spec.environmentCredentialInForce ? [{
				kind: "summary",
				status: "info",
				text: ENVIRONMENT_CREDENTIAL_NOTICE
			}] : []
		],
		stdout: () => rows.map((row) => `${row.label}: ${row.value}`),
		next: () => [{
			kind: "run-command",
			label: "Show the signed-in identity",
			command: `${CLI_NAME} auth whoami`
		}, {
			kind: "run-command",
			label: "List projects",
			command: `${CLI_NAME} project list`
		}]
	};
}
const authWorkspaceUseCommand = defineCommand({
	managesCredentials: true,
	args: { positionals: { workspace: positional.optionalString({
		brief: "Workspace id or name",
		placeholder: "id-or-name"
	}) } },
	help: {
		summary: "Make one of your workspace sessions current",
		examples: ["auth workspace use", "auth workspace use my-workspace"]
	},
	handler: async (args, ctx) => {
		const stored = await ctx.credentialManager.sessions();
		if (stored.sessions.length === 0) throw noWorkspaceSessionsError();
		const ref = args.positionals.workspace?.trim();
		const chosen = ref ? requireSession(stored.sessions, ref) : await promptForSession(stored, ctx.prompt.select);
		const previous = stored.sessions.find((session) => session.workspaceId === stored.selectedWorkspaceId);
		const session = await ctx.credentialManager.selectSession(chosen.workspaceId);
		const result = {
			workspace: {
				id: session.workspaceId,
				name: session.workspaceName ?? null
			},
			previousWorkspaceId: previous?.workspaceId ?? null
		};
		return ok(ctx.present({ data: result }, usePresentations({
			session,
			previous,
			environmentCredentialInForce: environmentCredentialInForce(ctx.env)
		}, result)));
	}
});
async function promptForSession(stored, select) {
	if (stored.sessions.length === 1) return stored.sessions[0];
	const workspaceId = await select("Select a workspace", stored.sessions.map((session) => ({
		value: session.workspaceId,
		label: `${sessionLabel(session)} (${session.workspaceId})${session.workspaceId === stored.selectedWorkspaceId ? " current" : ""}`
	})));
	return requireSession(stored.sessions, workspaceId);
}
//#endregion
//#region src/controllers/branch.ts
function sortBranches(branches) {
	return branches.slice().sort((left, right) => {
		const leftRank = branchOrder(left);
		const rightRank = branchOrder(right);
		if (leftRank !== rightRank) return leftRank - rightRank;
		return left.name.localeCompare(right.name);
	});
}
function branchOrder(branch) {
	return branch.role === "production" ? 0 : 1;
}
async function listBranches$1(client, projectId, signal) {
	const collected = [];
	let cursor;
	while (true) {
		const query = {};
		if (cursor !== void 0) query.cursor = cursor;
		const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
			params: {
				path: { projectId },
				query
			},
			signal
		});
		if (error || !data) throw branchApiError("Failed to list branches", response, error);
		collected.push(...data.data);
		if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
		cursor = data.pagination.nextCursor;
	}
	return collected;
}
function toBranchSummary(branch) {
	return {
		id: branch.id,
		name: branch.gitName,
		role: branch.role,
		envMap: branch.role
	};
}
function branchApiError(summary, response, error) {
	const status = response?.status ?? 0;
	const apiCode = error?.error?.code;
	return new CliStructuredError("BRANCH.API_ERROR", summary, {
		why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
		...status || apiCode !== void 0 ? { meta: {
			...status ? { status } : {},
			...apiCode !== void 0 ? { apiCode } : {}
		} } : {},
		nextActions: [{
			kind: "user-choice",
			label: error?.error?.hint ?? "Re-run with --log-level verbose for the underlying API response details."
		}]
	});
}
//#endregion
//#region src/lib/project/prisma-dir.ts
/**
* Walks up from cwd to the nearest directory containing a `.prisma/`
* directory and returns that directory, or null when no ancestor has
* one. Pure filesystem check — no config file is read or evaluated.
* Nearest wins by design: a nested directory deliberately linked to a
* different project beats the repo root.
*/
async function findNearestPrismaDir(cwd) {
	let dir = path.resolve(cwd);
	for (;;) {
		if (await isDirectory(path.join(dir, ".prisma"))) return dir;
		const parent = path.dirname(dir);
		if (parent === dir) return null;
		dir = parent;
	}
}
async function isDirectory(candidate) {
	try {
		return (await stat(candidate)).isDirectory();
	} catch {
		return false;
	}
}
//#endregion
//#region src/lib/project/local-pin.ts
const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
var LocalResolutionPinInvalidJsonError = class extends TaggedError("LocalResolutionPinInvalidJsonError")() {
	constructor(cause) {
		super({
			message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} contains invalid JSON.`,
			cause,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinInvalidShapeError = class extends TaggedError("LocalResolutionPinInvalidShapeError")() {
	constructor() {
		super({
			message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} must contain workspaceId and projectId string fields only.`,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolutionPinReadAbortedError")() {
	constructor(cause) {
		super({
			message: `Reading ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
			cause,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinSerializationError = class extends TaggedError("LocalResolutionPinSerializationError")() {
	constructor(cause) {
		super({
			message: `Could not serialize ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
			cause,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinWriteAbortedError = class extends TaggedError("LocalResolutionPinWriteAbortedError")() {
	constructor(cause) {
		super({
			message: `Writing ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
			cause,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinWriteFailedError = class extends TaggedError("LocalResolutionPinWriteFailedError")() {
	constructor(operation, cause) {
		super({
			message: `Could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
			cause,
			operation,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalResolutionPinGitignoreUpdateAbortedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateAbortedError")() {
	constructor(cause) {
		super({
			message: "Updating .gitignore for the local Project binding was aborted.",
			cause,
			gitignorePath: ".gitignore"
		});
	}
};
var LocalResolutionPinGitignoreUpdateFailedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateFailedError")() {
	constructor(operation, cause) {
		super({
			message: "Could not update .gitignore for the local Project binding.",
			cause,
			operation,
			gitignorePath: ".gitignore"
		});
	}
};
/** Reads the pin at the nearest ancestor with a `.prisma/` directory;
*  without one anywhere up the tree, reads at cwd and finds nothing.
*  Writes never walk — only discovery does. */
async function readLocalResolutionPin(cwd, signal) {
	return Result.gen(async function* () {
		yield* ensureLocalResolutionPinReadNotAborted(signal);
		const directory = await findNearestPrismaDir(cwd) ?? cwd;
		const file = yield* Result.await(readLocalResolutionPinFile(directory, signal));
		if (file.kind === "missing") return Result.ok({ kind: "missing" });
		const parsed = yield* parseLocalResolutionPin(file.raw);
		if (!isLocalResolutionPin(parsed)) return Result.err(new LocalResolutionPinInvalidShapeError());
		return Result.ok({
			kind: "present",
			pin: parsed,
			directory
		});
	});
}
function ensureLocalResolutionPinReadNotAborted(signal) {
	return Result.try({
		try: () => signal?.throwIfAborted(),
		catch: (cause) => new LocalResolutionPinReadAbortedError(cause)
	});
}
async function readLocalResolutionPinFile(cwd, signal) {
	const readResult = await Result.tryPromise({
		try: () => readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
			encoding: "utf8",
			signal
		}),
		catch: (cause) => signal?.aborted ? new LocalResolutionPinReadAbortedError(cause) : new UnhandledException({ cause })
	});
	if (readResult.isErr()) {
		if (readResult.error instanceof UnhandledException && readResult.error.cause.code === "ENOENT") return Result.ok({ kind: "missing" });
		return Result.err(readResult.error);
	}
	return Result.ok({
		kind: "present",
		raw: readResult.value
	});
}
function parseLocalResolutionPin(raw) {
	return Result.try({
		try: () => JSON.parse(raw),
		catch: (cause) => cause instanceof SyntaxError ? new LocalResolutionPinInvalidJsonError(cause) : new UnhandledException({ cause })
	});
}
async function writeLocalResolutionPin(cwd, pin, signal) {
	return Result.gen(async function* () {
		const prismaDir = path.join(cwd, ".prisma");
		yield* ensureLocalResolutionPinWriteNotAborted(signal);
		yield* Result.await(writeLocalResolutionPinBoundary(() => mkdir(prismaDir, { recursive: true }), "create-directory", signal));
		const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
		const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
		const serialized = yield* serializeLocalResolutionPin(pin);
		yield* Result.await(writeLocalResolutionPinBoundary(() => writeFile(tmpPath, serialized, {
			encoding: "utf8",
			signal
		}), "write-temp-file", signal));
		yield* ensureLocalResolutionPinWriteNotAborted(signal);
		yield* Result.await(writeLocalResolutionPinBoundary(() => rename(tmpPath, pinPath), "rename-temp-file", signal));
		return Result.ok(void 0);
	});
}
async function ensureLocalResolutionPinGitignore(cwd, signal) {
	const gitignorePath = path.join(cwd, ".gitignore");
	let existing = null;
	const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted(signal);
	if (notAborted.isErr()) return Result.err(notAborted.error);
	const existingResult = await Result.tryPromise({
		try: () => readFile(gitignorePath, {
			encoding: "utf8",
			signal
		}),
		catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("read", cause)
	});
	if (existingResult.isErr()) if (existingResult.error instanceof LocalResolutionPinGitignoreUpdateFailedError && existingResult.error.cause.code === "ENOENT") existing = null;
	else return Result.err(existingResult.error);
	else existing = existingResult.value;
	if (existing === null) return writeLocalResolutionPinGitignore(gitignorePath, ".prisma/\n", signal);
	if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return Result.ok(void 0);
	return writeLocalResolutionPinGitignore(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, signal);
}
function ensureLocalResolutionPinWriteNotAborted(signal) {
	return Result.try({
		try: () => signal?.throwIfAborted(),
		catch: (cause) => new LocalResolutionPinWriteAbortedError(cause)
	});
}
function serializeLocalResolutionPin(pin) {
	return Result.try({
		try: () => `${JSON.stringify(pin, null, 2)}\n`,
		catch: (cause) => new LocalResolutionPinSerializationError(cause)
	});
}
function writeLocalResolutionPinBoundary(run, operation, signal) {
	return Result.tryPromise({
		try: async () => {
			await run();
		},
		catch: (cause) => signal?.aborted ? new LocalResolutionPinWriteAbortedError(cause) : new LocalResolutionPinWriteFailedError(operation, cause)
	});
}
function ensureLocalResolutionPinGitignoreUpdateNotAborted(signal) {
	return Result.try({
		try: () => signal?.throwIfAborted(),
		catch: (cause) => new LocalResolutionPinGitignoreUpdateAbortedError(cause)
	});
}
function writeLocalResolutionPinGitignore(gitignorePath, contents, signal) {
	return Result.tryPromise({
		try: () => writeFile(gitignorePath, contents, {
			encoding: "utf8",
			signal
		}),
		catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
	});
}
function isLocalResolutionPin(value) {
	if (!value || typeof value !== "object") return false;
	const keys = Object.keys(value);
	if (keys.length !== 2 || !keys.includes("workspaceId") || !keys.includes("projectId")) return false;
	const candidate = value;
	return typeof candidate.workspaceId === "string" && candidate.workspaceId.trim().length > 0 && typeof candidate.projectId === "string" && candidate.projectId.trim().length > 0;
}
//#endregion
//#region src/lib/project/provider.ts
function createManagementProjectProvider(client) {
	return {
		async renameProject(options) {
			const result = await client.PATCH("/v1/projects/{id}", {
				params: { path: { id: options.projectId } },
				body: { name: options.name },
				signal: options.signal
			});
			const status = result.response?.status ?? 0;
			if (status === 400 || status === 422) throw projectRenameFailedError(options.name, result.error);
			if (result.error || !result.data) throw projectApiError("Failed to rename project", result.response, result.error);
			const project = result.data.data;
			return {
				id: project.id,
				name: project.name,
				...project.url ? { url: project.url } : {}
			};
		},
		async removeProject(options) {
			const result = await client.DELETE("/v1/projects/{id}", {
				params: { path: { id: options.projectId } },
				signal: options.signal
			});
			if (result.response?.status === 400) throw projectDeleteBlockedError(options.projectId, result.error);
			if (result.error) throw projectApiError("Failed to delete project", result.response, result.error);
		},
		async transferProject(options) {
			const result = await client.POST("/v1/projects/{id}/transfer", {
				params: { path: { id: options.projectId } },
				body: { recipientAccessToken: options.recipientAccessToken },
				signal: options.signal
			});
			if (result.response?.status === 400) throw projectTransferRejectedError(options.projectId, result.error);
			if (result.error) throw projectApiError("Failed to transfer project", result.response, result.error);
		}
	};
}
function userChoice$6(label) {
	return {
		kind: "user-choice",
		label
	};
}
function projectRenameFailedError(name, error) {
	return new CliStructuredError("PROJECT.RENAME_FAILED", "Project rename failed", {
		why: error?.error?.message ?? `The platform rejected the name "${name}".`,
		nextActions: [userChoice$6(error?.error?.hint ?? "Pass a different project name and retry the rename.")]
	});
}
function projectDeleteBlockedError(projectId, error) {
	const deleteServicesCommand = formatPrismaCliCommand([
		"service",
		"delete",
		"--service",
		"<name>"
	]);
	return new CliStructuredError("PROJECT.DELETE_BLOCKED", "Project cannot be deleted yet", {
		why: error?.error?.message ?? `Project "${projectId}" still has active deployments.`,
		nextActions: [userChoice$6("Delete the project's services first, then retry the deletion."), {
			kind: "run-command",
			label: deleteServicesCommand,
			command: deleteServicesCommand
		}]
	});
}
function projectTransferRejectedError(projectId, error) {
	return new CliStructuredError("PROJECT.TRANSFER_REJECTED", "Project transfer was rejected", {
		why: error?.error?.message ?? `The platform rejected the transfer of project "${projectId}", for example because the recipient token is invalid or expired.`,
		nextActions: [userChoice$6("Check the recipient workspace session or token and retry the transfer.")]
	});
}
function projectApiError(summary, response, error) {
	const status = response?.status ?? 0;
	const apiCode = error?.error?.code;
	return new CliStructuredError("PROJECT.API_ERROR", summary, {
		why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
		...apiCode !== void 0 || status ? { meta: {
			...status ? { status } : {},
			...apiCode !== void 0 ? { apiCode } : {}
		} } : {},
		nextActions: [userChoice$6(error?.error?.hint ?? "Re-run with --log-level verbose for the underlying API response details.")]
	});
}
//#endregion
//#region src/command-arguments.ts
function formatCommandArgument(value) {
	return /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") ? value : `'${value.replace(/'/g, "'\\''")}'`;
}
//#endregion
//#region src/lib/project/resolution.ts
var ProjectNotFoundError = class extends TaggedError("ProjectNotFoundError")() {
	constructor(projectRef, workspace) {
		super({
			message: `Project "${projectRef}" was not found in workspace "${workspace.name}".`,
			projectRef,
			workspace
		});
	}
};
var ProjectAmbiguousError = class extends TaggedError("ProjectAmbiguousError")() {
	constructor(projectRef, matches) {
		super({
			message: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
			projectRef,
			matches
		});
	}
};
var LocalStateStaleError = class extends TaggedError("LocalStateStaleError")() {
	constructor() {
		super({
			message: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
		});
	}
};
var LocalProjectWorkspaceMismatchError = class extends TaggedError("LocalProjectWorkspaceMismatchError")() {
	constructor(options) {
		super({
			message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but the active workspace is "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
			pinnedWorkspaceId: options.pinnedWorkspaceId,
			pinnedProjectId: options.pinnedProjectId,
			activeWorkspace: options.activeWorkspace
		});
	}
};
var ProjectSetupRequiredError = class extends TaggedError("ProjectSetupRequiredError")() {
	constructor(options) {
		const commandLabel = options.commandName ? `prisma ${options.commandName}` : "this command";
		super({
			message: `This directory is not linked to a Prisma Project, and ${commandLabel} will not choose one from package or directory names.`,
			commandName: options.commandName,
			suggestion: options.suggestion
		});
	}
};
async function resolveProjectTarget(options) {
	return Result.gen(async function* () {
		const localPin = yield* Result.await(readImplicitLocalPin(options));
		const projects = await options.listProjects();
		const target = yield* Result.await(resolveBoundProjectTarget(options, projects, { localPin }));
		if (target) return Result.ok(target);
		return Result.err(await projectSetupRequiredError({
			cwd: options.context.runtime.cwd,
			projects,
			commandName: options.commandName,
			signal: options.context.runtime.signal
		}));
	});
}
async function inspectProjectBinding(options) {
	return Result.gen(async function* () {
		const localPin = yield* Result.await(readImplicitLocalPin(options));
		const projects = await options.listProjects();
		const target = yield* Result.await(resolveBoundProjectTarget(options, projects, { localPin }));
		if (target) return Result.ok(target);
		return Result.ok({
			workspace: options.workspace,
			project: null,
			localBinding: { status: "not-linked" },
			resolution: { projectSource: "unbound" },
			...await buildProjectSetupSuggestion({
				cwd: options.context.runtime.cwd,
				projects,
				commandName: options.commandName ?? "project show",
				signal: options.context.runtime.signal
			})
		});
	});
}
function runCommand$5(command, reason) {
	return {
		kind: "run-command",
		label: command,
		command,
		...reason === void 0 ? {} : { reason }
	};
}
function userChoice$5(label) {
	return {
		kind: "user-choice",
		label
	};
}
function projectNotFoundError$1(projectRef, workspace) {
	return projectResolutionErrorToStructured(new ProjectNotFoundError(projectRef, workspace));
}
function projectNotFoundStructuredError(projectRef, workspace) {
	return new CliStructuredError("PROJECT.NOT_FOUND", "Project not found", {
		why: `The project "${projectRef}" does not exist in workspace "${workspace.name}" or is not accessible.`,
		nextActions: [userChoice$5("Pass a project id or name from prisma project list."), runCommand$5("prisma project list")]
	});
}
function projectAmbiguousError(projectRef, matches) {
	return projectResolutionErrorToStructured(new ProjectAmbiguousError(projectRef, matches));
}
function projectAmbiguousStructuredError(projectRef, matches) {
	const firstMatch = matches[0];
	const nextActions = [userChoice$5("Pass --project <id-or-name> to choose the project explicitly."), runCommand$5("prisma project list")];
	if (firstMatch) nextActions.push(runCommand$5(`prisma project link ${firstMatch.id}`));
	return new CliStructuredError("PROJECT.AMBIGUOUS", "Project resolution is ambiguous", {
		why: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
		meta: { matches: matches.map((project) => ({
			id: project.id,
			name: project.name
		})) },
		nextActions
	});
}
function localStateStaleStructuredError() {
	return new CliStructuredError("PROJECT.LOCAL_STATE_STALE", "Local project binding is stale", {
		why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
		meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
		nextActions: [
			userChoice$5(`Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`),
			runCommand$5("prisma project list"),
			runCommand$5("prisma project link <id-or-name>")
		]
	});
}
function localProjectWorkspaceMismatchStructuredError(options) {
	return new CliStructuredError("PROJECT.LOCAL_WORKSPACE_MISMATCH", "Project link uses another workspace", {
		why: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but your current CLI session is workspace "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
		meta: {
			pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
			pinnedWorkspaceId: options.pinnedWorkspaceId,
			pinnedProjectId: options.pinnedProjectId,
			activeWorkspaceId: options.activeWorkspace.id,
			activeWorkspaceName: options.activeWorkspace.name
		},
		nextActions: [
			userChoice$5("Switch to the linked workspace, or relink this directory to a project in the current workspace."),
			runCommand$5(`prisma auth workspace use ${options.pinnedWorkspaceId}`),
			runCommand$5("prisma project list"),
			runCommand$5("prisma project link <id-or-name>")
		]
	});
}
/**
* Converts expected project-resolution variants to the structured errors
* a command boundary raises — the codes here are the registered PROJECT.*
* codes, assigned at origin.
* `LocalResolutionPinReadAbortedError` and `UnhandledException` intentionally
* propagate as exceptions; callers such as `resolveProjectShowInRealMode`
* throw this helper's result, so passthrough variants should keep bubbling.
*/
function projectResolutionErrorToStructured(error) {
	return matchError(error, {
		ProjectNotFoundError: (error) => projectNotFoundStructuredError(error.projectRef, error.workspace),
		ProjectAmbiguousError: (error) => projectAmbiguousStructuredError(error.projectRef, error.matches),
		ProjectSetupRequiredError: (error) => projectSetupRequiredStructuredError(error),
		LocalStateStaleError: () => localStateStaleStructuredError(),
		LocalProjectWorkspaceMismatchError: (error) => localProjectWorkspaceMismatchStructuredError({
			pinnedWorkspaceId: error.pinnedWorkspaceId,
			pinnedProjectId: error.pinnedProjectId,
			activeWorkspace: error.activeWorkspace
		}),
		LocalResolutionPinReadAbortedError: (error) => {
			throw error;
		},
		UnhandledException: (error) => {
			throw error;
		}
	});
}
async function buildProjectSetupSuggestion(options) {
	const suggestedName = await inferTargetName(options.cwd, options.signal);
	const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary$1);
	return {
		suggestedProjectName: suggestedName.name,
		suggestedProjectNameSource: suggestedName.source,
		candidates,
		recoveryCommands: buildProjectRecoveryCommands(options.commandName)
	};
}
async function projectSetupRequiredError(options) {
	const suggestion = await buildProjectSetupSuggestion(options);
	return new ProjectSetupRequiredError({
		commandName: options.commandName,
		suggestion
	});
}
function projectSetupRequiredStructuredError(error) {
	const suggestion = error.suggestion;
	return new CliStructuredError("PROJECT.SETUP_REQUIRED", "Choose a Project before running this command", {
		why: error.message,
		meta: { ...suggestion },
		nextActions: buildProjectSetupNextActions({
			commandName: error.commandName,
			suggestedProjectName: suggestion.suggestedProjectName
		})
	});
}
function buildProjectSetupNextActions(options = {}) {
	const recoveryCommands = buildProjectRecoveryCommands(options.commandName);
	const linkCommand = recoveryCommands[0] ?? "prisma project link <id-or-name>";
	const retryCommand = options.retryCommand ?? recoveryCommands[1];
	const actions = [{
		kind: "user-choice",
		label: "Ask the user whether to link an existing Project or create a new one",
		commands: [
			"prisma project list",
			linkCommand,
			...retryCommand ? [retryCommand] : []
		],
		reason: options.reason ?? "This directory is not linked to a Prisma Project. Package and directory names are suggestions only, not a safe Project selection."
	}, {
		kind: "run-command",
		label: "Link the chosen Project",
		command: linkCommand,
		reason: "Linking writes the durable local Project binding for this directory."
	}];
	const createCommand = options.createCommand ?? (options.suggestedProjectName ? `prisma project create ${formatCommandArgument(options.suggestedProjectName)}` : void 0);
	if (createCommand) actions.push({
		kind: "run-command",
		label: "Create and link a new Project",
		command: createCommand,
		reason: "Use this when the user wants a new Prisma Project instead of an existing one."
	});
	if (options.commandName) actions.push({
		kind: "run-command",
		label: "Retry with an explicit Project",
		command: retryCommand ?? `prisma ${options.commandName} --project <id-or-name>`
	});
	return actions;
}
async function readPackageName(cwd, signal) {
	signal?.throwIfAborted();
	try {
		const raw = await readFile(path.join(cwd, "package.json"), {
			encoding: "utf8",
			signal
		});
		const parsed = JSON.parse(raw);
		if (!parsed || typeof parsed !== "object") return null;
		const packageName = "name" in parsed ? parsed.name : null;
		return typeof packageName === "string" && packageName.trim().length > 0 ? packageName.trim() : null;
	} catch (error) {
		if (error.code === "ENOENT") return null;
		if (error instanceof SyntaxError) return null;
		throw error;
	}
}
async function inferTargetName(cwd, signal) {
	const packageName = await readPackageName(cwd, signal);
	if (packageName && isValidInferredTargetName(packageName)) return {
		name: packageName,
		source: "package-name"
	};
	return {
		name: path.basename(cwd),
		source: "directory-name"
	};
}
const INFERRED_TARGET_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
function isValidInferredTargetName(value) {
	return INFERRED_TARGET_NAME.test(value);
}
function sortProjects(projects) {
	return projects.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
}
function resolveExplicitProject(projectRef, projects, workspace) {
	const byId = projects.filter((project) => project.id === projectRef);
	const matches = byId.length > 0 ? byId : projects.filter((project) => project.name === projectRef);
	if (matches.length === 1) return Result.ok(matches[0]);
	if (matches.length > 1) return Result.err(new ProjectAmbiguousError(projectRef, matches));
	return Result.err(new ProjectNotFoundError(projectRef, workspace));
}
function projectMatchesSuggestedName(project, suggestedName) {
	return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
}
async function resolveDurablePlatformMapping() {
	return null;
}
async function resolveBoundProjectTarget(options, projects, settings) {
	if (options.explicitProject) {
		const projectResult = resolveExplicitProject(options.explicitProject, projects, options.workspace);
		if (projectResult.isErr()) return Result.err(projectResult.error);
		return Result.ok(resolvedTarget(options.workspace, projectResult.value, "explicit", {
			targetName: options.explicitProject,
			targetNameSource: "explicit"
		}));
	}
	const localPin = settings.localPin;
	if (!localPin) return Result.ok(null);
	if (localPin.kind === "present") {
		if (!sameWorkspaceId(localPin.pin.workspaceId, options.workspace.id)) return Result.err(new LocalProjectWorkspaceMismatchError({
			pinnedWorkspaceId: localPin.pin.workspaceId,
			pinnedProjectId: localPin.pin.projectId,
			activeWorkspace: options.workspace
		}));
		const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
		if (!project) return Result.err(new LocalStateStaleError());
		return Result.ok(resolvedTarget(options.workspace, project, "local-pin", {
			targetName: project.name,
			targetNameSource: "local-pin"
		}));
	}
	const platformMapping = await resolveDurablePlatformMapping();
	if (platformMapping && sameWorkspaceId(platformMapping.workspace.id, options.workspace.id)) return Result.ok(resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
		targetName: platformMapping.name,
		targetNameSource: "platform-mapping"
	}));
	return Result.ok(null);
}
async function readImplicitLocalPin(options) {
	if (options.explicitProject) return Result.ok(null);
	const localPinResult = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
	if (localPinResult.isErr()) return Result.err(localPinReadErrorToProjectError(localPinResult.error));
	const localPin = localPinResult.value;
	if (localPin.kind === "present" && !sameWorkspaceId(localPin.pin.workspaceId, options.workspace.id)) return Result.err(new LocalProjectWorkspaceMismatchError({
		pinnedWorkspaceId: localPin.pin.workspaceId,
		pinnedProjectId: localPin.pin.projectId,
		activeWorkspace: options.workspace
	}));
	return Result.ok(localPin);
}
function localPinReadErrorToProjectError(error) {
	return matchError(error, {
		LocalResolutionPinInvalidJsonError: () => new LocalStateStaleError(),
		LocalResolutionPinInvalidShapeError: () => new LocalStateStaleError(),
		LocalResolutionPinReadAbortedError: (error) => error,
		UnhandledException: (error) => error
	});
}
function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
	return {
		workspace,
		project: toProjectSummary$1(project),
		resolution: {
			projectSource,
			...resolutionDetails
		}
	};
}
function buildProjectRecoveryCommands(commandName) {
	const commands = ["prisma project link <id-or-name>"];
	if (commandName) commands.push(`prisma ${commandName} --project <id-or-name>`);
	return commands;
}
function toProjectSummary$1(project) {
	return {
		id: project.id,
		name: project.name,
		...project.url ? { url: project.url } : {},
		...project.defaultRegion != null ? { defaultRegion: project.defaultRegion } : {}
	};
}
//#endregion
//#region src/controllers/project.ts
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
function runCommand$4(command) {
	return {
		kind: "run-command",
		label: command,
		command
	};
}
function userChoice$4(label) {
	return {
		kind: "user-choice",
		label
	};
}
/** A URL is not a command: putting one in `command` tells a consumer to
*  execute it. */
function openUrl(url) {
	return {
		kind: "open-url",
		label: url,
		url
	};
}
async function readProjectListLocalBinding(cwd, projects, signal) {
	const pinResult = await readLocalResolutionPin(cwd, signal);
	if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
	const pin = pinResult.value;
	if (pin.kind === "present") return projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
	return { status: "not-linked" };
}
function localPinReadErrorToInvalidLocalBinding(error) {
	return matchError(error, {
		LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
		LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
		LocalResolutionPinReadAbortedError: (error) => {
			throw error;
		},
		UnhandledException: (error) => {
			throw error;
		}
	});
}
function transferRecipientRequiredError(formatCommand) {
	return new CliStructuredError("PROJECT.TRANSFER_RECIPIENT_REQUIRED", "Transfer recipient required", {
		why: "Project transfer needs the receiving workspace.",
		nextActions: [
			userChoice$4("Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer."),
			runCommand$4(formatCommand([
				"auth",
				"workspace",
				"list"
			])),
			runCommand$4(formatCommand([
				"project",
				"transfer",
				"<project>",
				"--to-workspace",
				"<id-or-name>",
				"--confirm",
				"<project-id>"
			]))
		]
	});
}
function transferRecipientUnavailableError(formatCommand) {
	return new CliStructuredError("PROJECT.TRANSFER_RECIPIENT_UNAVAILABLE", "Local workspace sessions are unavailable", {
		why: `--to-workspace resolves locally stored OAuth sessions, but ${SERVICE_TOKEN_ENV_VAR} is set and service-token mode does not read them.`,
		nextActions: [userChoice$4("Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token."), runCommand$4(formatCommand([
			"project",
			"transfer",
			"<project>",
			"--recipient-token",
			"<token>",
			"--confirm",
			"<project-id>"
		]))]
	});
}
async function cleanupLocalPinForProject(context, projectId, hooks) {
	const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
	if (pinResult.isErr()) return false;
	const pin = pinResult.value;
	if (pin.kind !== "present" || pin.pin.projectId !== projectId) return false;
	try {
		await unlink(path.join(pin.directory, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
		return true;
	} catch {
		hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the deleted project but could not be deleted.`);
		return false;
	}
}
async function rewriteOrClearLocalPinForProject(context, projectId, recipientWorkspaceId, hooks) {
	const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
	if (pinResult.isErr()) return "none";
	const pin = pinResult.value;
	if (pin.kind !== "present" || pin.pin.projectId !== projectId) return "none";
	if (recipientWorkspaceId) {
		if ((await writeLocalResolutionPin(pin.directory, {
			workspaceId: recipientWorkspaceId,
			projectId
		}, context.runtime.signal)).isOk()) return "rewritten";
		hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be rewritten.`);
		return "none";
	}
	try {
		await unlink(path.join(pin.directory, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
		return "cleared";
	} catch {
		hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be cleared.`);
		return "none";
	}
}
/** The projects the API returns for the active credential, sorted.
*  Takes no workspace: the credential names one, and the API answers
*  within it. */
async function listRealWorkspaceProjects(client, signal) {
	const { data, error, response } = await client.GET("/v1/projects", { signal });
	if (error || !data) throw projectApiError("Failed to list projects", response, error);
	return sortProjects((data.data ?? []).map((project) => ({
		id: project.id,
		name: project.name,
		..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
		..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
		slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
		workspace: {
			id: project.workspace.id,
			name: project.workspace.name
		}
	})));
}
async function findRepositoryInInstallations(api, installations, repository, signal) {
	let inspectableInstallationCount = 0;
	for (const installation of installations) {
		if (installation.provider !== "github" || installation.suspended) continue;
		const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
		if (matchedRepository === "unavailable") continue;
		inspectableInstallationCount += 1;
		if (matchedRepository) return {
			match: {
				installation,
				repository: matchedRepository
			},
			inspectableInstallationCount
		};
	}
	return {
		match: null,
		inspectableInstallationCount
	};
}
function readPositiveIntegerEnv(value, fallback) {
	if (value === void 0) return fallback;
	const parsed = Number(value);
	return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}
async function listScmInstallations(api, workspaceId, signal) {
	const installations = [];
	let cursor;
	const seenCursors = /* @__PURE__ */ new Set();
	do {
		const { data, error, response } = await api.GET("/v1/scm-installations", {
			params: { query: {
				workspaceId,
				limit: 100,
				...cursor ? { cursor } : {}
			} },
			signal
		});
		if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
		installations.push(...data.data);
		cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
	} while (cursor);
	return installations;
}
async function findRepositoryInInstallation(api, installationId, repository, signal) {
	const expectedFullName = repository.fullName.toLowerCase();
	let cursor;
	const seenCursors = /* @__PURE__ */ new Set();
	do {
		const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
			params: {
				path: { installationId },
				query: {
					limit: 100,
					...cursor ? { cursor } : {}
				}
			},
			signal
		});
		if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
		const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
		if (matchedRepository) return matchedRepository;
		cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub repositories", response);
	} while (cursor);
	return null;
}
function readNextPaginationCursor(pagination, seenCursors, summary, response) {
	const nextCursor = pagination.hasMore && pagination.nextCursor ? pagination.nextCursor : void 0;
	if (!nextCursor) return;
	if (seenCursors.has(nextCursor)) throw repoConnectionApiError(summary, response, { error: { message: "Pagination cursor did not advance." } });
	seenCursors.add(nextCursor);
	return nextCursor;
}
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
	try {
		return await findRepositoryInInstallation(api, installationId, repository, signal);
	} catch (error) {
		if (signal.aborted) throw error;
		if (isUnavailableScmInstallationError(error)) return "unavailable";
		throw error;
	}
}
function isUnavailableScmInstallationError(error) {
	if (!CliStructuredError.is(error) || error.code !== "GIT.REPO_CONNECTION_FAILED") return false;
	return error.meta?.status === 404 || error.meta?.status === 422;
}
async function createGitHubInstallIntent(api, workspaceId, signal) {
	const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
		body: {
			provider: "github",
			workspaceId
		},
		signal
	});
	if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
	return data.data.installUrl;
}
async function readFirstSourceRepository(api, projectId, signal) {
	const { data, error, response } = await api.GET("/v1/source-repositories", {
		params: { query: {
			projectId,
			limit: 1
		} },
		signal
	});
	if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
	return data.data[0] ?? null;
}
function toRepositoryConnection(record) {
	const [owner = "", name = ""] = record.repoFullName.split("/");
	return {
		id: record.id,
		provider: "github",
		repoId: record.repoId,
		repository: {
			owner,
			name,
			fullName: record.repoFullName,
			url: `https://github.com/${record.repoFullName}`
		},
		defaultBranch: record.defaultBranch,
		isPrivate: record.isPrivate,
		status: record.status,
		installation: {
			id: record.installationId,
			status: "connected"
		},
		automation: {
			branches: record.status === "active",
			pullRequests: false,
			comments: false
		},
		connectedAt: record.createdAt,
		updatedAt: record.updatedAt
	};
}
function unsupportedRepositoryProviderError() {
	return new CliStructuredError("GIT.REPO_PROVIDER_UNSUPPORTED", "Repository provider is not supported", {
		why: "Repository connection supports GitHub repository URLs only.",
		nextActions: [userChoice$4("Pass a GitHub repository URL such as git@github.com:prisma/prisma-cli.git."), runCommand$4("prisma git connect git@github.com:owner/repo.git")]
	});
}
function repoNotConnectedError() {
	return new CliStructuredError("GIT.REPO_NOT_CONNECTED", "No GitHub repository connected", {
		why: "The resolved project does not have an active GitHub repository connection.",
		nextActions: [userChoice$4("Run prisma git connect before disconnecting."), runCommand$4("prisma git connect")]
	});
}
function repoInstallationRequiredError(repository, installUrl) {
	return new CliStructuredError("GIT.REPO_INSTALLATION_REQUIRED", "GitHub App installation required", {
		why: `The selected workspace does not have a GitHub App installation that can be used to link ${repository.fullName}.`,
		meta: {
			repository: repository.fullName,
			installUrl
		},
		nextActions: [
			userChoice$4("Finish installing the GitHub App in the browser, then rerun prisma git connect."),
			openUrl(installUrl),
			runCommand$4(`prisma git connect ${repository.url}`)
		]
	});
}
function repoNotAccessibleError(repository, installUrl) {
	return new CliStructuredError("GIT.REPO_NOT_ACCESSIBLE", "GitHub repository is not accessible", {
		why: `The GitHub App installations connected to this workspace do not expose ${repository.fullName}.`,
		meta: {
			repository: repository.fullName,
			installUrl
		},
		nextActions: [
			userChoice$4("Open the GitHub App installation URL, grant access to this repository, then rerun prisma git connect."),
			openUrl(installUrl),
			runCommand$4(`prisma git connect ${repository.url}`)
		]
	});
}
function repoAlreadyConnectedError(repositoryFullName) {
	return new CliStructuredError("GIT.REPO_ALREADY_CONNECTED", "Project already has a GitHub repository connected", {
		why: `The resolved project is already connected to ${repositoryFullName}.`,
		meta: { repository: repositoryFullName },
		nextActions: [userChoice$4("Disconnect the existing repository before connecting a different one."), runCommand$4("prisma git disconnect")]
	});
}
function repositoryFullNamesMatch(left, right) {
	return left.toLowerCase() === right.toLowerCase();
}
function repoConnectionApiError(summary, response, error) {
	const status = response?.status ?? 0;
	const apiCode = error?.error?.code;
	const apiMessage = error?.error?.message;
	const apiHint = error?.error?.hint;
	const unauthorized = status === 401 || status === 403;
	return new CliStructuredError("GIT.REPO_CONNECTION_FAILED", summary, {
		why: apiMessage ?? (unauthorized ? `The Management API rejected the request as unauthorized (HTTP ${status}).` : `The Management API returned status ${status || "unknown"}.`),
		meta: {
			status,
			...apiCode ? { apiCode } : {}
		},
		nextActions: [userChoice$4(apiHint ?? repoConnectionFixForStatus(status)), runCommand$4(unauthorized ? "prisma auth login" : "prisma project show")]
	});
}
function repoConnectionFixForStatus(status) {
	if (status === 401 || status === 403) return "Sign in again with prisma auth login, then rerun the command.";
	if (status === 404) return "Install the GitHub App for this workspace, then rerun prisma git connect.";
	if (status === 409) return "This project or repository is already linked. Disconnect the old link first, then try again.";
	if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
	return "Re-run with --log-level verbose for the underlying API response details.";
}
//#endregion
//#region src/lib/project/setup.ts
function isValidProjectSetupName(projectName) {
	return projectName.trim().length > 0;
}
function resolveProjectForSetup(projectRef, projects, workspace) {
	const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
	if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
	const match = matches[0];
	if (match !== void 0) return match;
	throw projectNotFoundError$1(projectRef, workspace);
}
function projectDirectoryBindingErrorToStructured(error) {
	return matchError(error, {
		LocalResolutionPinSerializationError: (error) => {
			throw error;
		},
		LocalResolutionPinWriteAbortedError: (error) => {
			throw error;
		},
		LocalResolutionPinWriteFailedError: (error) => localStateWriteFailedError(error, {
			why: `The CLI could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
			meta: {
				pinPath: error.pinPath,
				operation: error.operation
			}
		}),
		LocalResolutionPinGitignoreUpdateAbortedError: (error) => {
			throw error;
		},
		LocalResolutionPinGitignoreUpdateFailedError: (error) => localStateWriteFailedError(error, {
			why: "The CLI could not update .gitignore to keep local Project binding state out of git.",
			meta: {
				gitignorePath: error.gitignorePath,
				operation: error.operation
			}
		})
	});
}
function localStateWriteFailedError(error, options) {
	return new CliStructuredError("PROJECT.LOCAL_STATE_WRITE_FAILED", "Could not save local Project binding", {
		why: options.why,
		meta: options.meta,
		cause: error.cause,
		nextActions: [{
			kind: "user-choice",
			label: "Check that this directory is writable and that .prisma/local.json and .gitignore are not blocked by directories or permissions, then retry."
		}, {
			kind: "run-command",
			label: "prisma project link <id-or-name>",
			command: "prisma project link <id-or-name>"
		}]
	});
}
function toProjectSummary(project) {
	return {
		id: project.id,
		name: project.name,
		...project.url ? { url: project.url } : {},
		...project.defaultRegion != null ? { defaultRegion: project.defaultRegion } : {}
	};
}
function projectSetupNameRequiredError(command) {
	const example = `prisma ${command} my-app`;
	return new CliStructuredError("PROJECT.USAGE_ERROR", "Project create requires a name", {
		why: "The project name must be a non-empty value.",
		nextActions: [{
			kind: "user-choice",
			label: "Pass a Project name explicitly."
		}, {
			kind: "run-command",
			label: example,
			command: example
		}]
	});
}
function projectCreateFailedError(error, projectName, workspace, options) {
	const status = extractHttpStatus(error);
	const permissionRejection = status === 401 || status === 403;
	const message = error instanceof Error ? error.message : String(error);
	const nextActions = [{
		kind: "user-choice",
		label: permissionRejection ? options.permissionFix : options.fallbackFix
	}, ...options.nextSteps.map((step) => ({
		kind: "run-command",
		label: step,
		command: step
	}))];
	return new CliStructuredError("PROJECT.CREATE_FAILED", `Could not create Project "${projectName}"`, {
		why: permissionRejection ? `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).` : message,
		cause: error,
		nextActions
	});
}
const HTTP_STATUS_IN_MESSAGE = /\(HTTP (\d{3})\)/;
function extractHttpStatus(error) {
	if (!error || typeof error !== "object") return null;
	const candidate = error;
	if (typeof candidate.statusCode === "number") return candidate.statusCode;
	if (typeof candidate.status === "number") return candidate.status;
	if (typeof candidate.message === "string") {
		const match = HTTP_STATUS_IN_MESSAGE.exec(candidate.message);
		if (match) return Number.parseInt(match[1], 10);
	}
	return null;
}
//#endregion
//#region src/commands/project/context.ts
/**
* Glue between the engine command context and the project controllers.
* The resolution and env-file operations take a controller
* `CommandContext` but read only `runtime.cwd`, `runtime.env`
* and `runtime.signal`, so the CLI hands them exactly that.
*/
/**
* The controllers' `CommandContext` is typed to the three fields the
* CLI supplies today, but a controller edit could start reading a
* fourth. Left alone that surfaces as
* `Cannot read properties of undefined`, worst case inside
* `project transfer` after the project has already moved. Refusing the
* read here names the missing field at the moment it is read instead.
* Probes pass through rather than throwing: symbols are how the language
* inspects an object, and `then` is their string-keyed equivalent — the
* runtime reads it on anything it resolves through a promise. Throwing
* on a probe would be the very failure this trap exists to remove.
*/
const PROBE_KEYS = new Set(["then"]);
function refuseUnknownReads(fields, prefix) {
	return new Proxy(fields, { get(target, key) {
		if (typeof key !== "string" || key in target || PROBE_KEYS.has(key)) return Reflect.get(target, key);
		throw new Error(`the operation-context adapter provides only runtime.cwd, runtime.env and runtime.signal; ${prefix}${key} was read`);
	} });
}
function operationContext(ctx) {
	return refuseUnknownReads({ runtime: refuseUnknownReads({
		cwd: ctx.cwd,
		env: ctx.env,
		signal: ctx.signal
	}, "runtime.") }, "");
}
function listWorkspaceProjects$1(ctx) {
	return listRealWorkspaceProjects(ctx.api, ctx.signal);
}
/** Explicit `--project`, else the `.prisma/local.json` pin, else the
*  setup-required error. An absent `commandName` makes that error read
*  "this command" and drops its retry step — `branch list`'s legacy
*  behavior. */
async function resolvePinnedProject(ctx, workspace, explicitProject, commandName) {
	const target = await resolveProjectTarget({
		context: operationContext(ctx),
		workspace,
		explicitProject,
		listProjects: () => listWorkspaceProjects$1(ctx),
		commandName
	});
	if (target.isErr()) throw projectResolutionErrorToStructured(target.error);
	return target.value;
}
/** Writes `.prisma/local.json` for this directory and keeps it out of
*  git, then reports what `project create` / `project link` did. */
async function bindDirectoryToProject(ctx, workspace, project, action) {
	const written = await writeLocalResolutionPin(ctx.cwd, {
		workspaceId: workspace.id,
		projectId: project.id
	}, ctx.signal);
	if (written.isErr()) throw projectDirectoryBindingErrorToStructured(written.error);
	const ignored = await ensureLocalResolutionPinGitignore(ctx.cwd, ctx.signal);
	if (ignored.isErr()) throw projectDirectoryBindingErrorToStructured(ignored.error);
	return {
		workspace,
		project,
		directory: `./${path.basename(ctx.cwd)}`,
		localPin: {
			path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
			written: true
		},
		action
	};
}
//#endregion
//#region src/commands/resources-shared/workspace.ts
function workspaceRequiredError$1() {
	return new CliStructuredError("AUTH.USAGE_ERROR", "Workspace required", {
		why: "This command needs an active workspace, but the authenticated session does not have one.",
		nextActions: [{
			kind: "user-choice",
			label: `Run ${CLI_NAME} auth login and choose a workspace.`
		}, {
			kind: "run-command",
			label: `${CLI_NAME} auth login`,
			command: `${CLI_NAME} auth login`
		}]
	});
}
async function resolveActiveWorkspace(ctx) {
	const credential = await ctx.activeCredential();
	if (credential?.workspaceId === void 0) throw workspaceRequiredError$1();
	return {
		id: credential.workspaceId,
		name: credential.workspaceName ?? credential.workspaceId
	};
}
//#endregion
//#region src/commands/branch/list.ts
/** The `branch list` command. */
const TITLE$12 = "Listing branches for the resolved project.";
function listPresentations$8(result) {
	const rows = result.branches.map((branch) => [
		branch.name,
		branch.role,
		branch.envMap
	]);
	return {
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$12
			},
			{
				kind: "fields",
				rows: [{
					label: "project",
					value: result.projectName
				}]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No branches found."
			}] : [{
				kind: "table",
				columns: [
					"Name",
					"Role",
					"Env map"
				],
				rows
			}]
		],
		stdout: () => rows.map((row) => row.join("	"))
	};
}
const branchListCommand = defineCommand({
	help: {
		summary: "List Platform branches for the resolved project",
		examples: ["branch list", "branch list --project my-app"]
	},
	args: { flags: { project: flag.string({
		brief: "Project id or name",
		placeholder: "id-or-name"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const target = await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "branch list");
		const branches = await listBranches$1(ctx.api, target.project.id, ctx.signal);
		const result = {
			projectId: target.project.id,
			projectName: target.project.name,
			branches: sortBranches(branches.map(toBranchSummary))
		};
		return ok(ctx.present({ data: result }, listPresentations$8(result)));
	}
});
//#endregion
//#region src/lib/bucket/provider.ts
function createManagementBucketProvider(client) {
	return {
		async listBuckets(options) {
			const buckets = [];
			let cursor;
			while (true) {
				const result = await client.GET("/v1/buckets", {
					params: { query: {
						projectId: options.projectId,
						branchGitName: options.branchName,
						cursor
					} },
					signal: options.signal
				});
				if (result.error || !result.data) throw bucketApiError("Failed to list buckets", result.response, result.error);
				const data = result.data;
				buckets.push(...data.data);
				if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
				cursor = data.pagination.nextCursor;
			}
			return buckets.map(normalizeBucket);
		},
		async createBucket(options) {
			const result = await client.POST("/v1/buckets", {
				body: {
					projectId: options.projectId,
					...options.name ? { name: options.name } : {},
					...options.branchGitName ? { branchGitName: options.branchGitName } : {}
				},
				signal: options.signal
			});
			if (result.error || !result.data) throw bucketApiError("Failed to create bucket", result.response, result.error);
			const data = result.data;
			return normalizeBucket(data.data);
		},
		async deleteBucket(bucketId, options) {
			const result = await client.DELETE("/v1/buckets/{bucketId}", {
				params: { path: { bucketId } },
				signal: options?.signal
			});
			if (result.error) throw bucketApiError("Failed to delete bucket", result.response, result.error);
		},
		async listKeys(bucketId, options) {
			const keys = [];
			let cursor;
			while (true) {
				const result = await client.GET("/v1/buckets/{bucketId}/keys", {
					params: {
						path: { bucketId },
						query: { cursor }
					},
					signal: options?.signal
				});
				if (result.error || !result.data) throw bucketApiError("Failed to list bucket keys", result.response, result.error);
				const data = result.data;
				keys.push(...data.data);
				if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
				cursor = data.pagination.nextCursor;
			}
			return keys.map(normalizeKey);
		},
		async createKey(options) {
			const result = await client.POST("/v1/buckets/{bucketId}/keys", {
				params: { path: { bucketId: options.bucketId } },
				body: {
					role: options.role,
					...options.name ? { name: options.name } : {}
				},
				signal: options.signal
			});
			if (result.error || !result.data) throw bucketApiError("Failed to create bucket key", result.response, result.error);
			const raw = result.data.data;
			const secretAccessKey = raw.secretAccessKey;
			const accessKeyId = raw.accessKeyId;
			const endpoint = raw.endpoint;
			const bucketName = raw.bucketName;
			if (!secretAccessKey || !accessKeyId || !endpoint || !bucketName) throw bucketKeySecretMissingError(options.bucketId);
			return {
				key: normalizeKey(raw),
				secretAccessKey,
				accessKeyId,
				endpoint,
				bucketName
			};
		},
		async deleteKey(bucketId, keyId, options) {
			const result = await client.DELETE("/v1/buckets/{bucketId}/keys/{keyId}", {
				params: { path: {
					bucketId,
					keyId
				} },
				signal: options?.signal
			});
			if (result.error) throw bucketApiError("Failed to delete bucket key", result.response, result.error);
		}
	};
}
function normalizeBucket(raw) {
	return {
		id: raw.id,
		name: raw.name,
		status: raw.status,
		branchId: raw.branchId,
		createdAt: raw.createdAt
	};
}
function normalizeKey(raw) {
	return {
		id: raw.id,
		name: raw.name,
		role: raw.role,
		valueHint: raw.valueHint,
		createdAt: raw.createdAt
	};
}
const VERBOSE_LOG_FIX$1 = "Re-run with --log-level verbose for the underlying API response details.";
function userChoice$3(label) {
	return {
		kind: "user-choice",
		label
	};
}
function runCommand$3(command) {
	return {
		kind: "run-command",
		label: command,
		command
	};
}
function bucketKeySecretMissingError(bucketId) {
	return new CliStructuredError("BUCKET.KEY_SECRET_MISSING", "Created bucket key did not return credentials", {
		why: "Bucket key credentials are one-time-view secrets, but the Management API did not include them in this create response.",
		nextActions: [userChoice$3("Create another bucket key and store the returned credentials immediately."), runCommand$3(`${CLI_NAME} bucket key create ${bucketId}`)]
	});
}
/** A 401 or 403 is the API refusing the caller, not a bucket problem. */
function isRejectedCaller$1(status) {
	return status === 401 || status === 403;
}
function apiErrorWhy$1(status, message) {
	if (!isRejectedCaller$1(status)) return message ?? `The Management API returned status ${status || "unknown"}.`;
	const rejection = `The Management API rejected the request as ${status === 401 ? "unauthorized" : "forbidden"}.`;
	return message ? `${rejection} ${message}` : rejection;
}
function apiErrorMeta$1(status, apiCode) {
	if (!status && apiCode === void 0) return;
	return {
		...status ? { status } : {},
		...apiCode === void 0 ? {} : { apiCode }
	};
}
function apiErrorActions$1(status, hint) {
	if (!isRejectedCaller$1(status)) return [userChoice$3(hint ?? VERBOSE_LOG_FIX$1)];
	return [userChoice$3(hint ?? `Sign in again with prisma auth login, then retry the command.`), runCommand$3(`${CLI_NAME} auth login`)];
}
/**
* Every bucket Management API failure lands on the one registered code.
* The response's own error code is data, not an identity: it travels in
* `meta.apiCode` beside `meta.status` so a consumer can still branch on
* it without the CLI minting a code it never registered.
*/
function bucketApiError(summary, response, error) {
	const status = response?.status ?? 0;
	const meta = apiErrorMeta$1(status, error?.error?.code);
	return new CliStructuredError("BUCKET.API_ERROR", summary, {
		why: apiErrorWhy$1(status, error?.error?.message),
		...meta === void 0 ? {} : { meta },
		nextActions: apiErrorActions$1(status, error?.error?.hint)
	});
}
//#endregion
//#region src/commands/bucket/context.ts
/** Workspace, project and provider for the `bucket *` commands. */
/** Where a caller who is missing a bucket id finds one. */
const LIST_BUCKETS_COMMAND = `${CLI_NAME} bucket list`;
const projectFlag$3 = flag.string({
	brief: "Project id or name",
	placeholder: "id-or-name"
});
const branchFlag$2 = flag.string({
	brief: "Branch git name",
	placeholder: "git-name"
});
const bucketPositional = positional.string({
	brief: "Bucket id",
	placeholder: "bucket-id"
});
/** The legacy `requireBucketContext`: `bucket list` and `bucket create`
*  address a project, so they need the workspace and the resolved
*  project before the provider. */
async function resolveBucketContext(ctx, flags, commandName) {
	const target = await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), flags.project, commandName);
	return {
		provider: createManagementBucketProvider(ctx.api),
		projectId: target.project.id,
		projectName: target.project.name
	};
}
/** The legacy `requireBucketProviderOnly`: `bucket delete` and every
*  `bucket key` command address a bucket id directly, with no workspace
*  requirement and no project resolution. */
function resolveBucketProviderOnly(ctx) {
	return createManagementBucketProvider(ctx.api);
}
//#endregion
//#region src/commands/bucket/presentation.ts
/** Legacy `formatBucketTarget`. */
function bucketTargetLabel(projectName, branchId) {
	return branchId ? `${projectName} / ${branchId}` : projectName;
}
function bucketRows(buckets) {
	return buckets.map((bucket) => [
		bucket.name,
		bucket.id,
		bucket.status,
		bucket.branchId ?? "unscoped",
		bucket.createdAt
	]);
}
/** The stdout rows: an unscoped bucket has an empty branch field, not
*  the word a reader wants to see there. */
function bucketStdoutRows(buckets) {
	return buckets.map((bucket) => [
		bucket.name,
		bucket.id,
		bucket.status,
		bucket.branchId ?? "",
		bucket.createdAt
	]);
}
function bucketKeyRows(keys) {
	return keys.map((key) => [
		key.name,
		key.id,
		key.role,
		key.valueHint,
		key.createdAt
	]);
}
//#endregion
//#region src/commands/bucket/create.ts
/** The `bucket create` command. */
const bucketCreateCommand = defineCommand({
	args: { flags: {
		name: flag.string({
			brief: "Bucket display name (auto-generated if omitted)",
			placeholder: "name"
		}),
		project: projectFlag$3,
		branch: branchFlag$2
	} },
	help: {
		summary: "Create an object-store bucket",
		examples: [
			"bucket create",
			"bucket create --name my-store",
			"bucket create --branch preview --json"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket create");
		const bucket = await provider.createBucket({
			projectId,
			name: args.flags.name?.trim() || void 0,
			branchGitName: args.flags.branch,
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			bucket
		};
		return ok(ctx.present({ data: result }, {
			human: () => [{
				kind: "summary",
				status: "ok",
				text: `Created bucket "${bucket.name}" in ${bucketTargetLabel(projectName, bucket.branchId)}.`
			}],
			stdout: () => [],
			json: () => result,
			next: () => []
		}));
	}
});
//#endregion
//#region src/commands/bucket/delete.ts
/** The `bucket delete` command. */
const CONSENT_QUESTION$6 = "Deleting this bucket permanently removes all objects and access keys.";
function deletePresentations$5(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Deleting object-store bucket."
			},
			{
				kind: "fields",
				rows: [{
					label: "bucket",
					value: result.bucket.id
				}]
			},
			{
				kind: "list",
				items: ["Bucket and all its access keys were removed."]
			}
		]
	};
}
const bucketDeleteCommand = defineCommand({
	args: { positionals: { bucketId: bucketPositional } },
	help: {
		summary: "Delete a bucket and all its access keys",
		examples: ["bucket delete bkt_123 --confirm bkt_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const bucketId = args.positionals.bucketId.trim();
		if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
			why: "Bucket deletion needs a bucket id.",
			nextActions: [{
				kind: "user-choice",
				label: "Pass the bucket id to delete."
			}, {
				kind: "run-command",
				label: LIST_BUCKETS_COMMAND,
				command: LIST_BUCKETS_COMMAND
			}]
		});
		await ctx.prompt.consent(CONSENT_QUESTION$6, { token: bucketId });
		await resolveBucketProviderOnly(ctx).deleteBucket(bucketId, { signal: ctx.signal });
		const result = { bucket: { id: bucketId } };
		return ok(ctx.present({ data: result }, deletePresentations$5(result)));
	}
});
//#endregion
//#region src/commands/bucket/key-create.ts
/** The `bucket key create` command. */
/** Legacy `resolveKeyRole`: anything that is not exactly `read` — the
*  omitted flag included — is `read_write`. */
function resolveKeyRole(role) {
	return role === "read" ? "read" : "read_write";
}
function createPresentations$1(result) {
	return {
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: `Created key "${result.key.name}" for bucket "${result.bucketName}".`
			},
			{
				kind: "list",
				items: ["The credentials below are shown once — copy them now.", "Set these environment variables to use this bucket:"]
			},
			{
				kind: "fields",
				rows: [
					{
						label: "S3_ENDPOINT",
						value: result.endpoint
					},
					{
						label: "S3_ACCESS_KEY_ID",
						value: result.accessKeyId
					},
					{
						label: "S3_SECRET_ACCESS_KEY",
						value: result.secretAccessKey
					},
					{
						label: "S3_BUCKET",
						value: result.bucketName
					}
				]
			}
		],
		stdout: () => [
			`S3_ENDPOINT=${result.endpoint}`,
			`S3_ACCESS_KEY_ID=${result.accessKeyId}`,
			`S3_SECRET_ACCESS_KEY=${result.secretAccessKey}`,
			`S3_BUCKET=${result.bucketName}`
		]
	};
}
const bucketKeyCreateCommand = defineCommand({
	args: {
		positionals: { bucketId: bucketPositional },
		flags: {
			role: flag.enum({
				brief: "Access role (default: read_write)",
				values: ["read", "read_write"]
			}),
			name: flag.string({
				brief: "Key display name (auto-generated if omitted)",
				placeholder: "name"
			})
		}
	},
	help: {
		summary: "Create a bucket access key and print its one-time credentials",
		examples: [
			"bucket key create bkt_123",
			"bucket key create bkt_123 --role read",
			"bucket key create bkt_123 --name ci-key --role read_write"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const bucketId = args.positionals.bucketId.trim();
		if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
			why: "Bucket key creation needs a bucket id.",
			nextActions: [{
				kind: "user-choice",
				label: "Pass the bucket id."
			}, {
				kind: "run-command",
				label: LIST_BUCKETS_COMMAND,
				command: LIST_BUCKETS_COMMAND
			}]
		});
		const result = {
			bucketId,
			...await resolveBucketProviderOnly(ctx).createKey({
				bucketId,
				name: args.flags.name?.trim() || void 0,
				role: resolveKeyRole(args.flags.role),
				signal: ctx.signal
			})
		};
		return ok(ctx.present({ data: result }, createPresentations$1(result)));
	}
});
//#endregion
//#region src/commands/bucket/key-delete.ts
/** The `bucket key delete` command. */
function deletePresentations$4(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Deleting bucket access key."
			},
			{
				kind: "fields",
				rows: [{
					label: "key",
					value: result.key.id
				}]
			},
			{
				kind: "list",
				items: ["The access key was revoked and removed."]
			}
		]
	};
}
const bucketKeyDeleteCommand = defineCommand({
	args: { positionals: {
		bucketId: bucketPositional,
		keyId: positional.string({
			brief: "Key id",
			placeholder: "key-id"
		})
	} },
	help: {
		summary: "Revoke and delete a bucket access key",
		examples: ["bucket key delete bkt_123 bkey_456"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const bucketId = args.positionals.bucketId.trim();
		const keyId = args.positionals.keyId.trim();
		if (!bucketId || !keyId) {
			const listKeysCommand = `${CLI_NAME} bucket key list <bucketId>`;
			throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id and key id required", {
				why: "Bucket key deletion needs both a bucket id and a key id.",
				nextActions: [{
					kind: "user-choice",
					label: "Pass the bucket id and key id."
				}, {
					kind: "run-command",
					label: listKeysCommand,
					command: listKeysCommand
				}]
			});
		}
		await resolveBucketProviderOnly(ctx).deleteKey(bucketId, keyId, { signal: ctx.signal });
		const result = { key: { id: keyId } };
		return ok(ctx.present({ data: result }, deletePresentations$4(result)));
	}
});
//#endregion
//#region src/output/patterns.ts
function serializeList(input) {
	return {
		context: input.context,
		items: input.items.map((item) => ({
			name: item.label,
			id: item.id,
			status: item.status
		})),
		count: input.items.length
	};
}
//#endregion
//#region src/presenters/bucket.ts
function serializeBucketList(result) {
	return {
		context: {
			project: result.projectName,
			...result.branchName ? { branch: result.branchName } : {}
		},
		items: result.buckets.map((bucket) => ({
			name: bucket.name,
			id: bucket.id,
			status: bucket.status
		})),
		count: result.buckets.length,
		projectId: result.projectId,
		branchName: result.branchName,
		buckets: result.buckets
	};
}
function serializeBucketKeyList(result) {
	return {
		...serializeList({
			context: { bucket: result.bucketId },
			items: result.keys.map((key) => ({
				noun: "key",
				label: key.name,
				id: key.id,
				status: null
			}))
		}),
		bucketId: result.bucketId,
		keys: result.keys
	};
}
//#endregion
//#region src/commands/bucket/key-list.ts
/** The `bucket key list` command. */
const TITLE$11 = "Listing access keys for bucket.";
function listPresentations$7(result) {
	const rows = bucketKeyRows(result.keys);
	return {
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$11
			},
			{
				kind: "fields",
				rows: [{
					label: "bucket",
					value: result.bucketId
				}]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No keys found."
			}] : [{
				kind: "table",
				columns: [
					"Name",
					"Id",
					"Role",
					"Hint",
					"Created"
				],
				rows
			}]
		],
		stdout: () => rows.map((row) => row.join("	")),
		json: () => serializeBucketKeyList(result)
	};
}
const bucketKeyListCommand = defineCommand({
	args: { positionals: { bucketId: bucketPositional } },
	help: {
		summary: "List access keys for a bucket",
		examples: ["bucket key list bkt_123", "bucket key list bkt_123 --json"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const bucketId = args.positionals.bucketId.trim();
		if (!bucketId) throw new CliStructuredError("BUCKET.USAGE_ERROR", "Bucket id required", {
			why: "Bucket key listing needs a bucket id.",
			nextActions: [{
				kind: "user-choice",
				label: "Pass the bucket id."
			}, {
				kind: "run-command",
				label: LIST_BUCKETS_COMMAND,
				command: LIST_BUCKETS_COMMAND
			}]
		});
		const result = {
			bucketId,
			keys: await resolveBucketProviderOnly(ctx).listKeys(bucketId, { signal: ctx.signal })
		};
		return ok(ctx.present({ data: result }, listPresentations$7(result)));
	}
});
//#endregion
//#region src/commands/bucket/list.ts
/** The `bucket list` command. */
const TITLE$10 = "Listing object-store buckets for the resolved project.";
function listPresentations$6(result) {
	const rows = bucketRows(result.buckets);
	const stdoutRows = bucketStdoutRows(result.buckets);
	return {
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$10
			},
			{
				kind: "fields",
				rows: [{
					label: "project",
					value: result.projectName
				}, ...result.branchName ? [{
					label: "branch",
					value: result.branchName
				}] : []]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No buckets found."
			}] : [{
				kind: "table",
				columns: [
					"Name",
					"Id",
					"Status",
					"Branch",
					"Created"
				],
				rows
			}]
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeBucketList(result)
	};
}
const bucketListCommand = defineCommand({
	args: { flags: {
		project: projectFlag$3,
		branch: branchFlag$2
	} },
	help: {
		summary: "List object-store buckets for the resolved project",
		examples: [
			"bucket list",
			"bucket list --branch preview",
			"bucket list --json"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, projectId, projectName } = await resolveBucketContext(ctx, args.flags, "bucket list");
		const buckets = await provider.listBuckets({
			projectId,
			branchName: args.flags.branch,
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			branchName: args.flags.branch ?? null,
			buckets
		};
		return ok(ctx.present({ data: result }, listPresentations$6(result)));
	}
});
//#endregion
//#region src/lib/feedback.ts
const FEEDBACK_TIMEOUT_MS = 3e3;
const TIMEOUT_DETAIL = `The feedback service did not answer within ${FEEDBACK_TIMEOUT_MS / 1e3} seconds.`;
function isTimeout(error) {
	return error instanceof Error && error.name === "TimeoutError";
}
function unreachableDetail(error) {
	if (isTimeout(error)) return TIMEOUT_DETAIL;
	return `The feedback service could not be reached${error instanceof Error && error.cause instanceof Error ? ` (${error.cause.message})` : ""}.`;
}
function unreadableBodyDetail(error) {
	return isTimeout(error) ? TIMEOUT_DETAIL : "The feedback service response could not be read.";
}
//#endregion
//#region src/lib/version.ts
const requireFromHere = createRequire(import.meta.url);
/** The bundled entry sits one directory below the package root
*  (`dist/cli.js`); this source file sits two below (`src/lib/`). Both
*  are tried, nearest first, so the same code serves either. */
const PACKAGE_JSON_CANDIDATES = ["../package.json", "../../package.json"];
function readPackageMetadata() {
	for (const candidate of PACKAGE_JSON_CANDIDATES) try {
		const metadata = requireFromHere(candidate);
		if (metadata.version) return metadata;
	} catch {}
	return {};
}
function getCliVersion() {
	const pkg = readPackageMetadata();
	if (!pkg.version) throw new Error("CLI version metadata is missing from the installed package: the bundled package.json could not be read or did not contain a version field. Reinstall the CLI from the npm registry, or check your install path is intact.");
	return pkg.version;
}
function getCliName() {
	return CLI_NAME;
}
//#endregion
//#region src/commands/feedback.ts
const DEFAULT_FEEDBACK_ENDPOINT = "https://hiieirp2pwqnjvq9axzyg6d0.fra.prisma.build/feedback";
const MAX_MESSAGE_LENGTH = 4e3;
const MAX_EMAIL_LENGTH = 320;
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function sendFailedError(detail) {
	return new CliStructuredError("FEEDBACK.SEND_FAILED", "Feedback could not be delivered", {
		why: detail,
		nextActions: [{
			kind: "user-choice",
			label: "Check your network and rerun."
		}]
	});
}
function messageRequiredError() {
	return new CliStructuredError("FEEDBACK.MESSAGE_REQUIRED", "Feedback message required", {
		why: "The message argument is empty.",
		nextActions: [{
			kind: "user-choice",
			label: "Pass a non-empty message."
		}, {
			kind: "run-command",
			label: "Send feedback",
			command: `${CLI_NAME} feedback "the deploy flow is great"`
		}]
	});
}
function messageTooLongError(length) {
	return new CliStructuredError("FEEDBACK.MESSAGE_TOO_LONG", "Feedback message too long", {
		why: `The message is ${length} characters; the limit is ${MAX_MESSAGE_LENGTH}.`,
		nextActions: [{
			kind: "user-choice",
			label: "Shorten the message."
		}]
	});
}
function emailInvalidError(value) {
	return new CliStructuredError("FEEDBACK.EMAIL_INVALID", "Invalid email", {
		why: `"${value}" is not a valid email address of at most ${MAX_EMAIL_LENGTH} characters.`,
		nextActions: [{
			kind: "user-choice",
			label: "Pass a valid address with --email, or drop the flag to stay anonymous."
		}, {
			kind: "run-command",
			label: "Send feedback with a contact address",
			command: `${CLI_NAME} feedback "please add X" --email you@example.com`
		}]
	});
}
function feedbackPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "ok",
			text: "Feedback sent. Thank you!"
		}, {
			kind: "fields",
			rows: [
				...result.id ? [{
					label: "id",
					value: result.id
				}] : [],
				{
					label: "sent as",
					value: result.email ?? "anonymous"
				},
				{
					label: "included",
					value: `CLI ${result.context.cliVersion}, ${result.context.runtime.name} ${result.context.runtime.version}, ${result.context.platform} ${result.context.arch}`
				}
			]
		}]
	};
}
async function readServiceError(response, signal) {
	let payload;
	try {
		payload = await response.json();
	} catch (error) {
		if (signal.aborted) throw error;
		return "";
	}
	return typeof payload?.error?.message === "string" ? ` (${payload.error.message})` : "";
}
async function sendFeedback(endpoint, body, signal) {
	try {
		return await fetch(endpoint, {
			method: "POST",
			headers: {
				"content-type": "application/json",
				"user-agent": `${CLI_NAME}/${getCliVersion()}`
			},
			body: JSON.stringify(body),
			signal: AbortSignal.any([signal, AbortSignal.timeout(FEEDBACK_TIMEOUT_MS)])
		});
	} catch (error) {
		if (signal.aborted) throw error;
		throw sendFailedError(unreachableDetail(error));
	}
}
async function readSubmissionId(response, signal) {
	let payload;
	try {
		payload = await response.json();
	} catch (error) {
		if (signal.aborted) throw error;
		if (!(error instanceof SyntaxError)) throw sendFailedError(unreadableBodyDetail(error));
		payload = null;
	}
	return typeof payload?.id === "string" ? payload.id : null;
}
async function postFeedback(endpoint, body, signal) {
	const response = await sendFeedback(endpoint, body, signal);
	if (!response.ok) throw sendFailedError(`The feedback service responded with HTTP ${response.status}${await readServiceError(response, signal)}.`);
	return readSubmissionId(response, signal);
}
const feedbackCommand = defineCommand({
	help: {
		summary: "Send feedback to the Prisma CLI team",
		description: "Anonymous unless --email is passed. Every submission includes the CLI\nversion, node version, and OS platform/arch, and nothing else.",
		examples: ["feedback \"the deploy flow is great\"", "feedback \"please add X\" --email you@example.com"]
	},
	args: {
		flags: { email: flag.string({
			brief: "Contact email if you want a reply; feedback is anonymous without it",
			placeholder: "address"
		}) },
		positionals: { message: positional.string({
			brief: "Feedback text (up to 4000 characters)",
			placeholder: "message"
		}) }
	},
	handler: async (args, ctx) => {
		const message = args.positionals.message.trim();
		if (!message) throw messageRequiredError();
		if (message.length > MAX_MESSAGE_LENGTH) throw messageTooLongError(message.length);
		const email = args.flags.email?.trim();
		if (email !== void 0 && (email.length > MAX_EMAIL_LENGTH || !EMAIL_PATTERN.test(email))) throw emailInvalidError(args.flags.email ?? "");
		const context = {
			cliVersion: getCliVersion(),
			runtime: { ...ctx.host.runtime },
			platform: ctx.host.platform,
			arch: ctx.host.arch
		};
		const result = {
			id: await postFeedback(ctx.env.PRISMA_CLI_FEEDBACK_URL || DEFAULT_FEEDBACK_ENDPOINT, {
				message,
				...email ? { email } : {},
				meta: { ...context }
			}, ctx.signal),
			email: email ?? null,
			context
		};
		return ok(ctx.present({ data: result }, feedbackPresentations(result)));
	}
});
//#endregion
//#region src/adapters/git.ts
const execFileAsync = promisify(execFile);
async function readGitOriginRemote(cwd, signal) {
	try {
		const { stdout } = await execFileAsync("git", [
			"config",
			"--get",
			"remote.origin.url"
		], {
			cwd,
			timeout: 5e3,
			signal
		});
		const remote = stdout.trim();
		return remote.length > 0 ? remote : null;
	} catch (error) {
		if (signal?.aborted || isAbortError(error)) throw error;
		return null;
	}
}
function isAbortError(error) {
	return error instanceof Error && error.name === "AbortError";
}
function parseGitHubRepositoryUrl(value) {
	const input = value.trim();
	const shorthand = input.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/);
	if (shorthand) return toGitHubRepositoryReference(shorthand[1], shorthand[2]);
	let parsed;
	try {
		parsed = new URL(input);
	} catch {
		return null;
	}
	if (parsed.hostname !== "github.com") return null;
	if (parsed.protocol !== "https:" && parsed.protocol !== "http:" && parsed.protocol !== "ssh:") return null;
	const parts = parsed.pathname.split("/").filter(Boolean);
	if (parts.length !== 2) return null;
	const [owner, rawName] = parts;
	return toGitHubRepositoryReference(owner, rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName);
}
function toGitHubRepositoryReference(owner, name) {
	if (!owner || !name || owner.includes("/") || name.includes("/")) return null;
	return {
		provider: "github",
		owner,
		name,
		fullName: `${owner}/${name}`,
		url: `https://github.com/${owner}/${name}`
	};
}
//#endregion
//#region src/presenters/project.ts
function serializeProjectList(result) {
	return {
		...serializeList({
			context: { workspace: result.workspace.name },
			items: result.projects.map((project) => ({
				noun: "project",
				label: project.name,
				id: project.id,
				status: null
			}))
		}),
		localBinding: result.localBinding ?? null
	};
}
function serializeProjectSetup(result) {
	return result;
}
function formatGitConnectionDetail(status) {
	switch (status) {
		case "active": return "GitHub branch automation is active for this project.";
		case "pending": return "GitHub branch automation is pending GitHub App installation.";
		case "archived": return "GitHub branch automation has been archived for this project.";
		default: return "GitHub repository is connected, but branch automation is not active.";
	}
}
//#endregion
//#region src/commands/git/context.ts
/** Workspace, project and the source-repository client for the
*  `git *` commands. */
const projectFlag$2 = flag.string({
	brief: "Project id or name",
	placeholder: "id-or-name"
});
async function resolveGitContext(ctx, explicitProject, commandName) {
	const target = await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), explicitProject, commandName);
	return {
		api: ctx.api,
		target
	};
}
//#endregion
//#region src/commands/git/errors.ts
/**
* The install wait's two terminal outcomes: the workspace has an
* inspectable installation that simply does not expose the repository,
* or it has none at all.
*/
function installWaitFailedError(repository, installUrl, inspectableInstallationCount) {
	return inspectableInstallationCount > 0 ? repoNotAccessibleError(repository, installUrl) : repoInstallationRequiredError(repository, installUrl);
}
//#endregion
//#region src/commands/git/connect.ts
/** The `git connect` command. */
/** The legacy wait line, printed once before the poll loop. */
const WAIT_MESSAGE = "Waiting for GitHub App installation or repository access approval...";
/**
* The legacy `resolveInstalledRepository`: find the repository in the
* workspace's GitHub App installations, and when it is not there yet,
* send the user to an install intent and wait for them to finish. The
* engine owns the announcement, the browser and the polling clock; this
* only supplies the address, the cadence and the question being polled.
*/
async function resolveInstalledRepository(ctx, api, workspaceId, repository) {
	const inspect = async (signal) => findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, signal), repository, signal);
	const first = await inspect(ctx.signal);
	if (first.match) return first.match;
	const installUrl = await createGitHubInstallIntent(api, workspaceId, ctx.signal);
	let match = null;
	let inspectableInstallationCount = 0;
	try {
		await ctx.prompt.browserWait({
			url: installUrl,
			message: WAIT_MESSAGE,
			timeout: readPositiveIntegerEnv(ctx.env.PRISMA_CLI_GITHUB_INSTALL_TIMEOUT_MS, GITHUB_INSTALL_POLL_TIMEOUT_MS),
			interval: readPositiveIntegerEnv(ctx.env.PRISMA_CLI_GITHUB_INSTALL_POLL_INTERVAL_MS, GITHUB_INSTALL_POLL_INTERVAL_MS),
			poll: async (signal) => {
				const lookup = await inspect(signal);
				match = lookup.match;
				inspectableInstallationCount = lookup.inspectableInstallationCount;
				return lookup.match !== null;
			}
		});
	} catch (error) {
		if (CliStructuredError.is(error) && error.code === "CLI.BROWSER_WAIT_TIMEOUT") throw installWaitFailedError(repository, installUrl, inspectableInstallationCount);
		throw error;
	}
	if (match === null) throw installWaitFailedError(repository, installUrl, inspectableInstallationCount);
	return match;
}
function connectPresentations(result) {
	const connection = result.repositoryConnection;
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Connecting Git to the resolved project."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "project",
						value: result.project.name
					},
					{
						label: "workspace",
						value: result.workspace.name
					},
					{
						label: "repository",
						value: connection.repository.fullName
					},
					{
						label: "status",
						value: connection.status
					}
				]
			},
			{
				kind: "list",
				items: [formatGitConnectionDetail(connection.status)]
			}
		]
	};
}
const gitConnectCommand = defineCommand({
	args: {
		positionals: { gitUrl: positional.optionalString({
			brief: "GitHub repository URL",
			placeholder: "git-url"
		}) },
		flags: { project: projectFlag$2 }
	},
	help: {
		summary: "Connect the resolved project to a GitHub repository",
		examples: [
			"git connect",
			"git connect git@github.com:prisma/prisma-cli.git",
			"git connect --project proj_123"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { api, target } = await resolveGitContext(ctx, args.flags.project, "git connect");
		const remoteUrl = args.positionals.gitUrl ?? await readGitOriginRemote(ctx.cwd, ctx.signal);
		if (!remoteUrl) {
			const example = `${CLI_NAME} git connect git@github.com:prisma/prisma-cli.git`;
			throw new CliStructuredError("GIT.USAGE_ERROR", "Repository connection requires a GitHub repository URL", {
				why: "No git-url was provided and the local repo does not have an origin remote.",
				nextActions: [{
					kind: "user-choice",
					label: `Pass a GitHub repository URL, or add a GitHub origin remote and rerun ${CLI_NAME} git connect.`
				}, {
					kind: "run-command",
					label: example,
					command: example
				}]
			});
		}
		const repository = parseGitHubRepositoryUrl(remoteUrl);
		if (!repository) throw unsupportedRepositoryProviderError();
		const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
		if (existing) {
			const existingConnection = toRepositoryConnection(existing);
			if (!repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) throw repoAlreadyConnectedError(existingConnection.repository.fullName);
			const idempotent = {
				...target,
				repositoryConnection: existingConnection
			};
			return ok(ctx.present({ data: idempotent }, connectPresentations(idempotent)));
		}
		const installed = await resolveInstalledRepository(ctx, api, target.workspace.id, repository);
		const { data, error, response } = await api.POST("/v1/source-repositories", {
			body: {
				projectId: target.project.id,
				provider: "github",
				providerRepositoryId: installed.repository.id,
				installationId: installed.installation.id
			},
			signal: ctx.signal
		});
		if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
		const result = {
			...target,
			repositoryConnection: toRepositoryConnection(data.data)
		};
		return ok(ctx.present({ data: result }, connectPresentations(result)));
	}
});
//#endregion
//#region src/commands/git/disconnect.ts
/** The `git disconnect` command. */
function disconnectPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Disconnecting Git from the resolved project."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "project",
						value: result.project.name
					},
					{
						label: "workspace",
						value: result.workspace.name
					},
					{
						label: "repository",
						value: result.repositoryConnection.repository.fullName
					}
				]
			},
			{
				kind: "list",
				items: ["GitHub branch automation is no longer active for this project."]
			}
		]
	};
}
const gitDisconnectCommand = defineCommand({
	args: { flags: { project: projectFlag$2 } },
	help: {
		summary: "Disconnect the GitHub repository from the resolved project",
		examples: ["git disconnect", "git disconnect --project proj_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { api, target } = await resolveGitContext(ctx, args.flags.project, "git disconnect");
		const existing = await readFirstSourceRepository(api, target.project.id, ctx.signal);
		if (!existing) throw repoNotConnectedError();
		const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
			params: { path: { id: existing.id } },
			signal: ctx.signal
		});
		if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
		const result = {
			...target,
			repositoryConnection: toRepositoryConnection(existing)
		};
		return ok(ctx.present({ data: result }, disconnectPresentations(result)));
	}
});
//#endregion
//#region src/lib/skills/sync.ts
/**
* Brings the harness skill directories in line with the installed
* source packages: copies each skill tree whose stamp does not match
* the package it came from, and removes copies whose source package is
* gone. A target directory that exists but is not this CLI's copy is
* refused, never replaced. Doing nothing is the normal outcome and is
* not an error.
*/
async function syncSkills(status) {
	const synced = [];
	const refused = [];
	for (const skill of status.skills) {
		const dirs = skill.targets.filter((target) => target.state === "stale" || target.state === "absent").map((target) => target.dir);
		const refusedDirs = skill.targets.filter((target) => target.state === "unmanaged").map((target) => target.dir);
		if (refusedDirs.length > 0) refused.push({
			skill: skill.skill,
			dirs: refusedDirs
		});
		for (const target of skill.targets) if (target.state === "synced") await removeOldCliGitignore(path.join(status.projectRoot, target.dir, skill.skill, ".gitignore"));
		if (dirs.length === 0) continue;
		for (const dir of dirs) await replaceTree(skill.sourceDir, path.join(status.projectRoot, dir, skill.skill));
		synced.push({
			skill: skill.skill,
			library: skill.library,
			version: skill.version,
			dirs
		});
	}
	const pruned = [];
	for (const orphan of status.orphans) {
		for (const dir of orphan.dirs) await rm(path.join(status.projectRoot, dir, orphan.skill), {
			recursive: true,
			force: true
		});
		pruned.push({
			skill: orphan.skill,
			library: orphan.library,
			dirs: orphan.dirs
		});
	}
	return {
		projectRoot: status.projectRoot,
		packages: status.packages,
		skills: status.skills.map((skill) => skill.skill),
		synced,
		pruned,
		refused,
		checkDisabled: status.checkDisabled
	};
}
const OLD_CLI_GITIGNORE = /^\*\r?\n?$/;
async function removeOldCliGitignore(file) {
	let content;
	try {
		content = await readFile(file, "utf8");
	} catch {
		return;
	}
	if (OLD_CLI_GITIGNORE.test(content)) await rm(file, { force: true });
}
/**
* Copies a skill tree over whatever is at the destination, so a skill
* that lost a reference file between versions does not keep the stale
* one. Files are read and written rather than handed to `fs.cp`,
* because under Yarn PnP the source lives inside a zip and only the
* patched read path can see it.
*/
async function replaceTree(source, destination) {
	await rm(destination, {
		recursive: true,
		force: true
	});
	await copyTree(source, destination);
}
async function copyTree(source, destination) {
	await mkdir(destination, { recursive: true });
	for (const entry of await readdir(source, { withFileTypes: true })) {
		const from = path.join(source, entry.name);
		const to = path.join(destination, entry.name);
		if (entry.isDirectory()) {
			await copyTree(from, to);
			continue;
		}
		if (entry.isFile() || entry.isSymbolicLink()) await writeFile(to, await readFile(from));
	}
}
//#endregion
//#region src/commands/skills/presentation.ts
function projectFields(projectRoot, checkDisabled) {
	return {
		kind: "fields",
		rows: [{
			label: "project",
			value: projectRoot
		}, {
			label: "check",
			value: checkDisabled ? "disabled" : "enabled"
		}]
	};
}
/** Decision B: "up to date" may not over-claim — when directories were
*  refused, the summary says so in the same line, for sync and list
*  alike. */
function unmanagedClause(count) {
	if (count === 0) return "";
	return count === 1 ? "; 1 directory is not managed by this CLI" : `; ${count} directories are not managed by this CLI`;
}
function syncSummary(result) {
	if (result.pruned.length === 0) {
		if (result.agents.length === 0) return "No agents are configured to sync skills for.";
		if (result.packages.length === 0) return "No Prisma packages with agent skills are installed.";
		if (result.skills.length === 0) return "No Prisma dependencies in your project ship agent skills to sync.";
	}
	const refusedDirs = result.refused.reduce((count, skill) => count + skill.dirs.length, 0);
	if (result.synced.length === 0 && result.pruned.length === 0) return `Agent skills are up to date${unmanagedClause(refusedDirs)}.`;
	const removed = `${result.pruned.length} skill${result.pruned.length === 1 ? "" : "s"}`;
	if (result.synced.length === 0 && result.pruned.length > 0) return `Removed ${removed}${unmanagedClause(refusedDirs)}.`;
	const synced = `${result.synced.length} skill${result.synced.length === 1 ? "" : "s"}`;
	return `${result.pruned.length === 0 ? `Synced ${synced}` : `Synced ${synced} and removed ${result.pruned.length}`}${unmanagedClause(refusedDirs)}.`;
}
function syncPresentations(result) {
	const syncedRows = result.synced.map((skill) => [
		skill.skill,
		skill.library,
		skill.version,
		skill.dirs.join(", ")
	]);
	const prunedRows = result.pruned.map((skill) => [skill.skill, skill.dirs.join(", ")]);
	const refusedRows = result.refused.map((skill) => [skill.skill, skill.dirs.join(", ")]);
	return {
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: result.synced.length > 0 ? "ok" : "info",
				text: syncSummary(result)
			},
			projectFields(result.projectRoot, result.checkDisabled),
			...syncedRows.length === 0 ? [] : [{
				kind: "table",
				columns: [
					"Skill",
					"Package",
					"Version",
					"Installed into"
				],
				rows: syncedRows
			}],
			...prunedRows.length === 0 ? [] : [{
				kind: "table",
				columns: ["Removed skill", "Removed from"],
				rows: prunedRows
			}],
			...refusedRows.length === 0 ? [] : [{
				kind: "table",
				columns: ["Unmanaged skill", "Left untouched in"],
				rows: refusedRows
			}]
		],
		stdout: () => syncedRows.map((row) => row.join("	"))
	};
}
function listSummary(result) {
	if (result.agents.length === 0) return "No agents are configured to sync skills for.";
	if (result.packages.length === 0) return "No Prisma packages with agent skills are installed.";
	if (result.skills.length === 0) return "No Prisma dependencies in your project ship agent skills.";
	if (!result.upToDate) return "Agent skills are out of date.";
	const unmanaged = result.skills.flatMap((skill) => skill.targets).filter((target) => target.state === "unmanaged").length;
	return `Agent skills are up to date${unmanagedClause(unmanaged)}.`;
}
function listPresentations$5(result) {
	const rows = result.skills.flatMap((skill) => skill.targets.map((target) => [
		skill.skill,
		skill.library,
		skill.version,
		target.dir,
		target.syncedVersion ?? "-",
		target.state
	]));
	return {
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: listSummary(result)
			},
			projectFields(result.projectRoot, result.checkDisabled),
			...rows.length === 0 ? [] : [{
				kind: "table",
				columns: [
					"Skill",
					"Package",
					"Installed",
					"Directory",
					"Synced",
					"State"
				],
				rows
			}]
		],
		stdout: () => rows.map((row) => row.join("	"))
	};
}
//#endregion
//#region src/commands/skills/sync.ts
function packageReports(packages) {
	return packages.map((installed) => ({
		package: installed.name,
		version: installed.version,
		conflictingVersions: installed.conflictingVersions
	}));
}
/** Workspace members that pin different versions of the same
*  skill-bearing package: the highest wins, and the user hears about
*  it, because the losing members get a skill describing a version
*  they did not install. */
function versionConflictDiagnostics(packages) {
	return packages.filter((installed) => installed.conflictingVersions.length > 1).map((installed) => ({
		code: "SKILLS.VERSION_CONFLICT",
		severity: "warn",
		summary: `Workspace members install different versions of ${installed.name} (${installed.conflictingVersions.join(", ")}); the skills for ${installed.version} were installed.`,
		nextActions: [{
			kind: "user-choice",
			label: `Pin one version of ${installed.name} across the workspace.`
		}]
	}));
}
/** Target directories that already hold a skill this CLI does not
*  manage: sync leaves them alone, and the user hears why the packaged
*  skill was not installed there. */
function unmanagedDirectoryDiagnostics(refused) {
	return refused.flatMap((entry) => entry.dirs.map((dir) => ({
		code: "SKILLS.UNMANAGED_DIRECTORY",
		severity: "warn",
		summary: `${dir}/${entry.skill} is not managed by this CLI, so it was left untouched.`,
		nextActions: [{
			kind: "user-choice",
			label: `Move or remove ${dir}/${entry.skill}, then rerun skills sync to install the packaged skill.`
		}]
	})));
}
function bothSwitchesError() {
	return new CliStructuredError("CLI.INVALID_ARGUMENTS", "--disable and --enable ask for opposite things, so only one may be given.", { nextActions: [{
		kind: "user-choice",
		label: "Run with --disable to silence the skills check, or --enable to restore it."
	}] });
}
const skillsSyncCommand = defineCommand({
	help: {
		summary: "Copy the agent skills from installed Prisma packages into this project",
		description: "Skills come from the Prisma packages the project installs, so they always describe the version in use. Sync copies them into the skill directories the agent harnesses read, and removes copies whose package is gone. It does nothing, and exits 0, when everything is already current.",
		examples: ["skills sync", "skills sync --disable"]
	},
	needs: { config: skillsConfigSection },
	args: { flags: {
		disable: flag.boolean({ brief: "Stop other commands reporting out-of-date skills in this project" }),
		enable: flag.boolean({ brief: "Undo --disable for this project" })
	} },
	handler: async (args, ctx) => {
		if (args.flags.disable && args.flags.enable) return notOk(bothSwitchesError());
		const outcome = await syncSkills(await readSkillsStatus(ctx.cwd, { agents: ctx.config.agents }));
		let optedOut = outcome.checkDisabled;
		if (args.flags.disable || args.flags.enable) {
			optedOut = args.flags.disable;
			await writeSkillsCheckDisabled(outcome.projectRoot, optedOut);
		}
		const checkDisabled = optedOut || !ctx.config.check;
		const result = {
			projectRoot: outcome.projectRoot,
			agents: ctx.config.agents,
			packages: packageReports(outcome.packages),
			skills: outcome.skills,
			synced: outcome.synced,
			pruned: outcome.pruned,
			refused: outcome.refused,
			checkDisabled
		};
		return ok(ctx.present({
			data: result,
			diagnostics: [...versionConflictDiagnostics(outcome.packages), ...unmanagedDirectoryDiagnostics(outcome.refused)]
		}, syncPresentations(result)));
	}
});
//#endregion
//#region src/commands/init.ts
const POSTINSTALL_SCRIPT = "prisma skills sync || exit 0";
function summary(status, text) {
	return {
		kind: "summary",
		status,
		text
	};
}
const APPEND_ADVICE = {
	kind: "user-choice",
	label: `Append "${POSTINSTALL_SCRIPT}" to your postinstall script yourself to resync the skills on every install.`
};
function addDependencyAdvice(version) {
	return {
		kind: "user-choice",
		label: `Add "prisma": "${version}" to devDependencies yourself, then run your package manager's install.`
	};
}
function noPackageJsonDiagnostic(version) {
	return {
		code: "INIT.NO_PACKAGE_JSON",
		severity: "warn",
		summary: "There is no package.json in this directory, so the postinstall hook and the prisma dev dependency were not added.",
		nextActions: [{
			kind: "user-choice",
			label: `Run ${CLI_NAME} init from the directory that holds your package.json.`
		}, addDependencyAdvice(version)]
	};
}
function unreadablePackageJsonDiagnostic(version) {
	return {
		code: "INIT.PACKAGE_JSON_UNREADABLE",
		severity: "warn",
		summary: "package.json could not be parsed, so the postinstall hook and the prisma dev dependency were not added.",
		nextActions: [APPEND_ADVICE, addDependencyAdvice(version)]
	};
}
function unwritablePackageJsonDiagnostic(hookNeeded, dependencyNeeded, version) {
	return {
		code: "INIT.PACKAGE_JSON_UNWRITABLE",
		severity: "warn",
		summary: "package.json could not be written, so init left it unchanged.",
		nextActions: [...hookNeeded ? [APPEND_ADVICE] : [], ...dependencyNeeded ? [addDependencyAdvice(version)] : []]
	};
}
function scriptsNotAnObjectDiagnostic(dependencyNeeded, version) {
	return {
		code: "INIT.SCRIPTS_NOT_AN_OBJECT",
		severity: "warn",
		summary: "The scripts field in package.json is not an object, so init left it alone.",
		nextActions: [APPEND_ADVICE, ...dependencyNeeded ? [addDependencyAdvice(version)] : []]
	};
}
function foreignPostinstallDiagnostic(dependencyNeeded, version) {
	return {
		code: "INIT.POSTINSTALL_KEPT",
		severity: "warn",
		summary: "package.json already has a postinstall script, so init left it alone.",
		nextActions: [APPEND_ADVICE, ...dependencyNeeded ? [addDependencyAdvice(version)] : []]
	};
}
function devDependenciesNotAnObjectDiagnostic(version) {
	return {
		code: "INIT.DEV_DEPENDENCIES_NOT_AN_OBJECT",
		severity: "warn",
		summary: "The devDependencies field in package.json is not an object, so init did not add the prisma dev dependency.",
		nextActions: [{
			kind: "user-choice",
			label: `Fix the devDependencies field so it is an object, then add "prisma": "${version}" yourself and run your package manager's install.`
		}]
	};
}
function configSnippet(agents) {
	return `skills: { agents: [${agents.map((agent) => `"${agent}"`).join(", ")}] }`;
}
/** The same never-touch discipline as the postinstall step's
*  foreign-script rule: a prisma.config.ts the user already has is
*  theirs, and init only says what to add. */
function configKeptDiagnostic(agents) {
	return {
		code: "INIT.CONFIG_KEPT",
		severity: "warn",
		summary: "prisma.config.ts already exists, so init left it alone instead of writing the skills section.",
		nextActions: [{
			kind: "user-choice",
			label: `Add ${configSnippet(agents)} to the object passed to definePrismaConfig in prisma.config.ts.`
		}]
	};
}
function configUnwritableDiagnostic(agents) {
	return {
		code: "INIT.CONFIG_UNWRITABLE",
		severity: "warn",
		summary: "prisma.config.ts could not be written, so init skipped it.",
		nextActions: [{
			kind: "user-choice",
			label: `Create a prisma.config.ts whose definePrismaConfig call carries ${configSnippet(agents)}.`
		}]
	};
}
function skillsSyncFailedDiagnostic(cause) {
	return {
		code: "INIT.SKILLS_SYNC_FAILED",
		severity: "warn",
		summary: `The agent skills could not be synced: ${cause instanceof Error ? cause.message : String(cause)}`,
		nextActions: [{
			kind: "run-command",
			label: "Retry the sync on its own",
			command: `${CLI_NAME} skills sync`
		}]
	};
}
const FIRST_INDENT = /\n([ \t]+)"/;
/** The indentation the file already uses, so the rewrite matches it. */
function detectIndent(source) {
	return FIRST_INDENT.exec(source)?.[1] ?? "  ";
}
const BOM = "";
function isPlainObject(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function parseManifestObject(source) {
	try {
		const parsed = JSON.parse(source);
		return isPlainObject(parsed) ? parsed : null;
	} catch {
		return null;
	}
}
function renderManifest(manifest, source, bom, crlf) {
	let rewritten = JSON.stringify(manifest, null, detectIndent(source));
	if (crlf) rewritten = rewritten.replaceAll("\n", "\r\n");
	const eol = crlf ? "\r\n" : "\n";
	return `${bom}${rewritten}${source.endsWith("\n") ? eol : ""}`;
}
const DEPENDENCY_FIELDS = [
	"dependencies",
	"devDependencies",
	"optionalDependencies",
	"peerDependencies"
];
/** A declaration in any field, at any version or range, counts — init
*  never second-guesses a version the user chose. */
function declaresPrisma(manifest) {
	return DEPENDENCY_FIELDS.some((field) => {
		const value = manifest[field];
		return isPlainObject(value) && Object.hasOwn(value, "prisma");
	});
}
async function addPostinstallHook(cwd) {
	const manifestPath = path.join(cwd, "package.json");
	const version = getCliVersion();
	let raw;
	try {
		raw = await readFile(manifestPath, "utf8");
	} catch {
		return {
			report: {
				outcome: "skipped",
				script: null,
				dependency: "skipped"
			},
			lines: [summary("warn", "No package.json here; postinstall hook skipped.")],
			diagnostics: [noPackageJsonDiagnostic(version)]
		};
	}
	const bom = raw.startsWith(BOM) ? BOM : "";
	const source = bom === "" ? raw : raw.slice(1);
	const crlf = source.includes("\r\n");
	const manifest = parseManifestObject(source);
	if (manifest === null) return {
		report: {
			outcome: "skipped",
			script: null,
			dependency: "skipped"
		},
		lines: [summary("warn", "package.json could not be parsed; postinstall hook skipped.")],
		diagnostics: [unreadablePackageJsonDiagnostic(version)]
	};
	const dependencyDeclared = declaresPrisma(manifest);
	const keptDependency = dependencyDeclared ? "declared" : "skipped";
	if (manifest.scripts !== void 0 && !isPlainObject(manifest.scripts)) return {
		report: {
			outcome: "kept",
			script: null,
			dependency: keptDependency
		},
		lines: [summary("warn", "The scripts field in package.json is not an object; left untouched.")],
		diagnostics: [scriptsNotAnObjectDiagnostic(!dependencyDeclared, version)]
	};
	const scripts = manifest.scripts ?? {};
	const existing = scripts.postinstall;
	if (existing !== void 0 && existing !== "prisma skills sync || exit 0") return {
		report: {
			outcome: "kept",
			script: typeof existing === "string" ? existing : null,
			dependency: keptDependency
		},
		lines: [summary("warn", "package.json has its own postinstall script; left untouched.")],
		diagnostics: [foreignPostinstallDiagnostic(!dependencyDeclared, version)]
	};
	return writeHookAndDependency({
		cwd,
		manifestPath,
		manifest,
		scripts,
		source,
		bom,
		crlf,
		hookNeeded: existing === void 0,
		dependencyNeeded: !dependencyDeclared
	});
}
async function writeHookAndDependency(edit) {
	const { manifest, hookNeeded, dependencyNeeded } = edit;
	const version = getCliVersion();
	const dependencyBlocked = dependencyNeeded && manifest.devDependencies !== void 0 && !isPlainObject(manifest.devDependencies);
	const block = dependencyBlock(dependencyBlocked, version);
	const dependencyEdit = dependencyNeeded && !dependencyBlocked;
	if (!hookNeeded && !dependencyEdit) return {
		report: {
			outcome: "exists",
			script: POSTINSTALL_SCRIPT,
			dependency: block.kept
		},
		lines: [ALREADY_HOOKED, ...block.lines],
		diagnostics: block.diagnostics
	};
	if (hookNeeded) manifest.scripts = {
		...edit.scripts,
		postinstall: POSTINSTALL_SCRIPT
	};
	if (dependencyEdit) manifest.devDependencies = {
		...isPlainObject(manifest.devDependencies) ? manifest.devDependencies : {},
		prisma: version
	};
	try {
		await writeFile(edit.manifestPath, renderManifest(manifest, edit.source, edit.bom, edit.crlf), "utf8");
	} catch {
		return {
			report: {
				outcome: hookNeeded ? "skipped" : "exists",
				script: hookNeeded ? null : POSTINSTALL_SCRIPT,
				dependency: dependencyEdit ? "skipped" : block.kept
			},
			lines: [summary("warn", "package.json could not be written; left unchanged."), ...block.lines],
			diagnostics: [unwritablePackageJsonDiagnostic(hookNeeded, dependencyEdit, version), ...block.diagnostics]
		};
	}
	return manifestEditedStep(edit, dependencyEdit, block, version);
}
const ALREADY_HOOKED = {
	kind: "summary",
	status: "info",
	text: "The postinstall hook is already in package.json."
};
/** What a blocked (non-object) devDependencies field contributes to
*  every outcome: its warning line, its diagnostic, and the outcome the
*  untouched dependency reports. */
function dependencyBlock(blocked, version) {
	if (!blocked) return {
		lines: [],
		diagnostics: [],
		kept: "declared"
	};
	return {
		lines: [summary("warn", "The devDependencies field in package.json is not an object; prisma was not added.")],
		diagnostics: [devDependenciesNotAnObjectDiagnostic(version)],
		kept: "skipped"
	};
}
/** Detection walks ancestor package.json and lockfiles, and runs after
*  the manifest edit already landed — a directory it cannot read must
*  not fail the init, and guessing npm would name the wrong manager, so
*  the fallback advice names none. The same guard the post-login tip
*  puts around this walk. */
function installAddedDependencyAction(cwd) {
	try {
		return {
			kind: "run-command",
			label: "Install the added prisma dev dependency",
			command: resolveInstallCommandSync(cwd)
		};
	} catch {
		return {
			kind: "user-choice",
			label: "Run your package manager's install to fetch the added prisma dev dependency."
		};
	}
}
function manifestEditedStep(edit, dependencyEdit, block, version) {
	const { hookNeeded } = edit;
	return {
		report: {
			outcome: hookNeeded ? "added" : "exists",
			script: POSTINSTALL_SCRIPT,
			dependency: dependencyEdit ? "added" : block.kept
		},
		lines: [
			hookNeeded ? summary("ok", `Added "postinstall": "${POSTINSTALL_SCRIPT}" to package.json.`) : ALREADY_HOOKED,
			...dependencyEdit ? [summary("ok", `Added "prisma": "${version}" to devDependencies in package.json.`)] : [],
			...block.lines
		],
		next: dependencyEdit ? [installAddedDependencyAction(edit.cwd)] : [],
		diagnostics: block.diagnostics
	};
}
/** What the scaffold contains: the effective agents list, spelled the
*  way a user would write it by hand. */
function renderConfigScaffold(agents) {
	return [
		"import { definePrismaConfig } from \"prisma/config\";",
		"",
		"export default definePrismaConfig({",
		"  skills: {",
		`    agents: [${agents.map((agent) => `"${agent}"`).join(", ")}],`,
		"  },",
		"});",
		""
	].join("\n");
}
async function scaffoldConfigStep(cwd, agents, agentsConfigured) {
	const configPath = path.join(cwd, "prisma.config.ts");
	let existing = null;
	try {
		existing = await readFile(configPath, "utf8");
	} catch {
		existing = null;
	}
	if (existing !== null) return {
		report: {
			outcome: "exists",
			agents: null
		},
		lines: [agentsConfigured ? summary("info", "prisma.config.ts already configures skills.agents.") : summary("warn", "prisma.config.ts already exists; left untouched.")],
		diagnostics: agentsConfigured ? [] : [configKeptDiagnostic(agents)]
	};
	try {
		await writeFile(configPath, renderConfigScaffold(agents), {
			encoding: "utf8",
			flag: "wx"
		});
	} catch {
		return {
			report: {
				outcome: "skipped",
				agents: null
			},
			lines: [summary("warn", "prisma.config.ts could not be written; skipped.")],
			diagnostics: [configUnwritableDiagnostic(agents)]
		};
	}
	return {
		report: {
			outcome: "created",
			agents
		},
		lines: [summary("ok", `Created prisma.config.ts with ${configSnippet(agents)}.`)],
		diagnostics: []
	};
}
function skillsOutcome(result) {
	if (result.synced.length > 0 || result.pruned.length > 0) return "synced";
	if (result.agents.length === 0) return "no-agents";
	if (result.packages.length === 0) return "no-packages";
	if (result.skills.length === 0) return "no-skills";
	return "up-to-date";
}
async function syncSkillsStep(cwd, agents, checkEnabledByConfig) {
	try {
		const outcome = await syncSkills(await readSkillsStatus(cwd, { agents }));
		const result = {
			projectRoot: outcome.projectRoot,
			agents,
			packages: packageReports(outcome.packages),
			skills: outcome.skills,
			synced: outcome.synced,
			pruned: outcome.pruned,
			refused: outcome.refused,
			checkDisabled: outcome.checkDisabled || !checkEnabledByConfig
		};
		return {
			report: {
				outcome: skillsOutcome(result),
				sync: result
			},
			lines: null,
			diagnostics: [...versionConflictDiagnostics(outcome.packages), ...unmanagedDirectoryDiagnostics(outcome.refused)]
		};
	} catch (cause) {
		return {
			report: {
				outcome: "failed",
				sync: null
			},
			lines: [summary("warn", "The agent skills could not be synced.")],
			diagnostics: [skillsSyncFailedDiagnostic(cause)]
		};
	}
}
const SKIPPED_POSTINSTALL = {
	report: {
		outcome: "skipped",
		script: null,
		dependency: "skipped"
	},
	lines: [summary("info", "Skipped the postinstall hook (--no-postinstall).")],
	diagnostics: []
};
const SKIPPED_SKILLS = {
	report: {
		outcome: "skipped",
		sync: null
	},
	lines: [summary("info", "Skipped the skills sync (--skills=none).")],
	diagnostics: []
};
const SKIP_SENTINEL = "none";
function invalidSkillsFlagError(problem) {
	return new CliStructuredError("CLI.INVALID_ARGUMENTS", problem, { nextActions: [{
		kind: "user-choice",
		label: `Pass --skills a comma-separated list of agents (${KNOWN_AGENTS.join(", ")}), or --skills=${SKIP_SENTINEL} to record that no agent skills are wanted.`
	}] });
}
/** `--skills`: absent defers to the config's agents (every known agent
*  when there is no config); `none` records the choice — the scaffold
*  gets `agents: []` and the sync is skipped; otherwise a
*  comma-separated list of agent names. */
function parseSkillsFlag(raw, configured) {
	if (raw === void 0) return {
		kind: "agents",
		agents: configured
	};
	const names = raw.split(",").map((name) => name.trim()).filter((name) => name !== "");
	if (names.includes(SKIP_SENTINEL)) return names.length === 1 ? { kind: "skip" } : {
		kind: "invalid",
		error: invalidSkillsFlagError(`--skills=${SKIP_SENTINEL} records that no agent skills are wanted, so it cannot be combined with agent names.`)
	};
	const agents = [];
	for (const name of names) {
		if (!isKnownAgent(name)) return {
			kind: "invalid",
			error: invalidSkillsFlagError(`--skills names '${name}', which this CLI does not know. The known agents are ${KNOWN_AGENTS.join(", ")}.`)
		};
		if (!agents.includes(name)) agents.push(name);
	}
	if (agents.length === 0) return {
		kind: "invalid",
		error: invalidSkillsFlagError("--skills was given no agent names.")
	};
	return {
		kind: "agents",
		agents
	};
}
function initPresentations(result, postinstall, config, skills) {
	return {
		json: () => result,
		next: () => postinstall.next ?? [],
		stdout: () => [],
		human: (ui) => {
			const skillsBlocks = result.skills.sync === null ? [] : syncPresentations(result.skills.sync).human(ui);
			return [
				...postinstall.lines ?? [],
				...config.lines ?? [],
				...skills.lines ?? skillsBlocks
			];
		}
	};
}
const initCommand = defineCommand({
	help: {
		summary: "Prepare this repository for Prisma development",
		description: "Runs locally and calls no platform API. Adds a postinstall script to package.json that keeps the Prisma agent skills in sync on every install, adds prisma to devDependencies at this CLI's exact version when no dependency field declares it, scaffolds a prisma.config.ts recording which agents to install skills for, then syncs the skills once now. Everything lands in the current directory; a prisma.config.ts or postinstall script that already exists is never edited. Rerunning is safe: each step reports what is already done.",
		examples: [
			"init",
			"init --skills=claude,cursor",
			"init --skills=none",
			"init --no-postinstall"
		]
	},
	needs: { config: skillsConfigSection },
	args: { flags: {
		postinstall: flag.optionalBoolean({ brief: "Add the skills-sync postinstall hook (--no-postinstall skips)" }),
		skills: flag.string({
			brief: `Agents to install skills for (comma-separated: ${KNOWN_AGENTS.join(", ")}); '${SKIP_SENTINEL}' records that no agent skills are wanted`,
			placeholder: "agents"
		})
	} },
	handler: async (args, ctx) => {
		const skillsFlag = parseSkillsFlag(args.flags.skills, ctx.config.agents);
		if (skillsFlag.kind === "invalid") return notOk(skillsFlag.error);
		const postinstall = args.flags.postinstall === false ? SKIPPED_POSTINSTALL : await addPostinstallHook(ctx.cwd);
		const config = await scaffoldConfigStep(ctx.cwd, skillsFlag.kind === "skip" ? [] : skillsFlag.agents, ctx.config.agentsConfigured);
		const skills = skillsFlag.kind === "skip" ? SKIPPED_SKILLS : await syncSkillsStep(ctx.cwd, skillsFlag.agents, ctx.config.check);
		const result = {
			postinstall: postinstall.report,
			config: config.report,
			skills: skills.report
		};
		return ok(ctx.present({
			data: result,
			diagnostics: [
				...postinstall.diagnostics,
				...config.diagnostics,
				...skills.diagnostics
			]
		}, initPresentations(result, postinstall, config, skills)));
	}
});
//#endregion
//#region src/controllers/database.ts
const USAGE_DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
const USAGE_DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
const LIST_DATABASES_COMMAND = `${CLI_NAME} postgres list`;
/** The corrected `postgres usage` form both period errors point at. */
const USAGE_PERIOD_EXAMPLE_COMMAND = `${CLI_NAME} postgres usage <database> --from 2026-06-01 --to 2026-06-30`;
function userChoice$2(label) {
	return {
		kind: "user-choice",
		label
	};
}
function runCommand$2(command) {
	return {
		kind: "run-command",
		label: command,
		command
	};
}
function parseUsageDate(value, flagName, dayBoundary) {
	if (value === void 0) return;
	const trimmed = value.trim();
	if (USAGE_DATE_ONLY_PATTERN.test(trimmed) && isValidCalendarDate(trimmed)) return dayBoundary === "start" ? `${trimmed}T00:00:00.000Z` : `${trimmed}T23:59:59.999Z`;
	if (USAGE_DATETIME_PATTERN.test(trimmed) && !Number.isNaN(Date.parse(trimmed)) && isValidCalendarDate(trimmed.slice(0, 10))) return trimmed;
	throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid usage period", {
		why: `${flagName} must be an ISO date such as 2026-06-01 or an ISO datetime such as 2026-06-01T12:00:00Z.`,
		nextActions: [userChoice$2(`Pass an ISO date or datetime to ${flagName}.`), runCommand$2(USAGE_PERIOD_EXAMPLE_COMMAND)]
	});
}
function isValidCalendarDate(datePart) {
	const timestamp = Date.parse(`${datePart}T00:00:00.000Z`);
	return !Number.isNaN(timestamp) && new Date(timestamp).toISOString().startsWith(datePart);
}
function parseBackupLimit(value) {
	if (value === void 0) return;
	const limit = Number(value.trim());
	if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid backup limit", {
		why: "--limit must be an integer between 1 and 100.",
		nextActions: [userChoice$2("Pass a --limit between 1 and 100."), runCommand$2(`${CLI_NAME} postgres backup list <database> --limit 50`)]
	});
	return limit;
}
async function resolveDatabase(provider, target, databaseRef, branchName, signal) {
	const ref = databaseRef.trim();
	if (!ref) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Database id or name required", {
		why: "This command needs a database id or name.",
		nextActions: [userChoice$2("Pass a database id or name."), runCommand$2(LIST_DATABASES_COMMAND)]
	});
	const databases = await provider.listDatabases({
		projectId: target.project.id,
		branchName,
		signal
	});
	const byId = databases.filter((database) => database.id === ref);
	const matches = byId.length > 0 ? byId : databases.filter((database) => database.name === ref);
	if (matches.length === 0) throw databaseNotFoundError(ref, target.project.name, branchName);
	if (matches.length > 1) throw databaseAmbiguousError(ref, matches, branchName);
	const selected = matches[0];
	const shown = await provider.showDatabase(selected.id, {
		projectId: target.project.id,
		signal
	});
	if (shown === null) throw databaseRemovedDuringResolutionError(selected, target.project.name);
	return ensureProjectId(shown, target.project.id);
}
function databaseRemovedDuringResolutionError(database, projectName) {
	return new CliStructuredError("POSTGRES.NOT_FOUND", "Database not found", {
		why: `"${database.name}" (${database.id}) was listed for project "${projectName}", but reading it returned 404. It was most likely removed while this command was running.`,
		nextActions: [userChoice$2("Re-run the command, or list the project's databases to see what is there now."), runCommand$2(LIST_DATABASES_COMMAND)]
	});
}
function ensureProjectId(database, projectId) {
	return database.projectId ? database : {
		...database,
		projectId
	};
}
function sortDatabases(databases) {
	return databases.slice().sort((left, right) => {
		const branchOrder = (left.branchName ?? "").localeCompare(right.branchName ?? "");
		if (branchOrder !== 0) return branchOrder;
		const nameOrder = left.name.localeCompare(right.name);
		return nameOrder !== 0 ? nameOrder : left.id.localeCompare(right.id);
	});
}
function defaultConnectionName() {
	return `cli-${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 17)}-${randomBytes(2).toString("hex")}`;
}
function databaseNotFoundError(databaseRef, projectName, branchName) {
	return new CliStructuredError("POSTGRES.NOT_FOUND", "Database not found", {
		why: `No database matched "${databaseRef}"${projectName ? ` in project "${projectName}"${branchName ? ` on branch "${branchName}"` : ""}` : ""}.`,
		nextActions: [userChoice$2(`Pass a database id or name from ${LIST_DATABASES_COMMAND}.`), runCommand$2(LIST_DATABASES_COMMAND)]
	});
}
function databaseAmbiguousError(databaseRef, matches, branchName) {
	return new CliStructuredError("POSTGRES.AMBIGUOUS", "Database resolution is ambiguous", {
		why: branchName ? `Multiple databases matched "${databaseRef}" on branch "${branchName}".` : `Multiple databases matched "${databaseRef}".`,
		meta: { matches: matches.map((database) => ({
			id: database.id,
			name: database.name,
			branchName: database.branchName
		})) },
		nextActions: [userChoice$2("Pass the database id, or pass --branch <git-name> to narrow the match."), runCommand$2(LIST_DATABASES_COMMAND)]
	});
}
//#endregion
//#region src/presenters/database.ts
function serializeDatabaseList(result) {
	return {
		...serializeList({
			context: {
				project: result.projectName,
				...result.branchName ? { branch: result.branchName } : {}
			},
			items: result.databases.map((database) => ({
				noun: "database",
				label: database.name,
				id: database.id,
				status: database.isDefault ? "default" : null
			}))
		}),
		projectId: result.projectId,
		branchName: result.branchName,
		databases: result.databases
	};
}
function serializeDatabaseConnectionList(result) {
	return {
		...serializeList({
			context: {
				project: result.projectName,
				database: result.database.name
			},
			items: result.connections.map((connection) => ({
				noun: "connection",
				label: connection.name,
				id: connection.id,
				status: null
			}))
		}),
		projectId: result.projectId,
		database: result.database,
		connections: result.connections
	};
}
function serializeDatabaseBackupList(result) {
	return {
		...serializeList({
			context: {
				project: result.projectName,
				database: result.database.name
			},
			items: result.backups.map((backup) => ({
				noun: "backup",
				label: backup.id,
				id: backup.id,
				status: null
			}))
		}),
		projectId: result.projectId,
		database: result.database,
		backups: result.backups,
		retentionDays: result.retentionDays,
		hasMore: result.hasMore
	};
}
//#endregion
//#region src/lib/database/provider.ts
const SUBSCRIPTION_LOOKUP_TIMEOUT_MS = 3e3;
const VERBOSE_LOG_FIX = "Re-run with --log-level verbose for the underlying API response details.";
function userChoice$1(label) {
	return {
		kind: "user-choice",
		label
	};
}
function runCommand$1(command) {
	return {
		kind: "run-command",
		label: command,
		command
	};
}
function createManagementDatabaseProvider(client, options) {
	const toDatabaseApiError = (summary, response, error, signal) => databaseApiError({
		client,
		workspaceId: options?.workspaceId,
		summary,
		response,
		error,
		signal
	});
	return {
		async listDatabases(options) {
			const databases = [];
			let cursor;
			while (true) {
				const result = await client.GET("/v1/databases", {
					params: { query: {
						projectId: options.projectId,
						branchGitName: options.branchName,
						cursor
					} },
					signal: options.signal
				});
				if (result.error || !result.data) throw await toDatabaseApiError("Failed to list databases", result.response, result.error, options.signal);
				databases.push(...result.data.data);
				if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
				cursor = result.data.pagination.nextCursor;
			}
			return databases.map((database) => normalizeDatabase(database, options.projectId));
		},
		async showDatabase(databaseId, options) {
			const result = await client.GET("/v1/databases/{databaseId}", {
				params: { path: { databaseId } },
				signal: options?.signal
			});
			if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) return null;
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to show database", result.response, result.error, options?.signal);
			const database = result.data.data;
			return normalizeDatabase(database, requireDatabaseProjectId(database, options?.projectId));
		},
		async createDatabase(options) {
			const result = await client.POST("/v1/databases", {
				body: {
					projectId: options.projectId,
					name: options.name,
					source: { type: "empty" },
					...options.branchName ? { branchGitName: options.branchName } : {},
					...options.region ? { region: options.region } : {}
				},
				signal: options.signal
			});
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to create database", result.response, result.error, options.signal);
			return normalizeCreatedDatabase(result.data.data, options.projectId);
		},
		async removeDatabase(databaseId, options) {
			const result = await client.DELETE("/v1/databases/{databaseId}", {
				params: { path: { databaseId } },
				signal: options?.signal
			});
			if (result.error) throw await toDatabaseApiError("Failed to delete database", result.response, result.error, options?.signal);
		},
		async listConnections(databaseId, options) {
			const result = await client.GET("/v1/databases/{databaseId}/connections", {
				params: { path: { databaseId } },
				signal: options?.signal
			});
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to list database connections", result.response, result.error, options?.signal);
			return result.data.data.map((connection) => normalizeConnection(connection, databaseId));
		},
		async createConnection(options) {
			const result = await client.POST("/v1/databases/{databaseId}/connections", {
				params: { path: { databaseId: options.databaseId } },
				body: { name: options.name },
				signal: options.signal
			});
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to create database connection", result.response, result.error, options.signal);
			return normalizeCreatedConnection(result.data.data, options.databaseId);
		},
		async removeConnection(connectionId, options) {
			const result = await client.DELETE("/v1/connections/{id}", {
				params: { path: { id: connectionId } },
				signal: options?.signal
			});
			if (result.error) throw await toDatabaseApiError("Failed to delete database connection", result.response, result.error, options?.signal);
		},
		async getUsage(databaseId, options) {
			const result = await client.GET("/v1/databases/{databaseId}/usage", {
				params: {
					path: { databaseId },
					query: {
						...options?.from ? { startDate: options.from } : {},
						...options?.to ? { endDate: options.to } : {}
					}
				},
				signal: options?.signal
			});
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to fetch database usage", result.response, result.error, options?.signal);
			return normalizeUsage(result.data);
		},
		async listBackups(databaseId, options) {
			const result = await client.GET("/v1/databases/{databaseId}/backups", {
				params: {
					path: { databaseId },
					query: { ...options?.limit !== void 0 ? { limit: options.limit } : {} }
				},
				signal: options?.signal
			});
			if (result.response?.status === 422 && !isPlanLimitApiError(result.error)) throw backupsUnsupportedError(databaseId, result.error);
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to list database backups", result.response, result.error, options?.signal);
			return normalizeBackupList(result.data);
		},
		async restoreDatabase(options) {
			const result = await client.POST("/v1/databases/{targetDatabaseId}/restore", {
				params: { path: { targetDatabaseId: options.targetDatabaseId } },
				body: { source: {
					type: "backup",
					databaseId: options.sourceDatabaseId,
					backupId: options.backupId
				} },
				signal: options.signal
			});
			if (result.response?.status === 409 && !isPlanLimitApiError(result.error)) throw restoreConflictError(options.targetDatabaseId, result.error);
			if (result.response?.status === 404 && !isPlanLimitApiError(result.error)) throw restoreBackupNotFoundError(options, result.error);
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to restore database", result.response, result.error, options.signal);
			return normalizeDatabase(result.data.data, options.projectId);
		},
		async rotateConnection(connectionId, options) {
			const result = await client.POST("/v1/connections/{id}/rotate", {
				params: { path: { id: connectionId } },
				signal: options?.signal
			});
			if (result.error || !result.data) throw await toDatabaseApiError("Failed to rotate database connection", result.response, result.error, options?.signal);
			return normalizeRotatedConnection(result.data.data);
		}
	};
}
function normalizeDatabase(database, fallbackProjectId) {
	return {
		id: database.id,
		name: database.name,
		projectId: database.projectId ?? fallbackProjectId,
		branchId: database.branchId ?? database.branch?.id ?? null,
		branchName: database.branchGitName ?? database.branchName ?? database.branch?.gitName ?? database.branch?.name ?? null,
		region: normalizeRegion(database),
		status: database.status ?? null,
		isDefault: database.isDefault ?? null,
		createdAt: database.createdAt ?? null
	};
}
function normalizeConnection(connection, fallbackDatabaseId) {
	return {
		id: connection.id,
		name: connection.name ?? connection.id,
		databaseId: connection.databaseId ?? fallbackDatabaseId,
		createdAt: connection.createdAt ?? null
	};
}
function normalizeCreatedDatabase(database, fallbackProjectId) {
	const rawConnection = database.connections?.[0];
	if (!rawConnection) throw new CliStructuredError("POSTGRES.CONNECTION_MISSING", "Created database did not return a connection string", {
		why: "The Management API created the database but did not include the one-time connection payload.",
		nextActions: [userChoice$1(`Create a connection explicitly with ${CLI_NAME} postgres connection create <database>.`), runCommand$1(`${CLI_NAME} postgres connection create ${database.id}`)]
	});
	return {
		database: normalizeDatabase(database, fallbackProjectId),
		...normalizeCreatedConnection(rawConnection, database.id)
	};
}
function normalizeCreatedConnection(connection, fallbackDatabaseId) {
	const connectionString = extractConnectionString(connection);
	if (!connectionString) throw new CliStructuredError("POSTGRES.CONNECTION_STRING_MISSING", "Created connection did not return a connection string", {
		why: "Database connection strings are one-time-view secrets, but the Management API did not include one in this create response.",
		nextActions: [userChoice$1("Create another database connection and store the returned URL immediately."), runCommand$1(`${CLI_NAME} postgres connection create ${fallbackDatabaseId}`)]
	});
	return {
		connection: normalizeConnection(connection, fallbackDatabaseId),
		connectionString
	};
}
function normalizeRegion(database) {
	if (typeof database.region === "string") return database.region;
	return database.region?.id ?? database.regionId ?? null;
}
function requireDatabaseProjectId(database, fallbackProjectId) {
	const projectId = database.projectId ?? fallbackProjectId;
	if (projectId) return projectId;
	throw new CliStructuredError("POSTGRES.API_ERROR", "Database response did not include a project id", {
		why: "The Management API returned database metadata without project context.",
		nextActions: [userChoice$1(VERBOSE_LOG_FIX)]
	});
}
function extractConnectionString(connection) {
	return connection.endpoints?.pooled?.connectionString ?? connection.connectionString ?? connection.endpoints?.direct?.connectionString ?? connection.endpoints?.accelerate?.connectionString ?? null;
}
/** Absence is carried, not filled in. A `0` here reached the card, the
*  stdout lane and the json record as though the API had measured it, and
*  "you used nothing" is the answer a user acts on. The unit is the same
*  problem an order of magnitude worse: `GiB` was a guess, and a value the
*  API denominates in bytes would print against it unchanged. */
function normalizeUsageMetric(metric) {
	return {
		used: metric?.used ?? null,
		unit: metric?.unit ?? null
	};
}
function normalizeUsage(usage) {
	return {
		period: {
			start: usage.period?.start ?? null,
			end: usage.period?.end ?? null
		},
		metrics: {
			operations: normalizeUsageMetric(usage.metrics?.operations),
			storage: normalizeUsageMetric(usage.metrics?.storage)
		},
		generatedAt: usage.generatedAt ?? null
	};
}
function normalizeBackupList(body) {
	return {
		backups: (body.data ?? []).map((backup) => ({
			id: backup.id,
			backupType: backup.backupType ?? "",
			status: backup.status ?? "",
			size: backup.size ?? null,
			createdAt: backup.createdAt ?? ""
		})),
		retentionDays: body.meta?.backupRetentionDays ?? null,
		hasMore: body.pagination?.hasMore ?? false
	};
}
function normalizeRotatedConnection(connection) {
	const connectionString = extractConnectionString(connection);
	if (!connectionString) throw new CliStructuredError("POSTGRES.CONNECTION_STRING_MISSING", "Rotated connection did not return a connection string", {
		why: "Rotated connection strings are one-time-view secrets, but the Management API did not include one in this rotate response.",
		nextActions: [userChoice$1("Re-run the rotation, or create a replacement connection and store the returned URL immediately.")]
	});
	const database = connection.database?.id && connection.database?.name ? {
		id: connection.database.id,
		name: connection.database.name
	} : null;
	return {
		connection: normalizeConnection(connection, connection.database?.id ?? connection.databaseId ?? ""),
		database,
		connectionString
	};
}
function backupsUnsupportedError(databaseId, error) {
	return new CliStructuredError("POSTGRES.BACKUPS_UNSUPPORTED", "Backups are not available for this database", {
		why: error?.error?.message ?? `The platform does not manage backups for database "${databaseId}", for example because it is a remote/BYO database.`,
		nextActions: [userChoice$1("Use your own backup tooling for externally managed databases.")]
	});
}
function restoreBackupNotFoundError(options, error) {
	const listCommand = `${CLI_NAME} postgres backup list ${options.sourceDatabaseId}`;
	return new CliStructuredError("POSTGRES.BACKUP_NOT_FOUND", "Database backup not found", {
		why: error?.error?.message ?? `No backup matched "${options.backupId}" for database "${options.sourceDatabaseId}".`,
		nextActions: [userChoice$1(`Pass a backup id from ${listCommand}.`), runCommand$1(listCommand)]
	});
}
function restoreConflictError(targetDatabaseId, error) {
	return new CliStructuredError("POSTGRES.RESTORE_CONFLICT", "Database cannot be restored right now", {
		why: error?.error?.message ?? `Database "${targetDatabaseId}" is provisioning or already recovering.`,
		nextActions: [userChoice$1("Wait for the database to become ready, then retry the restore."), runCommand$1(`${CLI_NAME} postgres show ${targetDatabaseId}`)]
	});
}
/** A 401 or 403 is the API refusing the caller, not a database problem. */
function isRejectedCaller(status) {
	return status === 401 || status === 403;
}
function apiErrorWhy(status, message) {
	if (!isRejectedCaller(status)) return message ?? `The Management API returned status ${status || "unknown"}.`;
	const rejection = `The Management API rejected the request as ${status === 401 ? "unauthorized" : "forbidden"}.`;
	return message ? `${rejection} ${message}` : rejection;
}
function apiErrorMeta(status, apiCode) {
	if (!status && apiCode === void 0) return;
	return {
		...status ? { status } : {},
		...apiCode === void 0 ? {} : { apiCode }
	};
}
function apiErrorActions(status, hint) {
	if (!isRejectedCaller(status)) return [userChoice$1(hint ?? VERBOSE_LOG_FIX)];
	return [userChoice$1(hint ?? `Sign in again with prisma auth login, then retry the command.`), runCommand$1(`${CLI_NAME} auth login`)];
}
/**
* Every database Management API failure that is not a plan limit lands
* on the one registered code. The response's own error code is data, not
* an identity: it travels in `meta.apiCode` beside `meta.status` so a
* consumer can still branch on it without the CLI minting a code it
* never registered.
*/
async function databaseApiError(options) {
	if (isPlanLimitApiError(options.error)) return planLimitReachedError(options);
	const status = options.response?.status ?? 0;
	const meta = apiErrorMeta(status, options.error?.error?.code);
	return new CliStructuredError("POSTGRES.API_ERROR", options.summary, {
		why: apiErrorWhy(status, options.error?.error?.message),
		...meta === void 0 ? {} : { meta },
		nextActions: apiErrorActions(status, options.error?.error?.hint)
	});
}
async function planLimitReachedError(options) {
	const subscription = options.workspaceId ? await readWorkspaceSubscription(options.client, options.workspaceId, options.signal) : null;
	const planName = subscription?.planName || null;
	const usageBlocked = subscription?.usageBlocked ?? null;
	const upgradeUrl = subscription?.upgradeUrl || null;
	return new CliStructuredError("POSTGRES.PLAN_LIMIT_REACHED", "Workspace plan limit reached", {
		why: "Database operations are blocked because this workspace has used the operations included in its plan. This is a workspace plan limit, not a Prisma outage.",
		meta: {
			workspaceId: options.workspaceId ?? null,
			blockedFeature: null,
			planName,
			usageBlocked,
			upgradeUrl
		},
		nextActions: [{
			kind: "user-choice",
			label: "Upgrade the workspace plan",
			reason: upgradeUrl ? `Upgrade at ${upgradeUrl}${planName ? ` (current plan: ${planName})` : ""}.` : "Open Prisma Console and upgrade the affected workspace plan."
		}]
	});
}
function isPlanLimitApiError(error) {
	return error?.error?.code === "planLimitReached";
}
async function readWorkspaceSubscription(client, workspaceId, signal) {
	signal?.throwIfAborted();
	const timeoutController = new AbortController();
	const timeout = setTimeout(() => timeoutController.abort(), SUBSCRIPTION_LOOKUP_TIMEOUT_MS);
	const requestSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
	try {
		const result = await client.GET("/v1/workspaces/{id}/subscription", {
			params: { path: { id: workspaceId } },
			signal: requestSignal
		});
		signal?.throwIfAborted();
		if (result.error) return null;
		return result.data?.data ?? null;
	} catch {
		signal?.throwIfAborted();
		return null;
	} finally {
		clearTimeout(timeout);
	}
}
//#endregion
//#region src/commands/postgres/context.ts
/** Workspace, project and provider for the `postgres *` commands. */
const projectFlag$1 = flag.string({
	brief: "Project id or name",
	placeholder: "id-or-name"
});
const branchFlag$1 = flag.string({
	brief: "Branch git name",
	placeholder: "git-name"
});
const databasePositional = positional.string({
	brief: "Database id or name",
	placeholder: "database"
});
async function resolvePostgresContext(ctx, flags, commandName) {
	const workspace = await resolveActiveWorkspace(ctx);
	const target = await resolvePinnedProject(ctx, workspace, flags.project, commandName);
	return {
		provider: createManagementDatabaseProvider(ctx.api, { workspaceId: workspace.id }),
		target,
		projectId: target.project.id,
		projectName: target.project.name
	};
}
/** `connection rotate` and `connection delete` address a connection
*  directly: no workspace requirement and no project resolution, so
*  the workspace is only a plan-limit lookup hint. */
async function resolvePostgresProviderOnly(ctx) {
	const workspaceId = (await ctx.activeCredential())?.workspaceId;
	return createManagementDatabaseProvider(ctx.api, { ...workspaceId === void 0 ? {} : { workspaceId } });
}
//#endregion
//#region src/commands/postgres/presentation.ts
/** Legacy `formatDatabaseTarget`. */
function postgresTargetLabel(projectName, branchName) {
	return branchName ? `${projectName} / ${branchName}` : projectName;
}
/** The human status cell: the database's own status, or the word for a
*  status the API did not report. Whether the database is the project's
*  default is a different fact and never stands in for this one — a
*  reader could not tell that substitution from a real status, and a
*  stopped database would have read as healthy. */
function formatStatus(database) {
	return database.status ?? "unknown";
}
/** The reader's branch cell. "unscoped" is a claim about the database —
*  that it belongs to no branch — so only an absent `branchId` may
*  produce it. The API does not always send a branch name beside the id
*  it does send, and reporting that database as unscoped was wrong. */
function branchLabel(database) {
	return database.branchName ?? database.branchId ?? "unscoped";
}
/** The stdout status cell. The Option A channel ruling makes stdout the
*  machine-usable payload, so it carries the raw status and nothing
*  else: an absent status is an empty field, and `isDefault` is a
*  different fact that does not belong in this one. */
function statusValue(database) {
	return database.status ?? "";
}
/** The human usage cell: the measurement with the unit the API gave it.
*  A metric the API did not report reads "unknown" rather than `0`, and a
*  measurement with no unit prints alone rather than against a unit the
*  CLI picked. */
function formatUsageMetric(metric) {
	if (metric.used === null) return "unknown";
	return metric.unit ? `${metric.used} ${metric.unit}` : String(metric.used);
}
/** The stdout usage cell: the number the API measured, or an empty field
*  when it measured none. */
function usageMetricValue(metric) {
	return metric.used === null ? "" : String(metric.used);
}
/** The human size cell. `formatBackupSize` is for reading; stdout gets
*  the byte count through `backupStdoutRows`, because "2.0 KiB" will
*  not parse back to 2048. */
function formatBackupSize(size) {
	if (size === null) return "unknown";
	if (size < 1024) return `${size} B`;
	if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KiB`;
	if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MiB`;
	return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
}
function backupRows(backups) {
	return backups.map((backup) => [
		backup.id,
		backup.backupType || "unknown",
		backup.status || "unknown",
		formatBackupSize(backup.size),
		backup.createdAt || "unknown"
	]);
}
function backupStdoutRows(backups) {
	return backups.map((backup) => [
		backup.id,
		backup.backupType,
		backup.status,
		backup.size === null ? "" : String(backup.size),
		backup.createdAt || ""
	]);
}
/** The one-time-secret card. The URL prints bare: this card is the only
*  place an interactive user ever sees it. */
function secretBlocks(headline, connectionString) {
	return [
		{
			kind: "summary",
			status: "ok",
			text: headline
		},
		{
			kind: "list",
			items: ["The connection URL below is shown once, so save it now."]
		},
		{
			kind: "fields",
			rows: [{
				label: "connection URL",
				value: connectionString
			}]
		}
	];
}
//#endregion
//#region src/commands/postgres/backup-list.ts
/** The `postgres backup list` command. */
const TITLE$9 = "Listing platform-created database backups.";
function backupListPresentations(result) {
	const rows = backupRows(result.backups);
	const stdoutRows = backupStdoutRows(result.backups);
	return {
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$9
			},
			{
				kind: "fields",
				rows: [{
					label: "database",
					value: result.database.name
				}, ...result.retentionDays !== null ? [{
					label: "retention",
					value: `${result.retentionDays} days`
				}] : []]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No backups found."
			}] : [{
				kind: "table",
				columns: [
					"Id",
					"Type",
					"Status",
					"Size",
					"Created"
				],
				rows
			}],
			...result.hasMore ? [{
				kind: "list",
				items: ["More backups exist; raise --limit to see them."]
			}] : []
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeDatabaseBackupList(result)
	};
}
const postgresBackupListCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			limit: flag.string({
				brief: "Maximum number of backups to return",
				placeholder: "n"
			}),
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "List backups for a database",
		examples: ["postgres backup list db_123", "postgres backup list acme-production --limit 50"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const limit = parseBackupLimit(args.flags.limit);
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup list");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const backups = await provider.listBackups(database.id, {
			limit,
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			database,
			backups: backups.backups,
			retentionDays: backups.retentionDays,
			hasMore: backups.hasMore
		};
		return ok(ctx.present({ data: result }, backupListPresentations(result)));
	}
});
//#endregion
//#region src/commands/postgres/backup-restore.ts
/** The `postgres backup restore` command. */
const CONSENT_QUESTION$5 = "Restoring immediately and irreversibly overwrites all data in the target database, so it requires the exact target database id.";
function restorePresentations(result, sourceDatabaseId, targetDatabaseId) {
	const rows = [
		{
			label: "project",
			value: result.projectName
		},
		{
			label: "database",
			value: result.database.name
		},
		{
			label: "id",
			value: result.database.id
		},
		{
			label: "backup",
			value: result.source.backupId
		},
		...sourceDatabaseId ? [{
			label: "source",
			value: sourceDatabaseId
		}] : []
	];
	return {
		stdout: () => [],
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Restoring database from backup."
			},
			{
				kind: "fields",
				rows
			},
			{
				kind: "list",
				items: [`The restore is running; the database status is "${result.database.status ?? "recovering"}" until it completes.`, "Connections and credentials are preserved."]
			}
		],
		next: () => [{
			kind: "run-command",
			label: `${CLI_NAME} postgres show ${targetDatabaseId}`,
			command: `${CLI_NAME} postgres show ${targetDatabaseId}`
		}]
	};
}
const postgresBackupRestoreCommand = defineCommand({
	args: {
		positionals: { database: positional.string({
			brief: "Target database id or name",
			placeholder: "database"
		}) },
		flags: {
			backup: flag.string({
				brief: "Backup to restore from",
				placeholder: "backup-id"
			}),
			sourceDatabase: flag.string({
				brief: "Database the backup belongs to (defaults to the target)",
				placeholder: "database"
			}),
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Restore a database from a backup after exact id confirmation",
		examples: ["postgres backup restore db_123 --backup bkp_456 --confirm db_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const backupId = args.flags.backup?.trim();
		if (!backupId) {
			const listCommand = `${CLI_NAME} postgres backup list <database>`;
			throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Backup id required", {
				why: "Database restore needs the backup to restore from.",
				nextActions: [{
					kind: "user-choice",
					label: `Pass --backup <backup-id> from ${listCommand}.`
				}, {
					kind: "run-command",
					label: listCommand,
					command: listCommand
				}]
			});
		}
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres backup restore");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const sourceDatabase = args.flags.sourceDatabase ? await resolveDatabase(provider, target, args.flags.sourceDatabase, args.flags.branch, ctx.signal) : database;
		await ctx.prompt.consent(CONSENT_QUESTION$5, { token: database.id });
		const result = {
			projectId,
			projectName,
			database: await provider.restoreDatabase({
				targetDatabaseId: database.id,
				sourceDatabaseId: sourceDatabase.id,
				backupId,
				projectId,
				signal: ctx.signal
			}),
			source: {
				databaseId: sourceDatabase.id,
				backupId
			}
		};
		return ok(ctx.present({ data: result }, restorePresentations(result, sourceDatabase.id === database.id ? null : sourceDatabase.id, database.id)));
	}
});
//#endregion
//#region src/commands/postgres/connection-create.ts
/** The `postgres connection create` command. */
const postgresConnectionCreateCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			name: flag.string({
				brief: "Connection name",
				placeholder: "name"
			}),
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Create a database connection and print its one-time connection URL",
		examples: ["postgres connection create db_123", "postgres connection create db_123 --name readonly"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection create");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const created = await provider.createConnection({
			databaseId: database.id,
			name: args.flags.name?.trim() || defaultConnectionName(),
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			database,
			connection: created.connection,
			connectionString: created.connectionString
		};
		return ok(ctx.present({ data: result }, {
			human: () => secretBlocks(`Added a connection to "${database.name}" in ${postgresTargetLabel(projectName, database.branchName)}.`, result.connectionString),
			stdout: () => [result.connectionString],
			json: () => result,
			next: () => []
		}));
	}
});
//#endregion
//#region src/commands/postgres/connection-delete.ts
/** The `postgres connection delete` command. */
const CONSENT_QUESTION$4 = "Deleting this database connection is destructive and requires the exact id.";
const postgresConnectionDeleteCommand = defineCommand({
	args: { positionals: { connection: positional.string({
		brief: "Connection id",
		placeholder: "connection-id"
	}) } },
	help: {
		summary: "Delete a database connection after exact id confirmation",
		examples: ["postgres connection delete conn_123 --confirm conn_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const connectionId = args.positionals.connection.trim();
		if (!connectionId) {
			const example = `${CLI_NAME} postgres connection delete <connection-id> --confirm <connection-id>`;
			throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Connection id required", {
				why: "Database connection deletion needs a connection id.",
				nextActions: [{
					kind: "user-choice",
					label: "Pass the connection id to delete."
				}, {
					kind: "run-command",
					label: example,
					command: example
				}]
			});
		}
		await ctx.prompt.consent(CONSENT_QUESTION$4, { token: connectionId });
		await (await resolvePostgresProviderOnly(ctx)).removeConnection(connectionId, { signal: ctx.signal });
		const result = { connection: { id: connectionId } };
		return ok(ctx.present({ data: result }, {
			human: () => [
				{
					kind: "summary",
					status: "ok",
					text: "Deleting database connection."
				},
				{
					kind: "fields",
					rows: [{
						label: "connection",
						value: connectionId
					}]
				},
				{
					kind: "list",
					items: ["The connection metadata was deleted. Existing one-time secrets were not shown."]
				}
			],
			stdout: () => [],
			json: () => ({ connection: result.connection }),
			next: () => []
		}));
	}
});
//#endregion
//#region src/commands/postgres/connection-list.ts
/** The `postgres connection list` command. */
const TITLE$8 = "Listing database connection metadata.";
function connectionRows(result) {
	return result.connections.map((connection) => [
		connection.name,
		connection.id,
		connection.createdAt ?? "unknown"
	]);
}
/** The stdout rows: an absent creation time is an empty field. */
function connectionStdoutRows(result) {
	return result.connections.map((connection) => [
		connection.name,
		connection.id,
		connection.createdAt ?? ""
	]);
}
function listPresentations$4(result) {
	const rows = connectionRows(result);
	const stdoutRows = connectionStdoutRows(result);
	return {
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$8
			},
			{
				kind: "fields",
				rows: [{
					label: "database",
					value: result.database.name
				}]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No database connections found."
			}] : [{
				kind: "table",
				columns: [
					"Name",
					"Id",
					"Created"
				],
				rows
			}]
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeDatabaseConnectionList(result)
	};
}
const postgresConnectionListCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "List database connection metadata without secret values",
		examples: ["postgres connection list db_123", "postgres connection list acme-preview --branch preview --json"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres connection list");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const result = {
			projectId,
			projectName,
			database,
			connections: await provider.listConnections(database.id, { signal: ctx.signal })
		};
		return ok(ctx.present({ data: result }, listPresentations$4(result)));
	}
});
//#endregion
//#region src/commands/postgres/connection-rotate.ts
/** The `postgres connection rotate` command. */
const CONSENT_QUESTION$3 = "Rotating revokes the previous credentials and breaks clients still using them, so it requires the exact connection id.";
const postgresConnectionRotateCommand = defineCommand({
	args: { positionals: { connection: positional.string({
		brief: "Connection id",
		placeholder: "connection-id"
	}) } },
	help: {
		summary: "Rotate connection credentials and print the new one-time connection URL",
		examples: ["postgres connection rotate conn_123 --confirm conn_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const connectionId = args.positionals.connection.trim();
		if (!connectionId) {
			const example = `${CLI_NAME} postgres connection rotate <connection-id> --confirm <connection-id>`;
			throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Connection id required", {
				why: "Database connection rotation needs a connection id.",
				nextActions: [{
					kind: "user-choice",
					label: "Pass the connection id to rotate."
				}, {
					kind: "run-command",
					label: example,
					command: example
				}]
			});
		}
		await ctx.prompt.consent(CONSENT_QUESTION$3, { token: connectionId });
		const rotated = await (await resolvePostgresProviderOnly(ctx)).rotateConnection(connectionId, { signal: ctx.signal });
		const result = {
			connection: rotated.connection,
			database: rotated.database,
			connectionString: rotated.connectionString
		};
		const subject = result.database ? `"${result.database.name}"` : `connection ${result.connection.id}`;
		return ok(ctx.present({ data: result }, {
			human: () => secretBlocks(`Rotated credentials for ${subject}. The previous credentials no longer work.`, result.connectionString),
			stdout: () => [result.connectionString],
			json: () => result,
			next: () => []
		}));
	}
});
//#endregion
//#region src/commands/postgres/create.ts
/** The `postgres create` command. */
const postgresCreateCommand = defineCommand({
	args: {
		positionals: { name: positional.string({
			brief: "Database name",
			placeholder: "name"
		}) },
		flags: {
			region: flag.string({
				brief: "Prisma Postgres region id",
				placeholder: "region"
			}),
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Create a Prisma Postgres database and print its one-time connection URL",
		examples: ["postgres create my-db", "postgres create my-db --branch feature/foo --region eu-central-1"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const name = args.positionals.name.trim();
		if (!name) {
			const example = `${CLI_NAME} postgres create <name>`;
			throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Database name required", {
				why: "Database create needs a non-empty name.",
				nextActions: [{
					kind: "user-choice",
					label: "Pass a database name."
				}, {
					kind: "run-command",
					label: example,
					command: example
				}]
			});
		}
		const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres create");
		const created = await provider.createDatabase({
			projectId,
			name,
			branchName: args.flags.branch,
			region: args.flags.region,
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			database: ensureProjectId(created.database, projectId),
			connection: created.connection,
			connectionString: created.connectionString
		};
		return ok(ctx.present({ data: result }, {
			human: () => secretBlocks(`Created database "${result.database.name}" in ${postgresTargetLabel(projectName, result.database.branchName)}.`, result.connectionString),
			stdout: () => [result.connectionString],
			json: () => result,
			next: () => []
		}));
	}
});
//#endregion
//#region src/commands/postgres/delete.ts
/** The `postgres delete` command. */
const CONSENT_QUESTION$2 = "Deleting this database is destructive and requires the exact id.";
function deletePresentations$3(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Deleting database."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "project",
						value: result.projectName
					},
					{
						label: "database",
						value: result.database.name
					},
					{
						label: "id",
						value: result.database.id
					}
				]
			},
			{
				kind: "list",
				items: ["Database and its connection metadata were deleted."]
			}
		]
	};
}
const postgresDeleteCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Delete a database after exact id confirmation",
		examples: ["postgres delete db_123 --confirm db_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres delete");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		await ctx.prompt.consent(CONSENT_QUESTION$2, { token: database.id });
		await provider.removeDatabase(database.id, { signal: ctx.signal });
		const result = {
			projectId,
			projectName,
			database
		};
		return ok(ctx.present({ data: result }, deletePresentations$3(result)));
	}
});
//#endregion
//#region src/commands/postgres/list.ts
/** The `postgres list` command. */
const TITLE$7 = "Listing databases for the resolved project.";
function databaseRows(result) {
	return result.databases.map((database) => [
		database.name,
		branchLabel(database),
		database.region ?? "unknown",
		formatStatus(database),
		database.id
	]);
}
/** The stdout rows carry the values, not the reader's placeholders:
*  an absent branch, region or status is an empty field. */
function databaseStdoutRows(result) {
	return result.databases.map((database) => [
		database.name,
		database.branchName ?? "",
		database.region ?? "",
		statusValue(database),
		database.id
	]);
}
function listPresentations$3(result) {
	const rows = databaseRows(result);
	const stdoutRows = databaseStdoutRows(result);
	return {
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$7
			},
			{
				kind: "fields",
				rows: [{
					label: "project",
					value: result.projectName
				}, ...result.branchName ? [{
					label: "branch",
					value: result.branchName
				}] : []]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No databases found."
			}] : [{
				kind: "table",
				columns: [
					"Name",
					"Branch",
					"Region",
					"Status",
					"Id"
				],
				rows
			}]
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeDatabaseList(result)
	};
}
const postgresListCommand = defineCommand({
	args: { flags: {
		project: projectFlag$1,
		branch: branchFlag$1
	} },
	help: {
		summary: "List Prisma Postgres databases for the resolved project",
		examples: [
			"postgres list",
			"postgres list --branch feature/foo",
			"postgres list --json"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres list");
		const databases = sortDatabases(await provider.listDatabases({
			projectId,
			branchName: args.flags.branch,
			signal: ctx.signal
		}));
		const result = {
			projectId,
			projectName,
			branchName: args.flags.branch ?? null,
			databases
		};
		return ok(ctx.present({ data: result }, listPresentations$3(result)));
	}
});
//#endregion
//#region src/commands/postgres/show.ts
/** The `postgres show` command. */
const TITLE$6 = "Showing database metadata.";
function fieldRows$2(result) {
	return [
		{
			label: "project",
			value: result.projectName
		},
		{
			label: "database",
			value: result.database.name
		},
		{
			label: "id",
			value: result.database.id
		},
		{
			label: "branch",
			value: branchLabel(result.database)
		},
		{
			label: "region",
			value: result.database.region ?? "unknown"
		},
		{
			label: "status",
			value: formatStatus(result.database)
		},
		{
			label: "connections",
			value: String(result.connections.length)
		}
	];
}
/** The stdout mirror of the field rows: same labels, raw values. An
*  absent branch, region or status is an empty field rather than the
*  word the card shows a reader. */
function stdoutFieldRows$2(result) {
	return [
		{
			label: "project",
			value: result.projectName
		},
		{
			label: "database",
			value: result.database.name
		},
		{
			label: "id",
			value: result.database.id
		},
		{
			label: "branch",
			value: result.database.branchName ?? ""
		},
		{
			label: "region",
			value: result.database.region ?? ""
		},
		{
			label: "status",
			value: statusValue(result.database)
		},
		{
			label: "connections",
			value: String(result.connections.length)
		}
	];
}
function showPresentations$2(result) {
	const rows = fieldRows$2(result);
	return {
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "info",
			text: TITLE$6
		}, {
			kind: "fields",
			rows
		}],
		stdout: () => stdoutFieldRows$2(result).map((row) => `${row.label}: ${row.value}`)
	};
}
const postgresShowCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Show database metadata without secret values",
		examples: ["postgres show db_123", "postgres show acme-preview --branch preview --json"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres show");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const result = {
			projectId,
			projectName,
			database,
			connections: await provider.listConnections(database.id, { signal: ctx.signal })
		};
		return ok(ctx.present({ data: result }, showPresentations$2(result)));
	}
});
//#endregion
//#region src/commands/postgres/usage.ts
/** The `postgres usage` command. */
const TITLE$5 = "Showing database usage metrics.";
function fieldRows$1(result) {
	return [
		{
			label: "project",
			value: result.projectName
		},
		{
			label: "database",
			value: result.database.name
		},
		{
			label: "id",
			value: result.database.id
		},
		{
			label: "period",
			value: `${result.period.start || "unknown"} to ${result.period.end || "unknown"}`
		},
		{
			label: "operations",
			value: formatUsageMetric(result.metrics.operations)
		},
		{
			label: "storage",
			value: formatUsageMetric(result.metrics.storage)
		},
		{
			label: "generated",
			value: result.generatedAt || "unknown"
		}
	];
}
/** The stdout mirror of the field rows. The reader's card carries the
*  unit beside each metric and the word "unknown" for an absent value;
*  stdout carries the number and an empty field, because that is what a
*  program can consume. The units and the period bounds are both in the
*  `--json` record. */
function stdoutFieldRows$1(result) {
	return [
		{
			label: "project",
			value: result.projectName
		},
		{
			label: "database",
			value: result.database.name
		},
		{
			label: "id",
			value: result.database.id
		},
		{
			label: "period start",
			value: result.period.start || ""
		},
		{
			label: "period end",
			value: result.period.end || ""
		},
		{
			label: "operations",
			value: usageMetricValue(result.metrics.operations)
		},
		{
			label: "storage",
			value: usageMetricValue(result.metrics.storage)
		},
		{
			label: "generated",
			value: result.generatedAt || ""
		}
	];
}
function usagePresentations(result) {
	const rows = fieldRows$1(result);
	return {
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "info",
			text: TITLE$5
		}, {
			kind: "fields",
			rows
		}],
		stdout: () => stdoutFieldRows$1(result).map((row) => `${row.label}: ${row.value}`)
	};
}
const postgresUsageCommand = defineCommand({
	args: {
		positionals: { database: databasePositional },
		flags: {
			from: flag.string({
				brief: "Start of the usage period",
				placeholder: "iso-date"
			}),
			to: flag.string({
				brief: "End of the usage period",
				placeholder: "iso-date"
			}),
			project: projectFlag$1,
			branch: branchFlag$1
		}
	},
	help: {
		summary: "Show usage metrics for a database",
		examples: ["postgres usage db_123", "postgres usage acme-production --from 2026-06-01 --to 2026-06-30"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const from = parseUsageDate(args.flags.from, "--from", "start");
		const to = parseUsageDate(args.flags.to, "--to", "end");
		if (from && to && Date.parse(from) > Date.parse(to)) throw new CliStructuredError("POSTGRES.USAGE_ERROR", "Invalid usage period", {
			why: "--from must not be later than --to.",
			nextActions: [{
				kind: "user-choice",
				label: "Pass a --from date that is on or before the --to date."
			}, {
				kind: "run-command",
				label: USAGE_PERIOD_EXAMPLE_COMMAND,
				command: USAGE_PERIOD_EXAMPLE_COMMAND
			}]
		});
		const { provider, target, projectId, projectName } = await resolvePostgresContext(ctx, args.flags, "postgres usage");
		const database = await resolveDatabase(provider, target, args.positionals.database, args.flags.branch, ctx.signal);
		const usage = await provider.getUsage(database.id, {
			from,
			to,
			signal: ctx.signal
		});
		const result = {
			projectId,
			projectName,
			database,
			period: usage.period,
			metrics: usage.metrics,
			generatedAt: usage.generatedAt
		};
		return ok(ctx.present({ data: result }, usagePresentations(result)));
	}
});
//#endregion
//#region src/lib/app/branch-database-api.ts
async function createBranchDatabase(client, options) {
	const result = await client.POST("/v1/databases", {
		body: {
			projectId: options.projectId,
			branchId: options.branchId,
			name: options.branchName,
			source: { type: "empty" }
		},
		signal: options.signal
	});
	if (result.error || !result.data) throw apiCallError$2(`Failed to create database for branch "${options.branchName}"`, result.response, result.error);
	return normalizeBranchDatabaseRecord(result.data.data);
}
async function listEnvironmentVariables(client, options) {
	const variables = [];
	let cursor;
	while (true) {
		const result = await client.GET("/v1/environment-variables", {
			params: { query: {
				projectId: options.projectId,
				class: options.className,
				key: options.key,
				branchId: options.branchId,
				cursor
			} },
			signal: options.signal
		});
		if (result.error || !result.data) throw apiCallError$2("Failed to list environment variables", result.response, result.error);
		variables.push(...result.data.data);
		if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
		cursor = result.data.pagination.nextCursor;
	}
	return variables.map((variable) => normalizeEnvironmentVariable(variable));
}
async function createEnvironmentVariable(client, options) {
	const result = await client.POST("/v1/environment-variables", {
		body: {
			projectId: options.projectId,
			class: options.className,
			key: options.key,
			value: options.value,
			...options.branchId ? { branchId: options.branchId } : {}
		},
		signal: options.signal
	});
	if (result.error || !result.data) throw apiCallError$2(`Failed to add ${options.key}`, result.response, result.error);
	return normalizeEnvironmentVariable(result.data.data);
}
async function deleteBranchDatabase(client, options) {
	const result = await client.DELETE("/v1/databases/{databaseId}", {
		params: { path: { databaseId: options.databaseId } },
		signal: options.signal
	});
	if (result.error) throw apiCallError$2("Failed to delete branch database", result.response, result.error);
}
async function updateEnvironmentVariable(client, options) {
	const result = await client.PATCH("/v1/environment-variables/{envVarId}", {
		params: { path: { envVarId: options.envVarId } },
		body: { value: options.value },
		signal: options.signal
	});
	if (result.error || !result.data) throw apiCallError$2("Failed to update environment variable", result.response, result.error);
	return normalizeEnvironmentVariable(result.data.data);
}
async function deleteEnvironmentVariable(client, options) {
	const result = await client.DELETE("/v1/environment-variables/{envVarId}", {
		params: { path: { envVarId: options.envVarId } },
		signal: options.signal
	});
	if (result.error) throw apiCallError$2("Failed to delete environment variable", result.response, result.error);
}
function normalizeEnvironmentVariable(variable) {
	return {
		id: variable.id,
		key: variable.key,
		branchId: variable.branchId,
		className: variable.class,
		isManagedBySystem: variable.isManagedBySystem
	};
}
function normalizeBranchDatabaseRecord(database) {
	const connection = database.connections?.[0];
	const databaseUrl = connection?.endpoints?.pooled?.connectionString;
	const directUrl = connection?.endpoints?.direct?.connectionString ?? null;
	if (!databaseUrl) throw new Error("Created database did not return a pooled connection string.");
	return {
		id: database.id,
		name: database.name,
		branchId: database.branchId,
		databaseUrl,
		directUrl
	};
}
function apiCallError$2(summary, response, error) {
	if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
	const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
	const hint = error.error?.hint ? ` ${error.error.hint}` : "";
	return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
}
//#endregion
//#region src/lib/app/env-vars.ts
function envVarNames(envVars) {
	if (!envVars) return [];
	return Object.entries(envVars).filter(([, value]) => value !== null).map(([name]) => name).sort((left, right) => left.localeCompare(right));
}
//#endregion
//#region src/lib/app/app-provider.ts
var DomainApiError = class extends Error {
	status;
	code;
	hint;
	constructor(options) {
		super(`${options.summary}: ${options.message}${options.hint ? ` ${options.hint}` : ""}`);
		this.name = "DomainApiError";
		this.status = options.status;
		this.code = options.code ?? null;
		this.hint = options.hint ?? null;
	}
};
function createAppProvider(client, options) {
	const sdk = new ComputeClient(client);
	return {
		async createProject(options) {
			const projectResult = await sdk.createProject({
				name: options.name,
				region: options.region,
				signal: options.signal
			});
			if (projectResult.isErr()) throw new Error(projectResult.error.message);
			return {
				id: projectResult.value.id,
				name: projectResult.value.name,
				defaultRegion: projectResult.value.defaultRegion
			};
		},
		async listApps(projectId, options) {
			return listComputeServices(client, {
				projectId,
				branchGitName: options?.branchName,
				signal: options?.signal
			});
		},
		async createApp(options) {
			return createComputeService(client, {
				projectId: options.projectId,
				branchName: options.branchName,
				displayName: options.name,
				...options.region !== void 0 ? { region: options.region } : {},
				...options.signal !== void 0 ? { signal: options.signal } : {}
			});
		},
		async resolveBranch(projectId, options) {
			const branch = await resolveOrCreateBranch$1(client, {
				projectId,
				gitName: options.branchName,
				signal: options.signal
			});
			return {
				id: branch.id,
				name: branch.gitName,
				role: branch.role
			};
		},
		async createBranchDatabase(options) {
			return createBranchDatabase(client, options);
		},
		async deleteBranchDatabase(options) {
			return deleteBranchDatabase(client, options);
		},
		async listEnvironmentVariables(options) {
			return listEnvironmentVariables(client, options);
		},
		async createEnvironmentVariable(options) {
			return createEnvironmentVariable(client, options);
		},
		async updateEnvironmentVariable(options) {
			return updateEnvironmentVariable(client, options);
		},
		async deleteEnvironmentVariable(options) {
			return deleteEnvironmentVariable(client, options);
		},
		async removeApp(appId, options) {
			const appResult = await sdk.showApp({
				appId,
				signal: options?.signal
			});
			if (appResult.isErr()) throw new Error(appResult.error.message);
			const destroyResult = await sdk.destroyApp({
				appId,
				keepApp: false,
				timeoutSeconds: 120,
				pollIntervalMs: 2e3,
				signal: options?.signal,
				progress: options?.progress
			});
			if (destroyResult.isErr()) throw new Error(destroyResult.error.message);
			return {
				id: appResult.value.id,
				name: appResult.value.name
			};
		},
		async listDomains(appId, options) {
			return listComputeServiceDomains(client, appId, options?.signal);
		},
		async addDomain(options) {
			const result = await client.POST("/v1/apps/{appId}/domains", {
				params: { path: { appId: options.appId } },
				body: { hostname: options.hostname },
				signal: options.signal
			});
			if (result.error || !result.data) {
				if (result.response.status === 409) {
					const existing = (await listComputeServiceDomains(client, options.appId, options.signal)).find((domain) => sameHostname(domain.hostname, options.hostname));
					if (existing) return {
						domain: existing,
						existing: true
					};
				}
				throw domainApiCallError("Failed to add custom domain", result.response, result.error);
			}
			return {
				domain: normalizeDomainRecord(result.data.data),
				existing: false
			};
		},
		async showDomain(domainId, options) {
			const result = await client.GET("/v1/domains/{domainId}", {
				params: { path: { domainId } },
				signal: options?.signal
			});
			if (result.error || !result.data) throw domainApiCallError("Failed to show custom domain", result.response, result.error);
			return normalizeDomainRecord(result.data.data);
		},
		async removeDomain(domainId, options) {
			const result = await client.DELETE("/v1/domains/{domainId}", {
				params: { path: { domainId } },
				signal: options?.signal
			});
			if (result.error) throw domainApiCallError("Failed to delete custom domain", result.response, result.error);
		},
		async retryDomain(domainId, options) {
			const result = await client.POST("/v1/domains/{domainId}/retry", {
				params: { path: { domainId } },
				signal: options?.signal
			});
			if (result.error || !result.data) throw domainApiCallError("Failed to retry custom domain", result.response, result.error);
			return normalizeDomainRecord(result.data.data);
		},
		async promoteDeployment(options) {
			const promoteResult = await sdk.promote({
				appId: options.appId,
				deploymentId: options.deploymentId,
				timeoutSeconds: 120,
				pollIntervalMs: 2e3,
				signal: options.signal,
				progress: options.progress
			});
			if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
		},
		async startDeployment(options) {
			const result = await sdk.startDeployment({
				deploymentId: options.deploymentId,
				signal: options.signal
			});
			if (result.isErr()) throw new Error(result.error.message);
		},
		async stopDeployment(options) {
			const result = await sdk.stopDeployment({
				deploymentId: options.deploymentId,
				signal: options.signal
			});
			if (result.isErr()) throw new Error(result.error.message);
		},
		async deleteDeployment(options) {
			const result = await sdk.deleteDeployment({
				deploymentId: options.deploymentId,
				signal: options.signal
			});
			if (result.isErr()) throw new Error(result.error.message);
		},
		async readDeployment(options) {
			const result = await sdk.showDeployment({
				deploymentId: options.deploymentId,
				signal: options.signal
			});
			if (result.isErr()) throw new Error(result.error.message);
			return {
				id: result.value.id,
				status: result.value.status,
				createdAt: result.value.createdAt,
				url: toAbsoluteUrl(result.value.previewDomain ?? null),
				live: null
			};
		},
		async updateAppEnv(options) {
			const updateResult = await sdk.updateEnv({
				appId: options.appId,
				envVars: options.envVars,
				timeoutSeconds: 120,
				pollIntervalMs: 2e3,
				signal: options.signal,
				progress: options.progress
			});
			if (updateResult.isErr()) throw new Error(updateResult.error.message);
			const promoteResult = await sdk.promote({
				appId: options.appId,
				deploymentId: updateResult.value.deploymentId,
				timeoutSeconds: 120,
				pollIntervalMs: 2e3,
				signal: options.signal,
				progress: options.promoteProgress
			});
			if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
			const [serviceResult, versionResult] = await Promise.all([sdk.showApp({
				appId: options.appId,
				signal: options.signal
			}), sdk.showDeployment({
				deploymentId: updateResult.value.deploymentId,
				signal: options.signal
			})]);
			if (serviceResult.isErr()) throw new Error(serviceResult.error.message);
			if (versionResult.isErr()) throw new Error(versionResult.error.message);
			return {
				projectId: updateResult.value.projectId,
				app: {
					id: serviceResult.value.id,
					name: serviceResult.value.name,
					region: serviceResult.value.region ?? null,
					liveDeploymentId: serviceResult.value.latestDeploymentId ?? null,
					liveUrl: toAbsoluteUrl(serviceResult.value.appEndpointDomain ?? null)
				},
				deployment: {
					id: versionResult.value.id,
					status: versionResult.value.status,
					createdAt: versionResult.value.createdAt,
					url: toAbsoluteUrl(serviceResult.value.appEndpointDomain ?? versionResult.value.previewDomain ?? null),
					live: true
				},
				variables: envVarNames(versionResult.value.envVars)
			};
		},
		async listAppEnvNames(options) {
			const [serviceResult, versionResult] = await Promise.all([sdk.showApp({
				appId: options.appId,
				signal: options.signal
			}), sdk.showDeployment({
				deploymentId: options.deploymentId,
				signal: options.signal
			})]);
			if (serviceResult.isErr()) throw new Error(serviceResult.error.message);
			if (versionResult.isErr()) throw new Error(versionResult.error.message);
			return {
				projectId: serviceResult.value.projectId,
				app: {
					id: serviceResult.value.id,
					name: serviceResult.value.name,
					region: serviceResult.value.region ?? null,
					liveDeploymentId: serviceResult.value.latestDeploymentId ?? null,
					liveUrl: toAbsoluteUrl(serviceResult.value.appEndpointDomain ?? null)
				},
				deployment: {
					id: versionResult.value.id,
					status: versionResult.value.status,
					createdAt: versionResult.value.createdAt,
					url: toAbsoluteUrl(versionResult.value.previewDomain ?? null),
					live: serviceResult.value.latestDeploymentId === versionResult.value.id
				},
				variables: envVarNames(versionResult.value.envVars)
			};
		},
		async listDeployments(appId, options) {
			const [appResult, versionsResult] = await Promise.all([sdk.showApp({
				appId,
				signal: options?.signal
			}), sdk.listDeployments({
				appId,
				signal: options?.signal
			})]);
			if (appResult.isErr()) throw new Error(appResult.error.message);
			if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
			return {
				app: {
					id: appResult.value.id,
					name: appResult.value.name,
					region: appResult.value.region ?? null,
					liveDeploymentId: appResult.value.latestDeploymentId ?? null,
					liveUrl: toAbsoluteUrl(appResult.value.appEndpointDomain ?? null)
				},
				deployments: versionsResult.value.slice().sort((left, right) => {
					const byDate = right.createdAt.localeCompare(left.createdAt);
					return byDate !== 0 ? byDate : right.id.localeCompare(left.id);
				}).map((deployment) => ({
					id: deployment.id,
					status: deployment.status,
					createdAt: deployment.createdAt,
					url: toAbsoluteUrl(deployment.previewDomain ?? null),
					live: null
				}))
			};
		},
		async showDeployment(deploymentId, options) {
			const deploymentResult = await sdk.showDeployment({
				deploymentId,
				signal: options?.signal
			});
			if (deploymentResult.isErr()) {
				if (ApiError.is(deploymentResult.error) && deploymentResult.error.statusCode === 404) return null;
				throw new Error(deploymentResult.error.message);
			}
			const app = await owningService(sdk, deploymentResult.value.serviceId, options?.signal);
			const promotedUrl = app !== null && app.liveDeploymentId === deploymentResult.value.id ? app.liveUrl : null;
			return {
				app,
				deployment: {
					id: deploymentResult.value.id,
					status: deploymentResult.value.status,
					createdAt: deploymentResult.value.createdAt,
					url: promotedUrl ?? toAbsoluteUrl(deploymentResult.value.previewDomain ?? null),
					live: null
				}
			};
		},
		async streamDeploymentLogs(streamOptions) {
			if (!options?.baseUrl || !options.getToken) throw new Error("Log streaming requires an authenticated API base URL and token.");
			const result = await streamLogs({
				baseUrl: options.baseUrl,
				token: await options.getToken(),
				deploymentId: streamOptions.deploymentId,
				signal: streamOptions.signal
			}, streamOptions.onRecord);
			if (result.isErr()) {
				if (CancelledError.is(result.error)) return;
				throw result.error;
			}
		}
	};
}
async function listBranches(client, options) {
	const result = await client.GET("/v1/projects/{projectId}/branches", {
		params: {
			path: { projectId: options.projectId },
			query: { gitName: options.gitName }
		},
		signal: options.signal
	});
	if (result.error || !result.data) throw apiCallError$1("Failed to list branches", result.response, result.error);
	return result.data.data.map((branch) => ({
		id: branch.id,
		gitName: branch.gitName,
		isDefault: branch.isDefault,
		role: branch.role
	}));
}
async function resolveOrCreateBranch$1(client, options) {
	const existing = (await listBranches(client, options))[0];
	if (existing) return existing;
	const result = await client.POST("/v1/projects/{projectId}/branches", {
		params: { path: { projectId: options.projectId } },
		body: { gitName: options.gitName },
		signal: options.signal
	});
	if (result.error || !result.data) {
		if (result.response.status === 409) {
			const raced = (await listBranches(client, options))[0];
			if (raced) return raced;
		}
		throw apiCallError$1(`Failed to create branch "${options.gitName}"`, result.response, result.error);
	}
	const branch = result.data.data;
	return {
		id: branch.id,
		gitName: branch.gitName,
		isDefault: branch.isDefault,
		role: branch.role
	};
}
async function listComputeServices(client, options) {
	const services = [];
	let cursor;
	while (true) {
		const result = await client.GET("/v1/apps", {
			params: { query: {
				projectId: options.projectId,
				branchGitName: options.branchGitName,
				cursor
			} },
			signal: options.signal
		});
		if (result.error || !result.data) throw apiCallError$1("Failed to list apps", result.response, result.error);
		services.push(...result.data.data);
		if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
		cursor = result.data.pagination.nextCursor;
	}
	return services.map(toAppRecord);
}
async function owningService(sdk, serviceId, signal) {
	const result = await sdk.showApp({
		appId: serviceId,
		signal
	});
	if (result.isErr()) {
		if (ApiError.is(result.error) && result.error.statusCode === 404) return null;
		throw new Error(result.error.message);
	}
	return {
		id: result.value.id,
		name: result.value.name,
		region: result.value.region ?? null,
		liveDeploymentId: result.value.latestDeploymentId ?? null,
		liveUrl: toAbsoluteUrl(result.value.appEndpointDomain ?? null)
	};
}
function toAppRecord(service) {
	return {
		id: service.id,
		name: service.name,
		region: service.region.id ?? null,
		branchId: service.branchId,
		liveDeploymentId: service.latestDeploymentId ?? null,
		liveUrl: toAbsoluteUrl(service.appEndpointDomain ?? null)
	};
}
/**
* Creates a service on a branch, resolving (or creating) the branch
* first because the create body needs its id. A name already taken on
* the branch comes back as the existing service rather than an error:
* the API answers 409 and the caller asked for a service by that name,
* which already exists.
*/
async function createComputeService(client, options) {
	const branch = await resolveOrCreateBranch$1(client, {
		projectId: options.projectId,
		gitName: options.branchName,
		signal: options.signal
	});
	const result = await client.POST("/v1/apps", {
		body: {
			projectId: options.projectId,
			branchId: branch.id,
			displayName: options.displayName,
			...options.region ? { regionId: options.region } : {}
		},
		signal: options.signal
	});
	if (result.error || !result.data) {
		if (result.response.status === 409) {
			const matched = (await listComputeServices(client, {
				projectId: options.projectId,
				branchGitName: options.branchName,
				signal: options.signal
			})).find((service) => service.name === options.displayName);
			if (matched) return {
				service: matched,
				existing: true
			};
		}
		throw apiCallError$1(`Failed to create app "${options.displayName}"`, result.response, result.error);
	}
	return {
		service: toAppRecord(result.data.data),
		existing: false
	};
}
async function listComputeServiceDomains(client, appId, signal) {
	const result = await client.GET("/v1/apps/{appId}/domains", {
		params: { path: { appId } },
		signal
	});
	if (result.error || !result.data) throw domainApiCallError("Failed to list custom domains", result.response, result.error);
	return result.data.data.map((domain) => normalizeDomainRecord(domain));
}
function normalizeDomainRecord(domain) {
	return {
		id: domain.id,
		type: domain.type,
		url: domain.url,
		hostname: domain.hostname,
		appId: domain.appId,
		status: domain.status,
		foundryStatus: domain.foundryStatus,
		failureReason: domain.failureReason,
		failureCategory: domain.failureCategory,
		certExpiresAt: domain.certExpiresAt,
		createdAt: domain.createdAt,
		updatedAt: domain.updatedAt,
		dnsRecords: normalizeDomainDnsRecords(domain.dnsRecords)
	};
}
function normalizeDomainDnsRecords(records) {
	if (!Array.isArray(records)) return [];
	return records.map((record) => {
		if (typeof record.type !== "string" || typeof record.name !== "string" || typeof record.value !== "string") return null;
		return {
			type: record.type,
			name: record.name,
			value: record.value,
			ttl: typeof record.ttl === "number" ? record.ttl : null
		};
	}).filter((record) => Boolean(record));
}
function sameHostname(left, right) {
	return normalizeHostnameForComparison(left) === normalizeHostnameForComparison(right);
}
function normalizeHostnameForComparison(hostname) {
	return hostname.trim().replace(/\.$/, "").toLowerCase();
}
function apiCallError$1(summary, response, error) {
	if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
	const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
	const hint = error.error?.hint ? ` ${error.error.hint}` : "";
	return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
}
function domainApiCallError(summary, response, error) {
	return new DomainApiError({
		summary,
		status: response.status,
		code: error.error?.code ?? null,
		message: error.error?.message ?? `Management API returned HTTP ${response.status}.`,
		hint: error.error?.hint ?? null
	});
}
function toAbsoluteUrl(url) {
	if (!url) return null;
	return url.startsWith("https://") || url.startsWith("http://") ? url : `https://${url}`;
}
//#endregion
//#region src/commands/project/presentation.ts
/** Deploys come from pushing a connected repository, so the step after
*  creating or linking a Project is connecting one. */
const CONNECT_REPO_NEXT_ACTION = {
	kind: "run-command",
	label: `${CLI_NAME} git connect`,
	command: `${CLI_NAME} git connect`
};
/** The legacy local-pin warnings of `project delete` / `project
*  transfer`: the operation succeeded, so they are warn diagnostics
*  under the pinned local-state code, never errors. */
function localPinDiagnostics(warnings) {
	return warnings.map((warning) => ({
		code: "PROJECT.LOCAL_STATE_WRITE_FAILED",
		severity: "warn",
		summary: warning,
		nextActions: []
	}));
}
function setupPresentations(result) {
	return {
		stdout: () => [],
		human: () => [
			...result.action === "created" ? [{
				kind: "summary",
				status: "ok",
				text: `Created Project "${result.project.name}"`
			}] : [],
			{
				kind: "summary",
				status: "ok",
				text: `Linked "${result.directory}" to Project "${result.project.name}"`
			},
			{
				kind: "summary",
				status: "info",
				text: `Saved ${result.localPin.path}`
			}
		],
		json: () => serializeProjectSetup(result),
		next: () => [CONNECT_REPO_NEXT_ACTION]
	};
}
//#endregion
//#region src/commands/project/create.ts
/** The `project create` command. */
const projectCreateCommand = defineCommand({
	args: {
		positionals: { name: positional.string({
			brief: "Project name",
			placeholder: "name"
		}) },
		flags: { region: flag.string({
			brief: "Prisma Compute region id",
			placeholder: "region"
		}) }
	},
	help: {
		summary: "Create a Project and link this directory",
		examples: ["project create my-app", "project create my-app --json"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		if (!isValidProjectSetupName(args.positionals.name)) throw projectSetupNameRequiredError("project create");
		const name = args.positionals.name.trim();
		const created = await createAppProvider(ctx.api).createProject({
			name,
			region: args.flags.region,
			signal: ctx.signal
		}).catch((error) => {
			/** A cancelled run is cancelled, not a failed creation. The
			*  provider flattens the underlying AbortError into a plain
			*  Error, which the engine would settle as a bug, so hand it
			*  back its own abort reason and let it settle the run as
			*  cancelled. */
			if (ctx.signal.aborted) throw ctx.signal.reason;
			throw projectCreateFailedError(error, name, workspace, {
				nextSteps: ["prisma project list", "prisma project link <id-or-name>"],
				permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
				fallbackFix: "Retry the command, or choose an existing Project with prisma project link <id-or-name>."
			});
		});
		const result = await bindDirectoryToProject(ctx, workspace, {
			id: created.id,
			name: created.name,
			...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
		}, "created");
		return ok(ctx.present({ data: result }, setupPresentations(result)));
	}
});
//#endregion
//#region src/commands/project/delete.ts
/** The `project delete` command. */
const CONSENT_QUESTION$1 = "Deleting a project is permanent, destroys its databases, and stops its apps, so it requires the exact project id.";
function deletePresentations$2(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Deleting project."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "workspace",
						value: result.workspace.name
					},
					{
						label: "project",
						value: result.project.name
					},
					{
						label: "id",
						value: result.project.id
					}
				]
			},
			{
				kind: "list",
				items: ["The project, its databases, and its apps were deleted.", ...result.localPin.cleared ? ["This directory's local project binding was cleared."] : []]
			}
		]
	};
}
const projectDeleteCommand = defineCommand({
	args: { positionals: { project: positional.string({
		brief: "Project id or name",
		placeholder: "id-or-name"
	}) } },
	help: {
		summary: "Delete a Project permanently after exact id confirmation",
		examples: ["project delete proj_123 --confirm proj_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		const projects = await listWorkspaceProjects$1(ctx);
		const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
		await ctx.prompt.consent(CONSENT_QUESTION$1, { token: project.id });
		await createManagementProjectProvider(ctx.api).removeProject({
			projectId: project.id,
			signal: ctx.signal
		});
		const warnings = [];
		const result = {
			workspace,
			project,
			localPin: { cleared: await cleanupLocalPinForProject(operationContext(ctx), project.id, { onError: (message) => warnings.push(message) }) }
		};
		const diagnostics = localPinDiagnostics(warnings);
		return ok(ctx.present({
			data: result,
			diagnostics
		}, deletePresentations$2(result)));
	}
});
//#endregion
//#region src/lib/app/env-errors.ts
/**
* The structured errors the `project env` code paths raise, with the
* registered PROJECT.* codes assigned at origin. This is the lowest
* layer the env commands, controllers and parsers share, so the
* parsers can raise without depending on the controllers.
*/
function userChoice(label) {
	return {
		kind: "user-choice",
		label
	};
}
function runCommand(command, reason) {
	return {
		kind: "run-command",
		label: command,
		command,
		...reason === void 0 ? {} : { reason }
	};
}
function envUsageError(summary, why, fix, commands = []) {
	return new CliStructuredError("PROJECT.USAGE_ERROR", summary, {
		why,
		nextActions: [userChoice(fix), ...commands.map((step) => runCommand(step))]
	});
}
//#endregion
//#region src/lib/app/env-config.ts
const VALID_ROLES = new Set(["production", "preview"]);
function positionalHint(command) {
	if (command === "add" || command === "update") return "KEY=value ";
	if (command === "delete") return "KEY ";
	return "";
}
function resolveEnvScope(flags, options) {
	if (flags.roleName && flags.branchName) throw envUsageError(`prisma project env ${options.command} accepts either --role or --branch`, "--role targets a project-level config map; --branch targets a preview branch override.", "Pass exactly one scope flag.", [`prisma project env ${options.command} ${positionalHint(options.command)}--role preview`, `prisma project env ${options.command} ${positionalHint(options.command)}--branch feature/foo`]);
	if (flags.roleName) {
		if (!VALID_ROLES.has(flags.roleName)) throw envUsageError(`Unknown role "${flags.roleName}"`, "--role accepts production or preview.", "Pass --role production or --role preview.", [`prisma project env ${options.command} --role production`, `prisma project env ${options.command} --role preview`]);
		return {
			kind: "role",
			role: flags.roleName
		};
	}
	if (flags.branchName) return {
		kind: "branch",
		branchName: flags.branchName
	};
	if (options.requireExplicit) {
		const positional = positionalHint(options.command);
		throw envUsageError(`prisma project env ${options.command} requires --role or --branch`, "Writing without an explicit scope is rejected so the command never silently targets production.", "Pass --role production, --role preview, or --branch <git-name>.", [
			`prisma project env ${options.command} ${positional}--role production`,
			`prisma project env ${options.command} ${positional}--role preview`,
			`prisma project env ${options.command} ${positional}--branch feature/foo`
		]);
	}
	return null;
}
function parseKeyValuePositional(raw, command, env = process.env) {
	if (!raw) throw envUsageError(`prisma project env ${command} requires KEY=VALUE`, "No KEY=VALUE positional argument was supplied.", "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`]);
	const separatorIndex = raw.indexOf("=");
	if (separatorIndex === -1) {
		if (KEY_SHAPE.test(raw)) {
			validateKey(raw, command);
			const value = env[raw];
			if (typeof value === "string" && value.length > 0) return {
				key: raw,
				value
			};
			throw envUsageError(`Value for "${raw}" was not provided`, `No KEY=VALUE assignment was supplied, and ${raw} is not set in the current environment.`, "Pass KEY=VALUE or export the variable before running the command.", [`prisma project env ${command} ${raw}=value --role production`, `${raw}=value prisma project env ${command} ${raw} --role production`]);
		}
		throw envUsageError(`KEY=VALUE argument is missing the = separator`, `"${raw}" does not contain an = character.`, "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma project env ${command} STRIPE_KEY=sk_test_xxx --role production`]);
	}
	const key = raw.slice(0, separatorIndex);
	const value = raw.slice(separatorIndex + 1);
	validateKey(key, command);
	if (value.length === 0) throw envUsageError(`KEY=VALUE argument has an empty value`, `"${raw}" has an empty value after the = separator.`, `Pass a non-empty value, or use prisma project env delete to delete a variable.`, [`prisma project env ${command} ${key}=value --role production`]);
	return {
		key,
		value
	};
}
const KEY_SHAPE = /^[A-Z_][A-Z0-9_]*$/;
function validateKey(key, command) {
	if (key.length === 0) throw envUsageError(`Variable key cannot be empty`, "An empty key was passed.", "Pass an env-var key, e.g. STRIPE_KEY.", [`prisma project env ${command} STRIPE_KEY=value --role production`]);
	if (key.length > 256) throw envUsageError(`Variable key "${key}" exceeds the 256-character limit`, "Env-var keys are capped at 256 characters by the platform.", "Use a shorter key.");
	if (!KEY_SHAPE.test(key)) throw envUsageError(`Variable key "${key}" must match the POSIX env-var shape`, "Keys must start with an uppercase letter or underscore and contain only uppercase letters, digits, and underscores.", "Rename the key to match [A-Z_][A-Z0-9_]*.", [`prisma project env ${command} STRIPE_KEY=value --role production`]);
}
function formatScopeLabel(scope) {
	if (scope.kind === "role") return scope.role;
	return `branch:${scope.branchName}`;
}
//#endregion
//#region src/lib/app/env-file.ts
const ASSIGNMENT_KEY_PATTERN = /^\s*(?:export\s+)?([^#=\s]+)\s*=/;
async function readEnvFileAssignments(cwd, filePath, command) {
	const resolvedPath = path.resolve(cwd, filePath);
	let contents;
	try {
		contents = await readFile(resolvedPath, "utf8");
	} catch (error) {
		throw envUsageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`]);
	}
	return parseEnvFileContents(contents, filePath, command);
}
function parseEnvFileContents(contents, filePath, command) {
	const parsedKeys = extractParsedKeys(contents);
	if (parsedKeys.length === 0) throw envUsageError(`No environment variables found in "${filePath}"`, "The file does not contain any KEY=VALUE assignments.", "Pass a dotenv file with at least one non-empty variable.");
	const seen = /* @__PURE__ */ new Map();
	for (const entry of parsedKeys) {
		validateEnvFileKey(entry.key, entry.line, filePath, command);
		const firstLine = seen.get(entry.key);
		if (firstLine !== void 0) throw envUsageError(`Duplicate environment variable "${entry.key}" in "${filePath}"`, `Lines ${firstLine} and ${entry.line} both define ${entry.key}.`, "Keep one assignment for each key before importing the file.");
		seen.set(entry.key, entry.line);
	}
	const parsedValues = parse(contents);
	return parsedKeys.map(({ key }) => {
		const value = parsedValues[key];
		if (typeof value !== "string" || value.length === 0) {
			const line = seen.get(key);
			throw envUsageError(`Environment variable "${key}" in "${filePath}" has an empty value`, line === void 0 ? `${key} has an empty value.` : `Line ${line} defines ${key} with an empty value.`, "Pass a non-empty value, or omit the key from the file.");
		}
		return {
			key,
			value
		};
	});
}
function extractParsedKeys(contents) {
	const keys = [];
	let multilineQuote = null;
	const lines = contents.split(/\n/);
	for (const [index, line] of lines.entries()) {
		const lineNumber = index + 1;
		if (multilineQuote !== null) {
			if (hasClosingQuote(line, multilineQuote, 0)) multilineQuote = null;
			continue;
		}
		const match = ASSIGNMENT_KEY_PATTERN.exec(line);
		if (!match) continue;
		const key = match[1];
		keys.push({
			key,
			line: lineNumber
		});
		const valueStart = line.slice(match[0].length).trimStart();
		const openingQuote = valueStart[0];
		if ((openingQuote === "\"" || openingQuote === "'" || openingQuote === "`") && !hasClosingQuote(valueStart, openingQuote, 1)) multilineQuote = openingQuote;
	}
	return keys;
}
function validateEnvFileKey(key, line, filePath, command) {
	try {
		validateKey(key, command);
	} catch (error) {
		const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable key.";
		throw envUsageError(`Invalid environment variable "${key}" in "${filePath}"`, `Line ${line}: ${reason}`, "Use a valid env-var key and retry the import.");
	}
}
function hasClosingQuote(value, quote, startIndex) {
	for (let index = startIndex; index < value.length; index += 1) if (value[index] === quote && !isEscaped(value, index)) return true;
	return false;
}
function isEscaped(value, index) {
	let backslashes = 0;
	for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) backslashes += 1;
	return backslashes % 2 === 1;
}
//#endregion
//#region src/controllers/app-env-api.ts
async function findVariableByNaturalKey(client, projectId, key, resolved, signal) {
	const { data, error, response } = await client.GET("/v1/environment-variables", {
		params: { query: {
			projectId,
			class: resolved.apiTarget.class,
			key,
			...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {}
		} },
		signal
	});
	if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
	return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
}
function toMetadata(row, requestedScope) {
	const rowScope = row.branchId === null ? {
		kind: "role",
		role: row.class
	} : requestedScope;
	return {
		id: row.id,
		key: row.key,
		scope: rowScope,
		source: formatDescriptorLabel(rowScope),
		isManagedBySystem: row.isManagedBySystem,
		updatedAt: row.updatedAt
	};
}
function rowMatchesExactScope(row, resolved) {
	return row.class === resolved.apiTarget.class && row.branchId === resolved.apiTarget.branchId;
}
function apiCallError(summary, response, error) {
	const status = response?.status ?? 0;
	const apiCode = error?.error?.code;
	const apiMessage = error?.error?.message;
	const apiHint = error?.error?.hint;
	if (status === 401 || status === 403) return new CliStructuredError("PROJECT.ENV_API_ERROR", summary, {
		why: "The Management API rejected the request as unauthorized or forbidden.",
		meta: { status },
		nextActions: [runCommand("prisma auth login")]
	});
	return new CliStructuredError("PROJECT.ENV_API_ERROR", summary, {
		why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
		...status || apiCode !== void 0 ? { meta: {
			...status ? { status } : {},
			...apiCode !== void 0 ? { apiCode } : {}
		} } : {},
		nextActions: [userChoice(apiHint ?? "Re-run with --log-level verbose for the underlying API response details.")]
	});
}
function formatDescriptorLabel(scope) {
	if (scope.kind === "role") return scope.role ?? "unknown";
	if (scope.kind === "overview") return "overview";
	return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
}
//#endregion
//#region src/controllers/app-env.ts
function resolveEnvWriteSource(rawAssignment, filePath, command) {
	if (filePath !== void 0 && rawAssignment !== void 0) throw envUsageError(`prisma project env ${command} accepts either KEY=VALUE or --file`, "The command received both a positional assignment and a dotenv file path.", "Pass one input source.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`]);
	if (filePath !== void 0) {
		if (filePath.length === 0) throw envUsageError(`prisma project env ${command} --file requires a path`, "The --file flag was passed without a file path.", "Pass a readable dotenv file path.", [`prisma project env ${command} --file .env --role preview`]);
		return {
			kind: "file",
			filePath
		};
	}
	if (rawAssignment === void 0) throw envUsageError(`prisma project env ${command} requires KEY=VALUE or --file`, "No environment variable input was supplied.", "Pass a single KEY=VALUE assignment or a dotenv file path.", [`prisma project env ${command} KEY=value --role preview`, `prisma project env ${command} --file .env --role preview`]);
	return {
		kind: "single",
		rawAssignment
	};
}
async function resolveEnvWriteInput(context, source, command) {
	if (source.kind === "file") return {
		kind: "file",
		filePath: source.filePath,
		assignments: await readEnvFileAssignments(context.runtime.cwd, source.filePath, command)
	};
	return {
		kind: "single",
		...parseKeyValuePositional(source.rawAssignment, command, context.runtime.env)
	};
}
async function resolveScopeToApi(client, projectId, scope, options) {
	if (scope.kind === "role") return {
		scope,
		descriptor: {
			kind: "role",
			role: scope.role
		},
		apiTarget: {
			class: scope.role,
			branchId: null
		}
	};
	const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
	if (branch.role === "production") throw new CliStructuredError("PROJECT.ENV_BRANCH_SCOPE_IS_PRODUCTION", `Branch "${scope.branchName}" is the production branch`, {
		why: "Production variables are project-level only; branch overrides apply to preview branches.",
		nextActions: [userChoice("Use --role production for the production branch."), runCommand("prisma project env list --role production")]
	});
	return {
		scope,
		descriptor: {
			kind: "branch",
			branchName: branch.gitName,
			branchId: branch.id
		},
		apiTarget: {
			class: "preview",
			branchId: branch.id
		}
	};
}
/** No explicit scope lists the overview of every scope. Nothing is
*  inferred from ambient context: what runs is what was named. */
async function resolveListScopeToApi(client, projectId, explicit, options) {
	if (explicit) {
		const resolved = await resolveScopeToApi(client, projectId, explicit, {
			createBranchIfMissing: false,
			signal: options.signal
		});
		return {
			kind: "scoped",
			descriptor: resolved.descriptor,
			target: targetFromExplicitScope(resolved.descriptor),
			apiTarget: resolved.apiTarget,
			addScope: resolved.scope
		};
	}
	return {
		kind: "overview",
		descriptor: { kind: "overview" },
		target: {
			source: "overview",
			envMap: "overview"
		},
		addScope: {
			kind: "role",
			role: "preview"
		}
	};
}
function targetFromExplicitScope(scope) {
	if (scope.kind === "branch") return {
		source: "explicit",
		branchName: scope.branchName,
		branchId: scope.branchId,
		branchRole: "preview",
		branchExists: true,
		envMap: "preview"
	};
	if (scope.kind === "role") return {
		source: "explicit",
		envMap: scope.role
	};
	return {
		source: "overview",
		envMap: "overview"
	};
}
function formatScopeFlag$1(scope) {
	if (scope.kind === "role") return `--role ${scope.role}`;
	return `--branch ${scope.branchName}`;
}
async function listBranchesByName(client, projectId, branchName, signal) {
	const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
		params: {
			path: { projectId },
			query: { gitName: branchName }
		},
		signal
	});
	if (error || !data) throw apiCallError(`Failed to resolve branch "${branchName}"`, response, error);
	return data.data;
}
async function resolveExistingBranch(client, projectId, branchName, signal) {
	const branch = (await listBranchesByName(client, projectId, branchName, signal))[0];
	if (!branch) throw new CliStructuredError("PROJECT.ENV_BRANCH_NOT_FOUND", `Branch "${branchName}" not found`, {
		why: "Branch update, list, and delete commands only target existing preview branches.",
		nextActions: [userChoice("Create the branch by deploying it, or use `project env add --branch` to create its first override."), runCommand(`prisma project env add KEY=value --branch ${branchName}`)]
	});
	return branch;
}
async function resolveOrCreateBranch(client, projectId, branchName, signal) {
	const existing = (await listBranchesByName(client, projectId, branchName, signal))[0];
	if (existing) return existing;
	if (!await projectHasDefaultBranch(client, projectId, signal)) throw new CliStructuredError("PROJECT.ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH", `Cannot create branch "${branchName}" from project env`, {
		why: "Creating the first branch would make it the project default, but branch overrides are preview-only.",
		nextActions: [userChoice("Create or deploy the default branch first, then add the branch override."), runCommand("prisma git connect <repository-url>")]
	});
	const { data, error, response } = await client.POST("/v1/projects/{projectId}/branches", {
		params: { path: { projectId } },
		body: {
			gitName: branchName,
			isDefault: false
		},
		signal
	});
	if (error || !data) {
		if (response?.status === 409) {
			const raced = (await listBranchesByName(client, projectId, branchName, signal))[0];
			if (raced) return raced;
		}
		throw apiCallError(`Failed to create branch "${branchName}"`, response, error);
	}
	return data.data;
}
async function projectHasDefaultBranch(client, projectId, signal) {
	let cursor;
	while (true) {
		const query = {};
		if (cursor !== void 0) query.cursor = cursor;
		const result = await client.GET("/v1/projects/{projectId}/branches", {
			params: {
				path: { projectId },
				query
			},
			signal
		});
		if (result.error || !result.data) throw apiCallError("Failed to check project default branch", result.response, result.error);
		if (result.data.data.some((branch) => branch.isDefault)) return true;
		if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) return false;
		cursor = result.data.pagination.nextCursor;
	}
}
async function listVariables(client, projectId, resolved, signal) {
	return materializeEffectiveRows(await collectEnvironmentVariables(client, projectId, signal, {
		className: resolved.apiTarget.class,
		filter: (row) => rowMatchesScope(row, resolved)
	}), resolved);
}
async function listOverviewVariables(client, projectId, signal) {
	return (await collectEnvironmentVariables(client, projectId, signal, { filter: (row) => row.branchId === null && (row.class === "production" || row.class === "preview") })).sort((left, right) => {
		const roleOrder = roleSortOrder(left.class) - roleSortOrder(right.class);
		return roleOrder !== 0 ? roleOrder : left.key.localeCompare(right.key);
	});
}
async function collectEnvironmentVariables(client, projectId, signal, options) {
	const collected = [];
	let cursor;
	while (true) {
		const query = { projectId };
		if (options.className !== void 0) query.class = options.className;
		if (cursor !== void 0) query.cursor = cursor;
		const result = await client.GET("/v1/environment-variables", {
			params: { query },
			signal
		});
		if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
		const page = result.data.data.filter(options.filter);
		collected.push(...page);
		if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
		cursor = result.data.pagination.nextCursor;
	}
	return collected;
}
function roleSortOrder(role) {
	return role === "production" ? 0 : 1;
}
function rowMatchesScope(row, resolved) {
	if (row.class !== resolved.apiTarget.class) return false;
	if (resolved.apiTarget.branchId === null) return row.branchId === null;
	return row.branchId === null || row.branchId === resolved.apiTarget.branchId;
}
function materializeEffectiveRows(rows, resolved) {
	if (resolved.apiTarget.branchId === null) return rows;
	const byKey = /* @__PURE__ */ new Map();
	for (const row of rows) if (row.branchId === null && !byKey.has(row.key)) byKey.set(row.key, row);
	for (const row of rows) if (row.branchId === resolved.apiTarget.branchId) byKey.set(row.key, row);
	return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
}
//#endregion
//#region src/controllers/app-env-file.ts
async function runEnvAddFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
	const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
	const existingKeys = assignments.map((assignment) => assignment.key).filter((key) => existing.has(key));
	if (existingKeys.length > 0) throw new CliStructuredError("PROJECT.ENV_VARIABLE_ALREADY_EXISTS", `${existingKeys.length} environment variable(s) already exist in ${formatScopeLabel(resolved.scope)}`, {
		why: `Existing keys: ${formatKeyList(existingKeys)}.`,
		meta: { keys: existingKeys },
		nextActions: [userChoice("Split the input file by key state: update existing keys and add new keys separately."), ...splitFileActions(filePath, resolved.scope, {
			existingKeys,
			first: "update-existing"
		})]
	});
	const warnings = await missingPreviewDefaultWarnings(client, projectId, resolved.scope, assignments.map((assignment) => assignment.key), context.runtime.signal);
	const variables = [];
	for (const assignment of assignments) try {
		const { data, error, response } = await client.POST("/v1/environment-variables", {
			body: {
				projectId,
				class: resolved.apiTarget.class,
				...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
				key: assignment.key,
				value: assignment.value
			},
			signal: context.runtime.signal
		});
		if (error || !data) throw apiCallError(`Failed to add ${assignment.key}`, response, error);
		variables.push(toMetadata(data.data, resolved.descriptor));
	} catch (error) {
		throw envFileApplyFailedError("add", filePath, resolved.scope, assignment.key, variables, error);
	}
	return {
		command: "project.env.add",
		result: {
			projectId,
			verboseContext,
			scope: resolved.descriptor,
			variables,
			file: {
				path: filePath,
				count: variables.length
			}
		},
		warnings
	};
}
async function runEnvUpdateFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
	const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
	const missingKeys = assignments.map((assignment) => assignment.key).filter((key) => !existing.has(key));
	if (missingKeys.length > 0) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `${missingKeys.length} environment variable(s) not found in ${formatScopeLabel(resolved.scope)}`, {
		why: `Missing keys: ${formatKeyList(missingKeys)}.`,
		meta: { keys: missingKeys },
		nextActions: [userChoice("Split the input file by key state: add missing keys and update existing keys separately."), ...splitFileActions(filePath, resolved.scope, {
			missingKeys,
			first: "add-missing"
		})]
	});
	const variables = [];
	for (const assignment of assignments) {
		const existingVariable = existing.get(assignment.key);
		if (!existingVariable) continue;
		try {
			const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
				params: { path: { envVarId: existingVariable.id } },
				body: { value: assignment.value },
				signal: context.runtime.signal
			});
			if (error || !data) throw apiCallError(`Failed to update value for ${assignment.key}`, response, error);
			variables.push(toMetadata(data.data, resolved.descriptor));
		} catch (error) {
			throw envFileApplyFailedError("update", filePath, resolved.scope, assignment.key, variables, error);
		}
	}
	return {
		command: "project.env.update",
		result: {
			projectId,
			verboseContext,
			scope: resolved.descriptor,
			variables,
			file: {
				path: filePath,
				count: variables.length
			}
		},
		warnings: []
	};
}
async function findVariablesByNaturalKey(client, projectId, keys, resolved, signal) {
	const found = /* @__PURE__ */ new Map();
	for (const key of keys) {
		const row = await findVariableByNaturalKey(client, projectId, key, resolved, signal);
		if (row) found.set(key, row);
	}
	return found;
}
async function missingPreviewDefaultWarnings(client, projectId, scope, keys, signal) {
	if (scope.kind !== "branch") return [];
	const previewScope = {
		scope: {
			kind: "role",
			role: "preview"
		},
		descriptor: {
			kind: "role",
			role: "preview"
		},
		apiTarget: {
			class: "preview",
			branchId: null
		}
	};
	const missing = [];
	for (const key of keys) if (!await findVariableByNaturalKey(client, projectId, key, previewScope, signal)) missing.push(key);
	if (missing.length === 0) return [];
	if (missing.length === 1) return [`Variable "${missing[0]}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`];
	return [`Variables ${formatKeyList(missing)} do not exist in preview. They will only exist on ${formatScopeLabel(scope)}.`];
}
function envFileApplyFailedError(command, filePath, scope, failedKey, writtenVariables, error) {
	const writtenKeys = writtenVariables.map((variable) => variable.key);
	const cause = error instanceof Error ? error.message : "Unknown error.";
	return new CliStructuredError("PROJECT.ENV_FILE_APPLY_FAILED", `Failed to ${command} "${failedKey}" from "${filePath}"`, {
		why: writtenKeys.length === 0 ? `No variables were written before ${failedKey} failed. Cause: ${cause}` : `Written keys before failure: ${formatKeyList(writtenKeys)}. Cause: ${cause}`,
		meta: {
			file: filePath,
			failedKey,
			writtenKeys
		},
		cause: error,
		nextActions: [
			userChoice("Inspect the target scope, then retry the remaining keys once the API issue is resolved."),
			runCommand(`prisma project env list ${formatScopeFlag(scope)}`),
			runCommand(retryStepForApplyFailure(command, filePath, scope, writtenKeys))
		]
	});
}
function retryStepForApplyFailure(command, filePath, scope, writtenKeys) {
	if (command === "update") return `prisma project env update --file ${filePath} ${formatScopeFlag(scope)}`;
	if (writtenKeys.length === 0) return `prisma project env add --file ${filePath} ${formatScopeFlag(scope)}`;
	return `prisma project env add --file <remaining.env> ${formatScopeFlag(scope)}`;
}
/** Each command carries the key list it applies to as its reason. */
function splitFileActions(filePath, scope, options) {
	const scopeFlag = formatScopeFlag(scope);
	const existingFile = `${filePath}.existing`;
	const newFile = `${filePath}.new`;
	if (options.first === "update-existing") return [runCommand(`prisma project env update --file ${existingFile} ${scopeFlag}`, `existing keys: ${formatKeyList(options.existingKeys)}`), runCommand(`prisma project env add --file ${newFile} ${scopeFlag}`, "new keys only")];
	return [runCommand(`prisma project env add --file ${newFile} ${scopeFlag}`, `missing keys: ${formatKeyList(options.missingKeys)}`), runCommand(`prisma project env update --file ${existingFile} ${scopeFlag}`, "existing keys only")];
}
function formatKeyList(keys) {
	return keys.map((key) => `"${key}"`).join(", ");
}
function formatScopeFlag(scope) {
	if (scope.kind === "role") return `--role ${scope.role}`;
	return `--branch ${scope.branchName}`;
}
//#endregion
//#region src/presenters/verbose-context.ts
function stripVerboseContext(result) {
	const { verboseContext: _verboseContext, ...serialized } = result;
	return serialized;
}
//#endregion
//#region src/presenters/app-env.ts
function scopeLabel(scope) {
	if (scope.kind === "role") return scope.role ?? "unknown";
	if (scope.kind === "overview") return "overview";
	return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
}
function listTargetLabel(result) {
	const target = result.target;
	if (target.source === "overview") return "overview";
	if (target.branchName) {
		const suffix = target.branchExists === false ? " (not created yet)" : "";
		return `branch:${target.branchName} -> ${target.envMap}${suffix}`;
	}
	return scopeLabel(result.scope);
}
function serializeEnvList(result) {
	const serializable = stripVerboseContext(result);
	return {
		projectId: serializable.projectId,
		scope: serializable.scope,
		target: serializable.target,
		...serializeList({
			context: { target: listTargetLabel(serializable) },
			items: serializable.variables.map((variable) => ({
				noun: "variable",
				label: `${variable.key} (${variable.source})`,
				id: variable.id,
				status: variable.isManagedBySystem ? "default" : null
			}))
		}),
		variables: serializable.variables
	};
}
//#endregion
//#region src/commands/project/env-shared.ts
/** Flags, scope resolution and presentation shared by the
*  `project env *` commands. */
const roleFlag = flag.enum({
	brief: "Project template scope (production or preview)",
	values: ["production", "preview"]
});
const projectFlag = flag.string({
	brief: "Project id or name",
	placeholder: "id-or-name"
});
const branchFlag = flag.string({
	brief: "Preview branch override scope",
	placeholder: "git-name"
});
const fileFlag = flag.string({
	brief: "Read KEY=VALUE assignments from a dotenv file",
	placeholder: "path"
});
function requireEnvScope(flags, command) {
	const scope = resolveEnvScope({
		roleName: flags.role,
		branchName: flags.branch
	}, {
		requireExplicit: true,
		command
	});
	if (!scope) throw envUsageError(`prisma project env ${command} requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma project env ${command} KEY=value --role production`]);
	return scope;
}
/** Workspace, pinned project and the API scope every env write needs. */
async function resolveEnvTarget(ctx, flags, scope, commandName, createBranchIfMissing) {
	const workspace = await resolveActiveWorkspace(ctx);
	const target = await resolvePinnedProject(ctx, workspace, flags.project, commandName);
	const resolved = await resolveScopeToApi(ctx.api, target.project.id, scope, {
		createBranchIfMissing,
		signal: ctx.signal
	});
	return {
		projectId: target.project.id,
		verboseContext: {
			workspace,
			project: target.project,
			resolution: target.resolution
		},
		resolved
	};
}
/** The human table's rows: the first cell glues the key to where the
*  value comes from, which is what a reader wants to see. */
function variableRows(variables) {
	return variables.map((variable) => [
		`${variable.key} (${variable.source})`,
		variable.id,
		variable.isManagedBySystem ? "default" : ""
	]);
}
/** The stdout lane's rows: the bare key, because a consumer piping this
*  should not have to split on `" ("` to recover it (conventions §8 —
*  the stdout lane carries data, not decoration). The source is in the
*  `--json` record. */
function variableStdoutRows(variables) {
	return variables.map((variable) => [
		variable.key,
		variable.id,
		variable.isManagedBySystem ? "default" : ""
	]);
}
function variableFieldRows(projectId, scope, variable) {
	return [
		{
			label: "project",
			value: projectId
		},
		{
			label: "scope",
			value: scopeLabel(scope)
		},
		{
			label: "key",
			value: variable.key
		},
		{
			label: "id",
			value: variable.id
		},
		{
			label: "last updated",
			value: variable.updatedAt
		}
	];
}
function fileWritePresentations(input, result) {
	const rows = variableRows(input.variables);
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: input.title
			},
			{
				kind: "fields",
				rows: [{
					label: "target",
					value: `${scopeLabel(input.scope)} from ${input.filePath}`
				}]
			},
			...rows.length === 0 ? [{
				kind: "list",
				items: [input.emptyMessage]
			}] : [{
				kind: "table",
				columns: [
					"variable",
					"id",
					"status"
				],
				rows
			}]
		]
	};
}
/** The legacy "the key has no preview default" warnings, which the engine
*  envelope carries as warn diagnostics. */
function previewDefaultDiagnostics(warnings) {
	return warnings.map((warning) => ({
		code: "PROJECT.ENV_PREVIEW_DEFAULT_MISSING",
		severity: "warn",
		summary: warning,
		nextActions: []
	}));
}
//#endregion
//#region src/commands/project/env-add.ts
/** The `project env add` command. */
const TITLE$4 = "Setting a new environment variable.";
function singlePresentations$1(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "info",
			text: TITLE$4
		}, {
			kind: "fields",
			rows: variableFieldRows(result.projectId, result.scope, result.variable)
		}]
	};
}
const projectEnvAddCommand = defineCommand({
	args: {
		positionals: { assignment: positional.optionalString({
			brief: "Variable assignment as KEY=VALUE or KEY from the current environment",
			placeholder: "assignment"
		}) },
		flags: {
			file: fileFlag,
			role: roleFlag,
			branch: branchFlag,
			project: projectFlag
		}
	},
	help: {
		summary: "Create a new environment variable.",
		examples: [
			"project env add STRIPE_KEY=sk_test_xxx --role production",
			"project env add STRIPE_KEY=sk_test_xxx --role preview",
			"project env add --file .env --role preview",
			"project env add DATABASE_URL=postgresql://branch --branch feature/foo",
			"project env add --file .env.local --branch feature/foo",
			"API_URL=https://api.example prisma project env add API_URL --project proj_123 --role preview"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "add");
		const scope = requireEnvScope(args.flags, "add");
		const input = await resolveEnvWriteInput(operationContext(ctx), source, "add");
		const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env add", true);
		if (input.kind === "file") {
			const written = await runEnvAddFile(operationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
			const result = {
				projectId,
				scope: resolved.descriptor,
				variables: written.result.variables,
				file: written.result.file
			};
			return ok(ctx.present({
				data: result,
				diagnostics: previewDefaultDiagnostics(written.warnings)
			}, fileWritePresentations({
				title: "Setting new environment variables from file.",
				emptyMessage: "No environment variables imported.",
				scope: result.scope,
				filePath: result.file.path,
				variables: result.variables
			}, result)));
		}
		if (await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal)) throw new CliStructuredError("PROJECT.ENV_VARIABLE_ALREADY_EXISTS", `Variable "${input.key}" already exists in ${formatScopeLabel(scope)}`, {
			why: "A variable with this key already exists in the targeted scope.",
			nextActions: [userChoice("Use `prisma project env update` to change an existing variable's value."), runCommand(`prisma project env update ${input.key}=<new-value> ${formatScopeFlag$1(scope)}`)]
		});
		const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(ctx.api, projectId, input.key, {
			descriptor: {
				kind: "role",
				role: "preview"
			},
			apiTarget: {
				class: "preview",
				branchId: null
			}
		}, ctx.signal) ? [`Variable "${input.key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
		const { data, error, response } = await ctx.api.POST("/v1/environment-variables", {
			body: {
				projectId,
				class: resolved.apiTarget.class,
				...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
				key: input.key,
				value: input.value
			},
			signal: ctx.signal
		});
		if (error || !data) throw apiCallError(`Failed to add ${input.key}`, response, error);
		const result = {
			projectId,
			scope: resolved.descriptor,
			variable: toMetadata(data.data, resolved.descriptor)
		};
		return ok(ctx.present({
			data: result,
			diagnostics: previewDefaultDiagnostics(warnings)
		}, singlePresentations$1(result)));
	}
});
//#endregion
//#region src/commands/project/env-delete.ts
/** The `project env delete` command. */
const TITLE$3 = "Deleting the environment variable from the scope.";
function deletePresentations$1(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "info",
			text: TITLE$3
		}, {
			kind: "fields",
			rows: [
				{
					label: "project",
					value: result.projectId
				},
				{
					label: "scope",
					value: scopeLabel(result.scope)
				},
				{
					label: "key",
					value: result.key
				}
			]
		}]
	};
}
const projectEnvDeleteCommand = defineCommand({
	args: {
		positionals: { key: positional.string({
			brief: "Variable key to delete",
			placeholder: "key"
		}) },
		flags: {
			role: roleFlag,
			branch: branchFlag,
			project: projectFlag
		}
	},
	help: {
		summary: "Delete an environment variable from a scope.",
		examples: [
			"project env delete STRIPE_KEY --role production",
			"project env delete STRIPE_KEY --role preview",
			"project env delete DATABASE_URL --branch feature/foo"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const key = args.positionals.key;
		const scope = requireEnvScope(args.flags, "delete");
		const { projectId, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env delete", false);
		const existing = await findVariableByNaturalKey(ctx.api, projectId, key, resolved, ctx.signal);
		if (!existing) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `Variable "${key}" not found in ${formatScopeLabel(scope)}`, {
			why: "No variable with this key exists in the targeted scope, so there is nothing to delete.",
			nextActions: [userChoice("Run prisma project env list with the same scope to see the available variables."), runCommand(`prisma project env list ${formatScopeFlag$1(scope)}`)]
		});
		const { error, response } = await ctx.api.DELETE("/v1/environment-variables/{envVarId}", {
			params: { path: { envVarId: existing.id } },
			signal: ctx.signal
		});
		if (error) throw apiCallError(`Failed to delete ${key}`, response, error);
		const result = {
			projectId,
			scope: resolved.descriptor,
			key
		};
		return ok(ctx.present({ data: result }, deletePresentations$1(result)));
	}
});
//#endregion
//#region src/commands/project/env-list.ts
/** The `project env list` command. */
const TITLE$2 = "Listing environment variables for the selected scope.";
function listPresentations$2(result, addScopeFlag) {
	const rows = variableRows(result.variables);
	const stdoutRows = variableStdoutRows(result.variables);
	return {
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE$2
			},
			{
				kind: "fields",
				rows: [{
					label: "target",
					value: listTargetLabel(result)
				}]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No environment variables defined in this scope."
			}] : [{
				kind: "table",
				columns: [
					"variable",
					"id",
					"status"
				],
				rows
			}]
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeEnvList(result),
		next: () => result.variables.length === 0 ? [{
			kind: "run-command",
			label: `${CLI_NAME} project env add KEY=value ${addScopeFlag}`,
			command: `${CLI_NAME} project env add KEY=value ${addScopeFlag}`
		}] : []
	};
}
const projectEnvListCommand = defineCommand({
	args: { flags: {
		role: roleFlag,
		branch: flag.string({
			brief: "Preview branch resolved scope",
			placeholder: "git-name"
		}),
		project: projectFlag
	} },
	help: {
		summary: "List environment variable metadata for a scope (no values).",
		examples: [
			"project env list",
			"project env list --role production",
			"project env list --role preview",
			"project env list --branch feature/foo"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const explicit = resolveEnvScope({
			roleName: args.flags.role,
			branchName: args.flags.branch
		}, {
			requireExplicit: false,
			command: "list"
		});
		const projectId = (await resolvePinnedProject(ctx, await resolveActiveWorkspace(ctx), args.flags.project, "project env list")).project.id;
		const resolved = await resolveListScopeToApi(ctx.api, projectId, explicit ?? void 0, { signal: ctx.signal });
		const rows = resolved.kind === "scoped" ? await listVariables(ctx.api, projectId, {
			scope: resolved.addScope,
			descriptor: resolved.descriptor,
			apiTarget: resolved.apiTarget
		}, ctx.signal) : await listOverviewVariables(ctx.api, projectId, ctx.signal);
		const result = {
			projectId,
			scope: resolved.descriptor,
			target: resolved.target,
			variables: rows.map((row) => toMetadata(row, resolved.descriptor))
		};
		return ok(ctx.present({ data: result }, listPresentations$2(result, formatScopeFlag$1(resolved.addScope))));
	}
});
//#endregion
//#region src/commands/project/env-update.ts
/** The `project env update` command. */
const TITLE$1 = "Replacing the environment variable's value.";
function singlePresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [{
			kind: "summary",
			status: "info",
			text: TITLE$1
		}, {
			kind: "fields",
			rows: variableFieldRows(result.projectId, result.scope, result.variable)
		}]
	};
}
const projectEnvUpdateCommand = defineCommand({
	args: {
		positionals: { assignment: positional.optionalString({
			brief: "Variable assignment as KEY=VALUE or KEY from the current environment",
			placeholder: "assignment"
		}) },
		flags: {
			file: fileFlag,
			role: roleFlag,
			branch: branchFlag,
			project: projectFlag
		}
	},
	help: {
		summary: "Replace an existing environment variable's value.",
		examples: [
			"project env update STRIPE_KEY=sk_new_xxx --role production",
			"project env update STRIPE_KEY=sk_new_xxx --role preview",
			"project env update --file .env --role production",
			"project env update DATABASE_URL=postgresql://branch --branch feature/foo"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const source = resolveEnvWriteSource(args.positionals.assignment, args.flags.file, "update");
		const scope = requireEnvScope(args.flags, "update");
		const input = await resolveEnvWriteInput(operationContext(ctx), source, "update");
		const { projectId, verboseContext, resolved } = await resolveEnvTarget(ctx, args.flags, scope, "project env update", false);
		if (input.kind === "file") {
			const written = await runEnvUpdateFile(operationContext(ctx), ctx.api, projectId, resolved, input.filePath, input.assignments, verboseContext);
			const result = {
				projectId,
				scope: resolved.descriptor,
				variables: written.result.variables,
				file: written.result.file
			};
			return ok(ctx.present({ data: result }, fileWritePresentations({
				title: "Replacing environment variable values from file.",
				emptyMessage: "No environment variables updated.",
				scope: result.scope,
				filePath: result.file.path,
				variables: result.variables
			}, result)));
		}
		const existing = await findVariableByNaturalKey(ctx.api, projectId, input.key, resolved, ctx.signal);
		if (!existing) throw new CliStructuredError("PROJECT.ENV_VARIABLE_NOT_FOUND", `Variable "${input.key}" not found in ${formatScopeLabel(scope)}`, {
			why: "No variable with this key exists in the targeted scope.",
			nextActions: [userChoice("Use `prisma project env add` to create a new variable."), runCommand(`prisma project env add ${input.key}=<value> ${formatScopeFlag$1(scope)}`)]
		});
		const { data, error, response } = await ctx.api.PATCH("/v1/environment-variables/{envVarId}", {
			params: { path: { envVarId: existing.id } },
			body: { value: input.value },
			signal: ctx.signal
		});
		if (error || !data) throw apiCallError(`Failed to update value for ${input.key}`, response, error);
		const result = {
			projectId,
			scope: resolved.descriptor,
			variable: toMetadata(data.data, resolved.descriptor)
		};
		return ok(ctx.present({ data: result }, singlePresentations(result)));
	}
});
//#endregion
//#region src/commands/project/link.ts
/** The `project link` command. */
const CREATE_CHOICE = "__create__";
const CANCEL_CHOICE = "__cancel__";
function setupCanceledError() {
	return new CliStructuredError("PROJECT.USAGE_ERROR", "Project setup canceled", {
		why: "Project link needs a Project before it can continue.",
		nextActions: [
			{
				kind: "user-choice",
				label: "Choose an existing Project or create a new one, then rerun project link."
			},
			{
				kind: "run-command",
				label: "prisma project link <id-or-name>",
				command: "prisma project link <id-or-name>"
			},
			{
				kind: "run-command",
				label: "prisma project create <name>",
				command: "prisma project create <name>"
			}
		]
	});
}
function choiceOptions(projects) {
	const sorted = sortProjects(projects);
	const duplicated = new Set(sorted.map((project) => project.name).filter((name, index, names) => names.indexOf(name) !== index));
	return [
		{
			value: CREATE_CHOICE,
			label: "+ Create a new Project"
		},
		...sorted.map((project) => ({
			value: project.id,
			label: duplicated.has(project.name) ? `${project.name} (${project.id})` : project.name
		})),
		{
			value: CANCEL_CHOICE,
			label: "Cancel"
		}
	];
}
async function createProjectForLink(ctx, workspace, projectName) {
	const created = await createAppProvider(ctx.api).createProject({
		name: projectName,
		signal: ctx.signal
	}).catch((error) => {
		/** A cancelled run is cancelled, not a failed creation. The
		*  provider flattens the underlying AbortError into a plain
		*  Error, which the engine would settle as a bug, so hand it
		*  back its own abort reason and let it settle the run as
		*  cancelled. */
		if (ctx.signal.aborted) throw ctx.signal.reason;
		throw projectCreateFailedError(error, projectName, workspace, {
			nextSteps: [
				"prisma project list",
				"prisma project link <id-or-name>",
				`prisma project create ${formatCommandArgument(projectName)}`
			],
			permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
			fallbackFix: "Retry the command, or choose an existing Project with prisma project link <id-or-name>."
		});
	});
	return {
		id: created.id,
		name: created.name
	};
}
async function pickProject(ctx, workspace, projects) {
	const choice = await ctx.prompt.select("Which Project should this directory use?", choiceOptions(projects));
	if (choice === CANCEL_CHOICE) throw setupCanceledError();
	if (choice === CREATE_CHOICE) {
		const suggested = await inferTargetName(ctx.cwd, ctx.signal);
		const name = await ctx.prompt.text("Project name", {
			placeholder: suggested.name,
			default: suggested.name
		});
		if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project link");
		return await bindDirectoryToProject(ctx, workspace, await createProjectForLink(ctx, workspace, name.trim()), "created");
	}
	const project = projects.find((candidate) => candidate.id === choice);
	if (!project) throw setupCanceledError();
	return await bindDirectoryToProject(ctx, workspace, toProjectSummary(project), "linked");
}
/** The link itself, without the command around it: resolve the named
*  Project or pick one, then bind `ctx.cwd` to it. */
async function linkDirectoryToProject(ctx, projectRef) {
	const workspace = await resolveActiveWorkspace(ctx);
	const projects = await listWorkspaceProjects$1(ctx);
	const ref = projectRef?.trim();
	return ref ? await bindDirectoryToProject(ctx, workspace, toProjectSummary(resolveProjectForSetup(ref, projects, workspace)), "linked") : await pickProject(ctx, workspace, projects);
}
const projectLinkCommand = defineCommand({
	args: { positionals: { project: positional.optionalString({
		brief: "Project id or name",
		placeholder: "id-or-name"
	}) } },
	help: {
		summary: "Link this directory to a Project",
		examples: [
			"project link",
			"project link proj_123",
			"project link \"Acme Dashboard\" --json"
		]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const result = await linkDirectoryToProject(ctx, args.positionals.project);
		return ok(ctx.present({ data: result }, setupPresentations(result)));
	}
});
//#endregion
//#region src/commands/project/list.ts
/** The `project list` command. */
const TITLE = "Listing projects for the authenticated workspace.";
/** An absent region stays empty; the table renderer draws the dim
*  placeholder dash. */
function projectRows(result) {
	return result.projects.map((project) => [
		project.name,
		project.id,
		project.defaultRegion ?? ""
	]);
}
/** The stdout rows: a project with no default region has an empty
*  region field, not the word the human table shows. */
function projectStdoutRows(result) {
	return result.projects.map((project) => [
		project.name,
		project.id,
		project.defaultRegion ?? ""
	]);
}
function nextActionsFor(result) {
	if (result.localBinding?.status === "linked") return [];
	return buildProjectSetupNextActions({
		createCommand: `${CLI_NAME} project create <name>`,
		reason: result.localBinding?.status === "invalid" ? "This directory has an invalid local Project binding. Ask the user which Prisma Project to link before running Project-scoped commands." : "This directory is not linked to a Prisma Project. Project list shows available Projects, but none is selected for this directory."
	});
}
function listPresentations$1(result) {
	const rows = projectRows(result);
	const stdoutRows = projectStdoutRows(result);
	return {
		human: () => [
			{
				kind: "summary",
				status: "info",
				text: TITLE
			},
			{
				kind: "fields",
				rows: [{
					label: "workspace",
					value: result.workspace.name
				}]
			},
			...rows.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No projects found."
			}] : [{
				kind: "table",
				columns: [
					"name",
					"id",
					"region"
				],
				rows
			}]
		],
		stdout: () => stdoutRows.map((row) => row.join("	")),
		json: () => serializeProjectList(result),
		next: () => nextActionsFor(result)
	};
}
const projectListCommand = defineCommand({
	help: {
		summary: "List all projects in your workspace",
		examples: ["project list", "project list --json"]
	},
	needs: { credentials: true },
	handler: async (_args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		const projects = sortProjects(await listWorkspaceProjects$1(ctx));
		const localBinding = await readProjectListLocalBinding(ctx.cwd, projects, ctx.signal);
		const result = {
			workspace,
			projects: projects.map(toProjectSummary),
			localBinding
		};
		return ok(ctx.present({ data: result }, listPresentations$1(result)));
	}
});
//#endregion
//#region src/commands/project/rename.ts
/** The `project rename` command. */
function renamePresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Renaming project."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "workspace",
						value: result.workspace.name
					},
					{
						label: "project",
						value: result.previousName
					},
					{
						label: "id",
						value: result.project.id
					}
				]
			},
			{
				kind: "list",
				items: [`The project is now named "${result.project.name}". Directory bindings pin the project id, so they stay valid.`]
			}
		]
	};
}
const projectRenameCommand = defineCommand({
	args: {
		positionals: { name: positional.string({
			brief: "New project name",
			placeholder: "name"
		}) },
		flags: { project: flag.string({
			brief: "Project id or name",
			placeholder: "id-or-name"
		}) }
	},
	help: {
		summary: "Rename the resolved Project",
		examples: ["project rename \"Acme Dashboard v2\"", "project rename billing-api --project proj_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		const name = args.positionals.name.trim();
		if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
		const target = await resolvePinnedProject(ctx, workspace, args.flags.project, "project rename");
		const result = {
			workspace,
			project: await createManagementProjectProvider(ctx.api).renameProject({
				projectId: target.project.id,
				name,
				signal: ctx.signal
			}),
			previousName: target.project.name
		};
		return ok(ctx.present({ data: result }, renamePresentations(result)));
	}
});
//#endregion
//#region src/lib/fs/home-path.ts
/**
* Shortens a path under the user's home directory to `~/...` for display,
* posix-style on every platform. Falls back to the Windows home variables
* when `HOME` is unset (native cmd/PowerShell sessions).
*/
function shortenHomePath(value, env) {
	const resolved = path.resolve(value);
	const home = resolveHomeDirectory(env);
	if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
		const relative = path.relative(home, resolved).split(path.sep).join("/");
		return relative ? `~/${relative}` : "~";
	}
	return resolved;
}
function resolveHomeDirectory(env) {
	if (env.HOME) return path.resolve(env.HOME);
	if (env.USERPROFILE) return path.resolve(env.USERPROFILE);
	if (env.HOMEDRIVE && env.HOMEPATH) return path.resolve(`${env.HOMEDRIVE}${env.HOMEPATH}`);
	return null;
}
//#endregion
//#region src/commands/project/show.ts
/** The `project show` command. */
function fieldRows(result, cwd, env) {
	if (result.project === null) return [{
		label: "workspace",
		value: result.workspace.name
	}, {
		label: "project",
		value: "Not linked"
	}];
	return [
		{
			label: "local repo",
			value: shortenHomePath(cwd, env)
		},
		{
			label: "platform",
			value: `${result.workspace.name} / ${result.project.name}`
		},
		...result.project.url ? [{
			label: "url",
			value: result.project.url
		}] : [],
		...result.project.defaultRegion ? [{
			label: "region",
			value: result.project.defaultRegion
		}] : []
	];
}
/** The stdout mirror. Three human affordances stay on the human side:
*  the home directory shortened to `~`, the workspace and project glued
*  into one "platform" line, and the words "Not linked" standing in for
*  an absent project. stdout gets the raw path and one fact per line,
*  under the labels this same command already uses when the directory
*  is not linked. */
function stdoutFieldRows(result, cwd) {
	return [
		{
			label: "local repo",
			value: cwd
		},
		{
			label: "workspace",
			value: result.workspace.name
		},
		{
			label: "project",
			value: result.project?.name ?? ""
		},
		...result.project?.url ? [{
			label: "url",
			value: result.project.url
		}] : [],
		...result.project?.defaultRegion ? [{
			label: "region",
			value: result.project.defaultRegion
		}] : []
	];
}
function showPresentations$1(result, cwd, env) {
	const rows = fieldRows(result, cwd, env);
	return {
		json: () => result,
		human: () => [result.project === null ? {
			kind: "summary",
			status: "warn",
			text: "This directory is not linked to a Prisma Project."
		} : {
			kind: "summary",
			status: "info",
			text: result.resolution.projectSource === "explicit" ? "Showing the named project (this directory's own link, if any, is unchanged)." : "This directory is linked to the following platform project."
		}, {
			kind: "fields",
			rows
		}],
		stdout: () => stdoutFieldRows(result, cwd).map((row) => `${row.label}: ${row.value}`),
		next: () => result.project === null ? buildProjectSetupNextActions({
			commandName: "project show",
			retryCommand: "prisma project show <id-or-name>",
			suggestedProjectName: result.suggestedProjectName,
			reason: "This directory is not linked to a Prisma Project. Package and directory names can suggest setup defaults, but they do not select a Project."
		}) : []
	};
}
const projectShowCommand = defineCommand({
	args: { positionals: { project: positional.optionalString({
		brief: "Project id or name (default: the linked project)",
		placeholder: "id-or-name"
	}) } },
	help: {
		summary: "Show this directory's Project binding",
		examples: ["project show", "project show proj_123 --json"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		const inspected = await inspectProjectBinding({
			context: operationContext(ctx),
			workspace,
			explicitProject: args.positionals.project,
			listProjects: () => listWorkspaceProjects$1(ctx),
			commandName: "project show"
		});
		if (inspected.isErr()) throw projectResolutionErrorToStructured(inspected.error);
		const result = inspected.value;
		return ok(ctx.present({ data: result }, showPresentations$1(result, ctx.cwd, ctx.env)));
	}
});
//#endregion
//#region src/auth/errors.ts
function actions(fix, commands) {
	return [{
		kind: "user-choice",
		label: fix
	}, ...commands.map((command) => ({
		kind: "run-command",
		label: command,
		command
	}))];
}
function workspaceNotAuthenticatedError(workspaceRef) {
	return new CliStructuredError("AUTH.WORKSPACE_NOT_AUTHENTICATED", "Workspace is not authenticated", {
		why: `No stored OAuth session matched "${workspaceRef}".`,
		meta: { workspaceRef },
		nextActions: actions(`Run ${CLI_NAME} auth login and authorize that workspace, then switch to it.`, [`${CLI_NAME} auth workspace list`, `${CLI_NAME} auth login`])
	});
}
function workspaceAmbiguousError(workspaceRef, matches) {
	return new CliStructuredError("AUTH.WORKSPACE_AMBIGUOUS", "Workspace name is ambiguous", {
		why: `Multiple authenticated workspaces matched "${workspaceRef}".`,
		meta: {
			workspaceRef,
			matches
		},
		nextActions: actions(`Run ${CLI_NAME} auth workspace list and switch by workspace id.`, [`${CLI_NAME} auth workspace list`])
	});
}
//#endregion
//#region src/auth/recipient.ts
var RecipientSessionInvalidError = class extends Error {
	constructor(workspaceRef) {
		super(`The stored session for workspace "${workspaceRef}" could not be validated.`);
		this.workspaceRef = workspaceRef;
		this.name = "RecipientSessionInvalidError";
	}
};
/**
* Resolve a locally stored OAuth workspace session and return a validated
* access token for it, refreshing through the SDK when the stored token has
* expired. The active workspace pointer is never touched.
*
* Throws WorkspaceSelectionError when the ref does not match exactly one
* stored session, and RecipientSessionInvalidError when the session cannot
* be validated or refreshed.
*/
async function resolveRecipientWorkspaceSession(workspaceRef, env = process.env, signal) {
	const workspace = await new FileTokenStorage(env, signal).resolveWorkspace(workspaceRef);
	const pinnedStorage = new FileTokenStorage(env, signal, {
		activateOnSetTokens: false,
		lockSetTokens: false,
		pinnedWorkspaceId: workspace.credentialWorkspaceId
	});
	if ((await createManagementApiSdk({
		clientId: "cmm3lndn701oo0uefvxzo0ivw",
		redirectUri: "http://localhost:0/auth/callback",
		tokenStorage: pinnedStorage,
		apiBaseUrl: getApiBaseUrl(env)
	}).client.GET("/v1/workspaces", { signal })).error) throw new RecipientSessionInvalidError(workspaceRef);
	const tokens = await pinnedStorage.getTokens();
	if (!tokens) throw new RecipientSessionInvalidError(workspaceRef);
	return {
		workspace,
		accessToken: tokens.accessToken
	};
}
//#endregion
//#region src/commands/project/transfer.ts
/** The `project transfer` command. */
const CONSENT_QUESTION = "Transferring moves the project to another workspace and this workspace loses access, so it requires the exact project id.";
/** This CLI's command strings are `${CLI_NAME} …`; the legacy package-runner
*  formatter does not port. */
const formatCommand = (args) => [CLI_NAME, ...args].join(" ");
function recipientSourceError(workspaceRef, error) {
	if (error instanceof WorkspaceSelectionError) {
		if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
			id: match.id,
			name: match.name,
			credentialWorkspaceId: match.credentialWorkspaceId
		})));
		throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
	}
	if (error instanceof RecipientSessionInvalidError) throw workspaceNotAuthenticatedError(error.workspaceRef);
	throw error;
}
async function resolveRecipient(ctx, options) {
	const recipientToken = options.recipientToken?.trim();
	if (recipientToken) return {
		accessToken: recipientToken,
		workspaceId: null,
		workspaceName: null,
		source: "recipient-token"
	};
	/** The handler rejects a run carrying neither recipient flag before
	*  it reaches here, so a blank `--to-workspace` means the recipient
	*  token was set and returned above. */
	const workspaceRef = options.toWorkspace?.trim() ?? "";
	if (ctx.env[SERVICE_TOKEN_ENV_VAR] !== void 0) throw transferRecipientUnavailableError(formatCommand);
	try {
		const session = await resolveRecipientWorkspaceSession(workspaceRef, ctx.env, ctx.signal);
		return {
			accessToken: session.accessToken,
			workspaceId: session.workspace.id,
			workspaceName: session.workspace.name,
			source: "workspace-session"
		};
	} catch (error) {
		recipientSourceError(workspaceRef, error);
	}
}
function transferPresentations(result, toWorkspace) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [
			{
				kind: "summary",
				status: "ok",
				text: "Transferring project."
			},
			{
				kind: "fields",
				rows: [
					{
						label: "workspace",
						value: result.workspace.name
					},
					{
						label: "project",
						value: result.project.name
					},
					{
						label: "id",
						value: result.project.id
					},
					{
						label: "recipient",
						value: result.recipient.workspaceName ?? result.recipient.workspaceId ?? "workspace of the provided recipient token"
					}
				]
			},
			{
				kind: "list",
				items: [
					"The project now belongs to the recipient workspace; this workspace no longer has access.",
					...result.localPin.action === "rewritten" ? ["This directory's local project binding now points at the recipient workspace."] : [],
					...result.localPin.action === "cleared" ? ["This directory's local project binding was cleared."] : []
				]
			}
		],
		next: () => toWorkspace ? [{
			kind: "run-command",
			label: `${CLI_NAME} auth workspace use ${formatCommandArgument(toWorkspace)}`,
			command: `${CLI_NAME} auth workspace use ${formatCommandArgument(toWorkspace)}`
		}] : []
	};
}
const projectTransferCommand = defineCommand({
	args: {
		positionals: { project: positional.string({
			brief: "Project id or name",
			placeholder: "id-or-name"
		}) },
		flags: {
			toWorkspace: flag.string({
				brief: "Locally authenticated workspace to receive the project",
				placeholder: "id-or-name"
			}),
			recipientToken: flag.string({
				brief: "Access token for the receiving workspace",
				placeholder: "token"
			})
		}
	},
	help: {
		summary: "Transfer a Project to another workspace after exact id confirmation",
		examples: ["project transfer proj_123 --to-workspace \"Prisma Labs\" --confirm proj_123", "project transfer proj_123 --recipient-token <token> --confirm proj_123"]
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const workspace = await resolveActiveWorkspace(ctx);
		const toWorkspace = args.flags.toWorkspace?.trim() || void 0;
		const recipientToken = args.flags.recipientToken?.trim() || void 0;
		if (toWorkspace && recipientToken) {
			const retry = formatCommand([
				"project",
				"transfer",
				"<project>",
				"--to-workspace",
				"<id-or-name>",
				"--confirm",
				"<project-id>"
			]);
			throw new CliStructuredError("PROJECT.USAGE_ERROR", "Choose one transfer recipient source", {
				why: "--to-workspace and --recipient-token are mutually exclusive.",
				nextActions: [{
					kind: "user-choice",
					label: "Pass either --to-workspace <id-or-name> or --recipient-token <token>."
				}, {
					kind: "run-command",
					label: retry,
					command: retry
				}]
			});
		}
		if (!toWorkspace && !recipientToken) throw transferRecipientRequiredError(formatCommand);
		const projects = await listWorkspaceProjects$1(ctx);
		const project = toProjectSummary(resolveProjectForSetup(args.positionals.project.trim(), projects, workspace));
		await ctx.prompt.consent(CONSENT_QUESTION, { token: project.id });
		const recipient = await resolveRecipient(ctx, {
			toWorkspace,
			recipientToken
		});
		await createManagementProjectProvider(ctx.api).transferProject({
			projectId: project.id,
			recipientAccessToken: recipient.accessToken,
			signal: ctx.signal
		});
		const warnings = [];
		const action = await rewriteOrClearLocalPinForProject(operationContext(ctx), project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
		const result = {
			workspace,
			project,
			recipient: {
				workspaceId: recipient.workspaceId,
				workspaceName: recipient.workspaceName,
				source: recipient.source
			},
			localPin: { action }
		};
		const diagnostics = localPinDiagnostics(warnings);
		return ok(ctx.present({
			data: result,
			diagnostics
		}, transferPresentations(result, toWorkspace)));
	}
});
//#endregion
//#region src/lib/app/domain-guidance.ts
function formatDomainFailureFix(domain) {
	if (domain.status !== "failed") return null;
	const dnsRecord = domain.dnsRecords[0];
	if (domain.failureCategory === "dns") {
		if (dnsRecord) return `Add ${dnsRecord.type} ${dnsRecord.name} -> ${dnsRecord.value}, then run prisma service domain retry ${domain.hostname}.`;
		return `DNS verification failed, but the platform did not return a DNS record. Run prisma service domain show ${domain.hostname} later, then retry when the DNS target is available.`;
	}
	if (domain.failureCategory === "acme") return `Retry TLS issuance with prisma service domain retry ${domain.hostname}. Contact support if it fails again.`;
	if (domain.failureCategory === "storage") return `Retry provisioning with prisma service domain retry ${domain.hostname}. Contact support if it fails again.`;
	return `Run prisma service domain retry ${domain.hostname}. Contact support if it fails again.`;
}
//#endregion
//#region src/commands/service/errors.ts
function runCommandAction(label, command) {
	return {
		kind: "run-command",
		label,
		command: `${CLI_NAME} ${command}`
	};
}
function adviceAction(label) {
	return {
		kind: "user-choice",
		label
	};
}
const CNAME_HINT = /\bcname(?:s)?\s+to\b/;
const PRISMA_BUILD_HOST = /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i;
/**
* Consent declined interactively. The engine settles this code as a
* user cancellation (exit 3).
*/
function userCancelledError(summary) {
	return new CliStructuredError("CLI.PROMPT_CANCELLED", summary);
}
function workspaceRequiredError() {
	return new CliStructuredError("SERVICE.WORKSPACE_REQUIRED", "No workspace is selected for the current session", {
		why: "Platform commands need an authenticated session with an accessible workspace.",
		nextActions: [runCommandAction("Sign in", "auth login")]
	});
}
function serviceSelectionInvalidError(serviceName, projectId) {
	return new CliStructuredError("SERVICE.SELECTION_INVALID", "The requested service does not exist in the resolved project", {
		why: `The service "${serviceName}" could not be found in resolved project "${projectId}".`,
		nextActions: [adviceAction("Pass the id or name of an existing service."), runCommandAction("List services", "service list")]
	});
}
function serviceNameRequiredError() {
	return new CliStructuredError("SERVICE.NAME_REQUIRED", "Service create requires a name", {
		why: "The name positional was empty or only whitespace.",
		nextActions: [adviceAction("Pass a name, as in service create my-service."), runCommandAction("List services", "service list")]
	});
}
function projectNotFoundError(projectId) {
	return new CliStructuredError("PROJECT.NOT_FOUND", "Project not found", {
		why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
		nextActions: [runCommandAction("Inspect the directory binding", "project show"), runCommandAction("Link a project", "project link <id-or-name>")]
	});
}
function deployFailedError(summary, cause, nextActions) {
	return new CliStructuredError("SERVICE.DEPLOY_FAILED", summary, {
		why: cause instanceof Error ? cause.message : String(cause),
		nextActions,
		cause
	});
}
function noVersionsError(summary, why, serviceName) {
	return new CliStructuredError("SERVICE.NO_VERSIONS", summary, {
		why,
		nextActions: [runCommandAction("Inspect the service", `service show ${serviceName}`)]
	});
}
function versionNotFoundError(deploymentId) {
	return new CliStructuredError("SERVICE.VERSION_NOT_FOUND", `Version "${deploymentId}" not found`, {
		why: "The requested service version does not exist or is no longer available.",
		nextActions: [runCommandAction("Choose an available version id", "service version list <service>")]
	});
}
/** `--tail` and `--from-start` ask for opposite ends of the log, so a
*  run naming both has no answer to give. Refused before any work. */
function logsRangeConflictError() {
	return new CliStructuredError("SERVICE.LOGS_RANGE_CONFLICT", "Choose one end of the log to read from", {
		why: "--tail and --from-start are mutually exclusive: one reads the last lines, the other reads from the beginning.",
		nextActions: [adviceAction("Pass --tail <n> for the last n lines, or --from-start for the whole log.")]
	});
}
/** The version exists but names no owning service, so there is
*  nothing to report or act on it as. */
function versionDetachedError(deploymentId) {
	return new CliStructuredError("SERVICE.VERSION_DETACHED", `Version "${deploymentId}" has no owning service`, {
		why: "The Management API returned the version without a service, so there is nothing to report or act on it as.",
		nextActions: [runCommandAction("Show the version", `service version show ${deploymentId}`)]
	});
}
function versionNotFoundForServiceError(deploymentId, serviceName) {
	return new CliStructuredError("SERVICE.VERSION_NOT_FOUND", `Version "${deploymentId}" not found for service "${serviceName}"`, {
		why: "The requested version does not belong to the resolved service or is no longer available.",
		nextActions: [runCommandAction("Choose an available version id", `service version list ${serviceName}`)]
	});
}
/** Every command that acts on an existing service needs its target
*  named explicitly; nothing is inferred, remembered, or prompted for. */
function serviceTargetRequiredError(commandName) {
	return new CliStructuredError("SERVICE.TARGET_REQUIRED", `Command "${commandName}" requires a service`, {
		why: "Service commands act only on an explicitly named service, and this run named none.",
		nextActions: [adviceAction("Pass the service id or name as the first argument."), runCommandAction("List services", "service list")]
	});
}
function noPreviousVersionError(serviceName) {
	return new CliStructuredError("SERVICE.NO_PREVIOUS_VERSION", "No previous version available for rollback", {
		why: "The service does not have an earlier version to switch back to.",
		nextActions: [adviceAction("Deploy a second version first, or pass --to <version-id> for a specific earlier version."), runCommandAction("List versions", `service version list ${serviceName}`)]
	});
}
/** Rolling back without `--to` needs the live deployment: the default
*  target is defined relative to it. */
function liveVersionUnknownError(serviceName) {
	return new CliStructuredError("SERVICE.LIVE_VERSION_UNKNOWN", "Cannot determine which version is currently live", {
		why: "The service record does not name a live version, so the version to roll back to cannot be chosen without guessing what production is serving.",
		nextActions: [runCommandAction("Roll back to a named version", `service version rollback ${serviceName} --to <version>`), runCommandAction("List versions", `service version list ${serviceName}`)]
	});
}
function deleteFailedError(summary, cause, serviceName) {
	return new CliStructuredError("SERVICE.DELETE_FAILED", summary, {
		why: cause instanceof Error ? cause.message : String(cause),
		nextActions: [runCommandAction("Inspect the service", `service show ${serviceName}`), runCommandAction("List versions", `service version list ${serviceName}`)],
		cause
	});
}
/** A blank `--branch` names no branch and must never fall through to
*  the branch the command targets when the flag is omitted. */
function branchValueEmptyError() {
	return new CliStructuredError("SERVICE.BRANCH_INVALID", "The --branch value cannot be empty", {
		why: "The command scopes its work to the given branch; an empty --branch names none, and omitting the flag targets the default branch instead.",
		nextActions: [adviceAction("Pass a non-empty branch name, or omit --branch to target the default branch.")]
	});
}
function liveUrlUnavailableError(serviceName) {
	return new CliStructuredError("SERVICE.FEATURE_UNAVAILABLE", "Live URL is not available for this service", {
		why: "Versions exist, but the provider does not expose a stable live service URL for this service yet.",
		nextActions: [runCommandAction("Inspect the service state", `service show ${serviceName}`)]
	});
}
function branchNotDeployableError(branchName) {
	return new CliStructuredError("SERVICE.BRANCH_NOT_DEPLOYABLE", "Custom domains require the production branch", {
		why: `Custom domains on preview branch "${branchName}" are not supported in Public Beta.`,
		nextActions: [adviceAction("Use --branch production, or attach the domain after promoting/deploying to the production branch."), runCommandAction("Add on production", "service domain add <hostname> --service <name> --branch production")]
	});
}
function domainHostnameInvalidError(hostname, why) {
	return new CliStructuredError("SERVICE.DOMAIN_HOSTNAME_INVALID", `Invalid custom domain "${hostname}"`, {
		why: why ?? "Custom domains must be valid hostnames without protocol, path, wildcard, or port.",
		nextActions: [adviceAction("Pass a hostname like shop.acme.com."), runCommandAction("Add a domain", "service domain add shop.acme.com --service <name>")]
	});
}
function domainNotFoundError(hostname) {
	return new CliStructuredError("SERVICE.DOMAIN_NOT_FOUND", `Custom domain "${hostname}" not found`, {
		why: "The hostname is not attached to the service.",
		nextActions: [adviceAction("Check the hostname and the service, or add the domain first."), runCommandAction("Add the domain", `service domain add ${hostname} --service <name>`)]
	});
}
function formatDomainFailureWhy(domain) {
	if (!domain.failureReason) return "The platform reported a terminal failed state for this custom domain.";
	if (!domain.failureCategory) return domain.failureReason;
	return `${domain.failureCategory}: ${domain.failureReason}`;
}
function domainVerificationFailedError(hostname, domain) {
	const why = formatDomainFailureWhy(domain);
	const guidance = formatDomainFailureFix(domain);
	return new CliStructuredError("SERVICE.DOMAIN_VERIFICATION_FAILED", `Custom domain "${hostname}" failed verification`, {
		why,
		nextActions: [
			...guidance ? [adviceAction(guidance)] : [],
			runCommandAction("Show the domain", `service domain show ${hostname} --service <name>`),
			runCommandAction("Retry verification", `service domain retry ${hostname} --service <name>`)
		]
	});
}
function domainVerificationTimeoutError(hostname, lastStatus) {
	return new CliStructuredError("SERVICE.DOMAIN_VERIFICATION_TIMEOUT", `Timed out waiting for "${hostname}" to become active`, {
		why: `The domain is still "${lastStatus}".`,
		nextActions: [runCommandAction("Show the domain", `service domain show ${hostname} --service <name>`), adviceAction("Retry wait with a longer --timeout.")]
	});
}
function timeoutInvalidError(value) {
	return new CliStructuredError("SERVICE.TIMEOUT_INVALID", `Invalid timeout "${value}"`, {
		why: "Timeout must be a duration such as 0, 30s, 15m, or 1h.",
		nextActions: [runCommandAction("Wait with a valid timeout", "service domain wait shop.acme.com --service <name> --timeout 15m")]
	});
}
function debugMeta(error) {
	if (error instanceof DomainApiError) return {
		status: error.status,
		apiCode: error.code,
		hint: error.hint
	};
	return {};
}
function domainCommandError(command, error, hostname) {
	if (error instanceof DomainApiError) {
		const known = domainApiFailure(command, error, hostname);
		if (known) return known;
	}
	return new CliStructuredError("SERVICE.DEPLOY_FAILED", `Custom domain ${command} failed`, {
		why: error instanceof Error ? error.message : String(error),
		meta: debugMeta(error),
		nextActions: [runCommandAction("Show the domain", `service domain show ${hostname} --service <name>`)],
		cause: error
	});
}
function domainApiFailure(command, error, hostname) {
	if (command === "add") return domainAddFailure(error, hostname);
	if (error.status === 404) return domainNotFoundError(hostname);
	if (command === "retry" && error.status === 409) return domainRetryNotEligibleError(hostname, error);
	return null;
}
function domainAddFailure(error, hostname) {
	if ((error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
	if (error.status === 400) return domainHostnameRejectedError(hostname, error);
	if (error.status === 429 || isDomainQuotaError(error)) return domainQuotaExceededError(error);
	if (error.status === 409) return domainAlreadyRegisteredError(hostname, error);
	if (error.status === 422) return domainRequiresDeploymentError(hostname, error);
	return null;
}
function domainHostnameRejectedError(hostname, error) {
	return new CliStructuredError("SERVICE.DOMAIN_HOSTNAME_INVALID", `Invalid custom domain "${hostname}"`, {
		why: error.message,
		meta: debugMeta(error),
		nextActions: [adviceAction("Pass a valid hostname like shop.acme.com and make sure DNS can be verified."), runCommandAction("Add a domain", "service domain add shop.acme.com --service <name>")]
	});
}
function domainQuotaExceededError(error) {
	return new CliStructuredError("SERVICE.DOMAIN_QUOTA_EXCEEDED", "Custom domain quota exceeded", {
		why: error.message,
		meta: debugMeta(error),
		nextActions: [adviceAction("Delete an existing custom domain before adding another one."), runCommandAction("Delete a domain", "service domain delete <hostname> --service <name>")]
	});
}
function domainAlreadyRegisteredError(hostname, error) {
	return new CliStructuredError("SERVICE.DOMAIN_ALREADY_REGISTERED", `Custom domain "${hostname}" is already registered`, {
		why: error.hint ?? error.message,
		meta: debugMeta(error),
		nextActions: [adviceAction("Select the service that owns this hostname and delete it there, or contact Prisma support if you cannot access it.")]
	});
}
function domainRequiresDeploymentError(hostname, error) {
	return new CliStructuredError("SERVICE.NO_VERSIONS", "Custom domain requires a live production version", {
		why: "The selected production service does not have a promoted version that can receive a custom domain.",
		meta: debugMeta(error),
		nextActions: [adviceAction("Promote a version on the service's production branch, then add the domain again."), runCommandAction("Add the domain", `service domain add ${hostname} --service <name>`)]
	});
}
function domainRetryNotEligibleError(hostname, error) {
	return new CliStructuredError("SERVICE.DOMAIN_RETRY_NOT_ELIGIBLE", `Custom domain "${hostname}" is not eligible for retry`, {
		why: error.message,
		meta: debugMeta(error),
		nextActions: [adviceAction("Wait for the current verification or TLS step to finish, then rerun retry if the domain fails."), runCommandAction("Show the domain", `service domain show ${hostname} --service <name>`)]
	});
}
function isDomainQuotaError(error) {
	if (error.status !== 409) return false;
	const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
	return text.includes("quota") || text.includes("maximum") || text.includes("limit");
}
function isDomainDnsError(error) {
	const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
	return text.includes("dns is not configured") || text.includes("dns verification failed") || text.includes("no cname") || text.includes("cname record") || text.includes("no a/aaaa") || CNAME_HINT.test(text);
}
function domainDnsNotConfiguredError(hostname, error) {
	const target = extractDomainDnsTarget(error);
	const record = target ? `CNAME ${hostname} -> ${target}` : null;
	return new CliStructuredError("SERVICE.DOMAIN_DNS_NOT_CONFIGURED", `DNS is not configured for "${hostname}"`, {
		why: error.hint ?? error.message,
		meta: {
			...debugMeta(error),
			...record ? { dnsRecord: record } : {}
		},
		nextActions: record ? [adviceAction(`Add ${record} at your DNS provider, then rerun the domain command.`), runCommandAction("Add the domain", `service domain add ${hostname} --service <name>`)] : [adviceAction("The platform did not return the required DNS target. Re-run with --log-level verbose for the underlying API response details.")]
	});
}
function extractDomainDnsTarget(error) {
	const text = `${error.hint ?? ""} ${error.message}`;
	return PRISMA_BUILD_HOST.exec(text)?.[1]?.toLowerCase() ?? null;
}
//#endregion
//#region src/commands/service/presentation.ts
function fields(rows) {
	return {
		kind: "fields",
		rows
	};
}
/** The heading a command that only reports opens with. */
function title(text) {
	return {
		kind: "summary",
		status: "info",
		text
	};
}
/** The line a command that changed something ends on: what it did, in
*  the past tense, marked as a success. */
function completed(text) {
	return {
		kind: "summary",
		status: "ok",
		text
	};
}
function formatRecentDeployments(deployments) {
	if (deployments.length === 0) return "none";
	return deployments.map((deployment) => `${deployment.id} (${deployment.status}${deployment.live ? ", live" : ""})`).join(", ");
}
function domainTargetRows(target) {
	return [
		{
			label: "workspace",
			value: target.workspace.name
		},
		{
			label: "project",
			value: target.project.name
		},
		{
			label: "branch",
			value: target.branch.name
		},
		{
			label: "service",
			value: target.service.name
		}
	];
}
function domainDnsRows(domain) {
	return domain.dnsRecords.map((record) => ({
		label: `dns ${record.type}`,
		value: `${record.name} -> ${record.value}${record.ttl === null ? "" : ` (ttl ${record.ttl})`}`
	}));
}
function domainFailureRows(domain) {
	if (!domain.failureReason) return [];
	return [{
		label: "failure",
		value: domain.failureCategory ? `${domain.failureCategory}: ${domain.failureReason}` : domain.failureReason
	}];
}
function listPresentations(result) {
	return {
		json: () => result,
		human: () => [
			title("Listing services for the selected project."),
			fields([{
				label: "project",
				value: result.projectName
			}, {
				label: "branch",
				value: result.branch
			}]),
			...result.services.length === 0 ? [{
				kind: "summary",
				status: "info",
				text: "No services found."
			}] : [{
				kind: "table",
				columns: [
					"name",
					"id",
					"region",
					"live url"
				],
				rows: result.services.map((service) => [
					service.name,
					service.id,
					service.region ?? "",
					service.liveUrl ?? "not deployed"
				])
			}]
		],
		/** The machine rows leave an absent region and an undeployed service
		*  empty, rather than repeating the words the human table shows. */
		stdout: () => result.services.map((service) => [
			service.name,
			service.id,
			service.region ?? "",
			service.liveUrl ?? ""
		].join("	")),
		next: () => {
			const first = result.services[0];
			return first ? [runCommandAction("Show a service", `service show ${first.name}`)] : [adviceAction("Create a service with service create <name>, choosing the name.")];
		}
	};
}
function createPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [result.existing ? title(`${result.service.name} already exists on ${result.branch}; showing it.`) : completed(`Created ${result.service.name} on ${result.branch}.`), fields([
			{
				label: "project",
				value: result.projectId
			},
			{
				label: "branch",
				value: result.branch
			},
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "id",
				value: result.service.id
			},
			{
				label: "region",
				value: result.service.region ?? ""
			},
			{
				label: "live url",
				value: result.service.liveUrl ?? "not deployed"
			}
		])],
		next: () => [runCommandAction("Deploy to the service", "deploy"), runCommandAction("Show the service", `service show ${result.service.name}`)]
	};
}
function showPresentations(result) {
	const next = [];
	if (result.liveUrl) next.push(runCommandAction("Open the live URL", `service open ${result.service.name}`));
	const inspectable = result.liveVersion ?? result.recentVersions[0];
	if (inspectable) next.push(runCommandAction("Show the version", `service version show ${inspectable.id}`));
	return {
		stdout: () => [],
		json: () => result,
		human: () => [title(`Showing the state of service ${result.service.name}.`), fields([
			{
				label: "project",
				value: result.projectId
			},
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "live version",
				value: result.liveVersion?.id ?? ""
			},
			{
				label: "live url",
				value: result.liveUrl ?? "unavailable"
			},
			{
				label: "recent versions",
				value: formatRecentDeployments(result.recentVersions)
			}
		])],
		next: () => next
	};
}
function versionListPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [
			title(`Listing versions of service ${result.service.name}.`),
			fields([{
				label: "project",
				value: result.projectId
			}, {
				label: "service",
				value: result.service.name
			}]),
			result.versions.length === 0 ? {
				kind: "summary",
				status: "info",
				text: "No versions found."
			} : {
				kind: "table",
				columns: [
					"version",
					"status",
					"created",
					"live"
				],
				rows: result.versions.map((deployment) => [
					deployment.id,
					deployment.status,
					deployment.createdAt,
					deployment.live ? "yes" : ""
				])
			}
		],
		next: () => {
			const newest = result.versions[0];
			return newest ? [runCommandAction("Show the newest version", `service version show ${newest.id}`)] : [];
		}
	};
}
function versionShowPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [title("Showing service version details."), fields([
			...result.service ? [{
				label: "service",
				value: result.service.name
			}] : [],
			{
				label: "version",
				value: result.version.id
			},
			{
				label: "status",
				value: result.version.status
			},
			...result.version.url ? [{
				label: "url",
				value: result.version.url
			}] : [],
			...result.version.live === null ? [] : [{
				label: "live",
				value: result.version.live ? "yes" : "no"
			}],
			{
				label: "created",
				value: result.version.createdAt
			}
		])]
	};
}
function openPresentations(result, liveDeploymentId) {
	return {
		json: () => result,
		human: () => [result.opened ? completed(`Opened the live URL for service ${result.service.name}.`) : title(`Resolved the live URL for service ${result.service.name}.`), fields([
			{
				label: "project",
				value: result.projectId
			},
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "url",
				value: result.url
			},
			{
				label: "opened",
				value: result.opened ? "yes" : "no"
			}
		])],
		stdout: () => [result.url],
		next: () => [runCommandAction("Inspect the service", `service show ${result.service.name}`), runCommandAction("Show the live version", `service version show ${liveDeploymentId}`)]
	};
}
function versionNextActions(deploymentId, serviceName) {
	return [runCommandAction("List versions", `service version list ${serviceName}`), runCommandAction("Show the version", `service version show ${deploymentId}`)];
}
function promotePresentations(result, alreadyLive) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(alreadyLive ? `${result.version.id} was already live for ${result.service.name}.` : `Promoted ${result.version.id} to production.`), fields([
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "version",
				value: result.version.id
			},
			{
				label: "status",
				value: result.version.status
			},
			...result.version.url ? [{
				label: "url",
				value: result.version.url
			}] : []
		])],
		next: () => versionNextActions(result.version.id, result.service.name)
	};
}
function rollbackPresentations(result, alreadyLive) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(alreadyLive ? `${result.version.id} was already live for ${result.service.name}.` : `Rolled ${result.service.name} back to ${result.version.id}.`), fields([
			{
				label: "project",
				value: result.projectId
			},
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "version",
				value: result.version.id
			},
			{
				label: "status",
				value: result.version.status
			},
			{
				label: "previous live version",
				value: result.previousLiveVersionId ?? "unknown"
			},
			...result.version.url ? [{
				label: "url",
				value: result.version.url
			}] : []
		])],
		next: () => versionNextActions(result.version.id, result.service.name)
	};
}
function versionStartPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(result.alreadyInState ? `${result.version.id} was already running.` : `Started ${result.version.id}.`), fields([
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "version",
				value: result.version.id
			},
			{
				label: "status",
				value: result.version.status
			},
			...result.version.url ? [{
				label: "url",
				value: result.version.url
			}] : []
		])],
		next: () => versionNextActions(result.version.id, result.service.name)
	};
}
function versionStopPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(result.alreadyInState ? `${result.version.id} was already stopped.` : `Stopped ${result.version.id}.`), fields([
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "version",
				value: result.version.id
			},
			{
				label: "status",
				value: result.version.status
			}
		])],
		next: () => versionNextActions(result.version.id, result.service.name)
	};
}
function versionDeletePresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(`Deleted ${result.versionId} from ${result.service.name}.`), fields([
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "version",
				value: result.versionId
			},
			{
				label: "deleted",
				value: "yes"
			}
		])],
		next: () => [runCommandAction("List versions", `service version list ${result.service.name}`)]
	};
}
function deletePresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(`Deleted ${result.service.name} and every version it owned.`), fields([
			{
				label: "project",
				value: result.projectId
			},
			{
				label: "service",
				value: result.service.name
			},
			{
				label: "deleted",
				value: "yes"
			}
		])],
		next: () => [runCommandAction("List remaining services", "service list")]
	};
}
function domainAddPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [result.existing ? title(`Showing the existing custom domain on ${result.service.name}.`) : completed(`Added ${result.domain.hostname} to ${result.service.name}.`), fields([
			...domainTargetRows(result),
			{
				label: "hostname",
				value: result.domain.hostname
			},
			{
				label: "status",
				value: result.domain.status
			},
			...domainDnsRows(result.domain)
		])],
		next: () => [runCommandAction("Wait for activation", `service domain wait ${result.domain.hostname}`), runCommandAction("Show the domain", `service domain show ${result.domain.hostname}`)]
	};
}
function domainShowPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [title("Showing custom domain status."), fields([
			...domainTargetRows(result),
			{
				label: "hostname",
				value: result.domain.hostname
			},
			{
				label: "status",
				value: result.domain.status
			},
			...domainFailureRows(result.domain),
			{
				label: "cert expires",
				value: result.domain.certExpiresAt ?? "not yet issued"
			},
			{
				label: "created",
				value: result.domain.createdAt
			},
			...domainDnsRows(result.domain)
		])],
		next: () => {
			if (result.domain.status === "active") return [];
			if (result.domain.status === "failed") return [runCommandAction("Retry verification", `service domain retry ${result.domain.hostname}`)];
			return [runCommandAction("Wait for activation", `service domain wait ${result.domain.hostname}`)];
		}
	};
}
function domainDeletePresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [completed(`Deleted ${result.hostname} from ${result.service.name}.`), fields([
			...domainTargetRows(result),
			{
				label: "hostname",
				value: result.hostname
			},
			{
				label: "deleted",
				value: "yes"
			}
		])]
	};
}
function domainRetryPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		human: () => [completed(`Retried verification for ${result.domain.hostname}.`), fields([
			...domainTargetRows(result),
			{
				label: "hostname",
				value: result.domain.hostname
			},
			{
				label: "status",
				value: result.domain.status
			},
			...domainFailureRows(result.domain),
			...domainDnsRows(result.domain)
		])],
		next: () => [runCommandAction("Wait for activation", `service domain wait ${result.domain.hostname}`)]
	};
}
function domainWaitPresentations(result) {
	return {
		stdout: () => [],
		json: () => result,
		next: () => [],
		human: () => [completed(`${result.hostname} is live at ${result.liveUrl}`), fields([
			...domainTargetRows(result),
			{
				label: "hostname",
				value: result.hostname
			},
			{
				label: "status",
				value: result.status
			}
		])]
	};
}
//#endregion
//#region src/lib/app/read-branch.ts
/**
* Resolves the branch an app management command should read from, without ever
* creating one. Returns the branch whose `gitName` matches `branchName`, else
* the project's default branch, else null when the project has no branches.
*/
async function resolveReadBranch(client, options) {
	const branches = [];
	let cursor;
	do {
		const result = await client.GET("/v1/projects/{projectId}/branches", {
			params: {
				path: { projectId: options.projectId },
				query: { cursor }
			},
			signal: options.signal
		});
		if (result.error || !result.data) throw new Error(`Failed to list branches for project ${options.projectId}: ${JSON.stringify(result.error)}`);
		branches.push(...result.data.data);
		cursor = result.data.pagination.hasMore ? result.data.pagination.nextCursor ?? void 0 : void 0;
	} while (cursor);
	const chosen = branches.find((branch) => branch.gitName === options.branchName) ?? branches.find((branch) => branch.isDefault) ?? null;
	return chosen ? {
		id: chosen.id,
		name: chosen.gitName,
		kind: chosen.role
	} : null;
}
//#endregion
//#region src/commands/service/target.ts
/** A hostname's optional root dot, and one DNS label. */
const TRAILING_DOT = /\.$/;
const DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
/** The workspace the run is acting as, from the credential the engine
*  is authenticating with. A workspace with no name shows its id
*  instead: the id is the only other identifier the user can act on,
*  and every display of this name needs a non-empty string. A
*  credential that names no workspace at all — an environment token
*  whose claims carry none — cannot scope these commands, so it is the
*  same failure as having no credential. */
async function requireWorkspace(ctx) {
	const credential = await ctx.activeCredential();
	if (!credential?.workspaceId) throw workspaceRequiredError();
	return {
		id: credential.workspaceId,
		name: credential.workspaceName ?? credential.workspaceId
	};
}
/** What project resolution reads: where the command was invoked, and
*  the run's abort signal. */
function resolutionContext(ctx) {
	return { runtime: {
		cwd: ctx.cwd,
		signal: ctx.signal
	} };
}
function toBranchKind(name) {
	return name === "production" || name === "main" ? "production" : "preview";
}
/**
* The same listing `controllers/project.ts#listRealWorkspaceProjects`
* performs, on ctx.api — duplicated here so this CLI does not drag
* the legacy controller import graph (child-process git adapters).
*
* No workspace filter, and no workspace parameter that could invite one
* back: the credential is issued for one workspace and the API answers
* within it. The filter this function used to carry compared the
* credential's bare workspace id against the API's `wksp_`-prefixed one
* and so discarded every project, every time — which made every service
* command report the pinned project as missing. #144 removed it from
* the legacy listing this mirrors; the copy here was written from the
* version that still had it.
*
* A refused request is raised, not read as an empty workspace. Without
* that, a 401, 403 or 500 becomes "no projects", the caller finds the
* pinned project missing, and the user is told their local binding is
* stale — sent to re-link a project that was never the problem. That is
* the same wrong recovery path the missing filter produced.
*/
async function listWorkspaceProjects(ctx) {
	const { data, error, response } = await ctx.api.GET("/v1/projects", { signal: ctx.signal });
	if (error || !data) throw projectApiError("Failed to list projects", response, error);
	return sortProjects((data.data ?? []).map((project) => ({
		id: project.id,
		name: project.name,
		..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
		..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
		slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
		workspace: {
			id: project.workspace.id,
			name: project.workspace.name
		}
	})));
}
/** A blank `--branch` names no branch and must never fall through to
*  the default-branch behavior of omitting the flag. */
function requireBranchFlagValue(branchName) {
	if (branchName !== void 0 && branchName.trim() === "") throw branchValueEmptyError();
}
async function resolveServiceProjectContext(ctx, explicitProject, options) {
	requireBranchFlagValue(options.branchName);
	const workspace = await requireWorkspace(ctx);
	const projects = await listWorkspaceProjects(ctx);
	const resolvedResult = await resolveProjectTarget({
		context: resolutionContext(ctx),
		workspace,
		...explicitProject !== void 0 ? { explicitProject } : {},
		listProjects: () => Promise.resolve(projects),
		commandName: options.commandName
	});
	if (resolvedResult.isErr()) throw projectResolutionErrorToStructured(resolvedResult.error);
	const resolved = resolvedResult.value;
	const requested = options.branchName ? {
		name: options.branchName,
		explicit: true
	} : {
		name: "main",
		explicit: false
	};
	const remoteBranch = requested.explicit ? null : await resolveReadBranch(ctx.api, {
		projectId: resolved.project.id,
		branchName: requested.name,
		signal: ctx.signal
	});
	return {
		workspace: resolved.workspace,
		project: resolved.project,
		resolution: resolved.resolution,
		branch: remoteBranch ?? {
			id: null,
			name: requested.name,
			kind: toBranchKind(requested.name)
		}
	};
}
function serviceProvider(ctx) {
	return createAppProvider(ctx.api);
}
function sortServices(services) {
	return services.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
}
function isMissingProjectError(error) {
	return error instanceof Error && error.message === "Resource Not Found";
}
async function listServices(ctx, provider, projectId, branchName) {
	return provider.listApps(projectId, {
		...branchName !== void 0 ? { branchName } : {},
		signal: ctx.signal
	}).then(sortServices).catch((error) => {
		if (isMissingProjectError(error)) throw projectNotFoundError(projectId);
		throw deployFailedError("Failed to list services", error, [runCommandAction("Inspect the project", "project show")]);
	});
}
/** The service argument, required: service commands never infer,
*  remember, or prompt for a target. */
function requireServiceArgument(serviceRef, commandName) {
	if (!serviceRef) throw serviceTargetRequiredError(commandName);
	return serviceRef;
}
/** Matches the service argument against the branch's services: the
*  stable platform id is primary, the name is the fallback. An id
*  match always wins, so a service named like another service's id
*  cannot shadow it. */
function matchRequestedService(serviceRef, services, projectId) {
	const matched = services.find((service) => service.id === serviceRef) ?? services.find((service) => service.name === serviceRef);
	if (!matched) throw serviceSelectionInvalidError(serviceRef, projectId);
	return matched;
}
/** Resolve a service version by its globally-unique id. The id alone names
*  the subject — no service, project, or branch parameter is consulted,
*  the same way `service version show` resolves it. */
async function resolveVersionSubject(ctx, deploymentId) {
	const provider = serviceProvider(ctx);
	const shown = await provider.showDeployment(deploymentId, { signal: ctx.signal }).catch((error) => {
		throw deployFailedError("Failed to show version", error, []);
	});
	if (!shown) throw versionNotFoundError(deploymentId);
	if (!shown.app) throw versionDetachedError(deploymentId);
	return {
		provider,
		service: shown.app,
		version: shown.deployment
	};
}
/** The live deployment is the one the service record names as its latest
*  deployment. Nothing else decides it — local CLI state never does. */
function resolveCurrentLiveVersionId(service, deployments) {
	if (service.liveDeploymentId && deployments.some((deployment) => deployment.id === service.liveDeploymentId)) return service.liveDeploymentId;
	return null;
}
function applyLiveVersionHint(deployments, currentLiveDeploymentId) {
	if (!currentLiveDeploymentId) return deployments.map((deployment) => ({
		...deployment,
		live: deployment.live ?? null
	}));
	return deployments.map((deployment) => ({
		...deployment,
		live: deployment.id === currentLiveDeploymentId
	}));
}
function sortVersionsNewestFirst(deployments) {
	return deployments.slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
}
function toServiceSummary(service) {
	return {
		id: service.id,
		name: service.name
	};
}
/** A service record as the listing and create presenters report it. A
*  service that names no live deployment has no URL to show: the
*  endpoint domain it already carries does not resolve until the first
*  promote. */
function toServiceListEntry(service) {
	return {
		id: service.id,
		name: service.name,
		region: service.region,
		liveVersionId: service.liveDeploymentId,
		liveUrl: service.liveDeploymentId ? service.liveUrl : null
	};
}
function toServiceDomainSummary(domain) {
	return {
		id: domain.id,
		type: domain.type,
		url: domain.url,
		hostname: domain.hostname,
		serviceId: domain.appId,
		status: domain.status,
		foundryStatus: domain.foundryStatus,
		failureReason: domain.failureReason,
		failureCategory: domain.failureCategory,
		certExpiresAt: domain.certExpiresAt,
		createdAt: domain.createdAt,
		updatedAt: domain.updatedAt,
		dnsRecords: domain.dnsRecords.map((record) => ({
			type: record.type,
			name: record.name,
			value: record.value,
			ttl: record.ttl
		}))
	};
}
function normalizeDomainHostname(hostname) {
	const normalized = hostname.trim().replace(TRAILING_DOT, "").toLowerCase();
	if (!isValidDomainHostname(normalized)) throw domainHostnameInvalidError(hostname);
	return normalized;
}
function isValidDomainHostname(hostname) {
	if (hostname.length < 1 || hostname.length > 253) return false;
	if (hostname.includes("://") || hostname.includes("/") || hostname.includes(":") || hostname.startsWith("*.")) return false;
	const labels = hostname.split(".");
	if (labels.length < 2) return false;
	return labels.every((label) => DNS_LABEL.test(label));
}
function sameDomainHostname(left, right) {
	return left.trim().replace(TRAILING_DOT, "").toLowerCase() === right.trim().replace(TRAILING_DOT, "").toLowerCase();
}
async function resolveDomainByHostname(provider, serviceId, hostname, command, signal) {
	const matched = (await provider.listDomains(serviceId, { signal }).catch((error) => {
		throw domainCommandError(command, error, hostname);
	})).find((domain) => sameDomainHostname(domain.hostname, hostname));
	if (matched) return matched;
	throw domainNotFoundError(hostname);
}
/** Project + branch resolution, before the service match. */
async function resolveServiceProjectState(ctx, options) {
	const provider = serviceProvider(ctx);
	const target = await resolveServiceProjectContext(ctx, options.projectRef, {
		commandName: options.commandName,
		...options.branchName !== void 0 ? { branchName: options.branchName } : {}
	});
	return {
		provider,
		target,
		projectId: target.project.id
	};
}
/** The shared read flow for every command that acts on an existing
*  service: project + branch resolution, service listing, and the
*  parameter-only service match. */
async function resolveServiceReadState(ctx, options) {
	const requested = requireServiceArgument(options.serviceName, options.commandName);
	const state = await resolveServiceProjectState(ctx, options);
	const service = matchRequestedService(requested, await listServices(ctx, state.provider, state.projectId, state.target.branch.name), state.projectId);
	return {
		...state,
		service
	};
}
async function resolveServiceDomainTarget(ctx, options) {
	requireBranchFlagValue(options.branchName);
	const branchName = options.branchName?.trim() ?? "production";
	if (toBranchKind(branchName) !== "production") throw branchNotDeployableError(branchName);
	const requested = requireServiceArgument(options.serviceName, options.commandName);
	const provider = serviceProvider(ctx);
	const target = await resolveServiceProjectContext(ctx, options.projectRef, {
		commandName: options.commandName,
		branchName
	});
	const projectId = target.project.id;
	const service = matchRequestedService(requested, await listServices(ctx, provider, projectId, target.branch.name), projectId);
	return {
		provider,
		service,
		resultTarget: {
			workspace: target.workspace,
			project: target.project,
			branch: {
				name: target.branch.name,
				kind: target.branch.kind
			},
			service: toServiceSummary(service)
		}
	};
}
//#endregion
//#region src/commands/service/create.ts
const serviceCreateCommand = defineCommand({
	help: {
		summary: "Create a service in a project",
		examples: ["service create my-service", "service create my-service --region us-east-1 --branch main"]
	},
	args: {
		positionals: { name: positional.string({
			brief: "Service name",
			placeholder: "name"
		}) },
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			region: flag.string({
				brief: "Prisma Compute region id",
				placeholder: "region"
			}),
			branch: flag.string({
				brief: "Branch name",
				placeholder: "branch"
			})
		}
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const name = args.positionals.name.trim();
		if (!name) throw serviceNameRequiredError();
		const target = await resolveServiceProjectContext(ctx, args.flags.project, {
			commandName: "service create",
			...args.flags.branch !== void 0 ? { branchName: args.flags.branch } : {}
		});
		const created = await serviceProvider(ctx).createApp({
			projectId: target.project.id,
			branchName: target.branch.name,
			name,
			...args.flags.region !== void 0 ? { region: args.flags.region } : {},
			signal: ctx.signal
		}).catch((error) => {
			throw deployFailedError("Failed to create service", error, [runCommandAction("List services", "service list")]);
		});
		const result = {
			projectId: target.project.id,
			branch: target.branch.name,
			service: toServiceListEntry(created.service),
			existing: created.existing
		};
		return ok(ctx.present({ data: result }, createPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/release.ts
function requireVersionForService(deployments, deploymentId, serviceName) {
	const deployment = deployments.find((candidate) => candidate.id === deploymentId);
	if (!deployment) throw versionNotFoundForServiceError(deploymentId, serviceName);
	return deployment;
}
/** The rollback default: the newest deployment that is not the live
*  one. With nothing naming the live deployment, every deployment
*  qualifies and the newest one — most likely the one already live — is
*  what a caller would get, so this refuses instead of guessing. */
function resolveRollbackTarget(deployments, currentLiveDeploymentId, serviceName) {
	if (deployments.length === 0) throw noPreviousVersionError(serviceName);
	if (currentLiveDeploymentId === null) throw liveVersionUnknownError(serviceName);
	const previousDeployment = deployments.find((deployment) => deployment.id !== currentLiveDeploymentId);
	if (!previousDeployment) throw noPreviousVersionError(serviceName);
	return previousDeployment;
}
/**
* Maps the compute SDK's promote callbacks onto engine events: one
* `status` event per reported transition of the target deployment, an
* `endpoint` event for the promoted URL, and a warning message when the
* SDK reports a promotion failure before rejecting.
*/
function promoteProgressReporter(ctx, deploymentId) {
	let previousStatus = null;
	const status = (next) => {
		if (previousStatus === next) return;
		ctx.report({
			kind: "status",
			subject: deploymentId,
			status: next,
			...previousStatus === null ? {} : { from: previousStatus }
		});
		previousStatus = next;
	};
	return {
		onDeploymentStarting: () => status("starting"),
		onDeploymentStartRequested: () => status("start-requested"),
		onStatusChange: (next) => status(next),
		onDeploymentRunning: () => status("running"),
		onPromoteStart: () => status("promoting"),
		onPromoted: (appEndpointDomain) => {
			status("promoted");
			if (appEndpointDomain) ctx.report({
				kind: "endpoint",
				name: "live",
				url: `https://${appEndpointDomain}`
			});
		},
		onPromoteFailed: (error) => {
			ctx.report({
				kind: "message",
				severity: "warn",
				text: `Promotion failed${error?.message ? `: ${error.message}` : "."}`
			});
		}
	};
}
/**
* Maps the compute SDK's app-teardown callbacks onto engine events: the
* SDK polls each deployment down internally, so the deployment stop and
* delete phases become `progress` counts and the terminal delete becomes
* a `status` event for the service.
*/
function destroyProgressReporter(ctx, serviceName) {
	let stopping = 0;
	let stopped = 0;
	let deleting = 0;
	let deleted = 0;
	return {
		onStoppingDeployments: (deploymentIds) => {
			stopping = deploymentIds.length;
			ctx.report({
				kind: "progress",
				step: "stop-versions",
				completed: 0,
				total: stopping
			});
		},
		onDeploymentStopped: () => {
			stopped += 1;
			ctx.report({
				kind: "progress",
				step: "stop-versions",
				completed: stopped,
				total: stopping
			});
		},
		onDeletingDeployments: (deploymentIds) => {
			deleting = deploymentIds.length;
			ctx.report({
				kind: "progress",
				step: "delete-versions",
				completed: 0,
				total: deleting
			});
		},
		onDeploymentDeleted: () => {
			deleted += 1;
			ctx.report({
				kind: "progress",
				step: "delete-versions",
				completed: deleted,
				total: deleting
			});
		},
		onAppDeleted: () => {
			ctx.report({
				kind: "status",
				subject: serviceName,
				status: "deleted",
				from: "deleting"
			});
		}
	};
}
//#endregion
//#region src/commands/service/delete.ts
const serviceDeleteCommand = defineCommand({
	help: {
		summary: "Delete the service from the resolved branch",
		examples: ["service delete my-service", "service delete my-service --confirm my-service"]
	},
	args: {
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the deletion is scoped to",
				placeholder: "name"
			})
		},
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) }
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const state = await resolveServiceReadState(ctx, {
			serviceName: args.positionals.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: "service delete"
		});
		if (!await ctx.prompt.consent(`Delete Service "${state.service.name}" and every version it owns?`, { token: state.service.name })) throw userCancelledError("Service deletion canceled");
		ctx.report({
			kind: "step-started",
			step: "delete"
		});
		ctx.report({
			kind: "status",
			subject: state.service.name,
			status: "deleting"
		});
		let deletedService;
		try {
			deletedService = await state.provider.removeApp(state.service.id, {
				signal: ctx.signal,
				progress: destroyProgressReporter(ctx, state.service.name)
			});
		} catch (error) {
			ctx.report({
				kind: "step-finished",
				step: "delete",
				outcome: "failed"
			});
			throw deleteFailedError("Failed to delete service", error, state.service.name);
		}
		ctx.report({
			kind: "step-finished",
			step: "delete",
			outcome: "ok"
		});
		const result = {
			projectId: state.projectId,
			service: toServiceSummary(deletedService),
			deleted: true
		};
		return ok(ctx.present({ data: result }, deletePresentations(result)));
	}
});
//#endregion
//#region src/commands/service/domain-shared.ts
/** The shared argument surface of every `service domain` command. */
function domainTargetArgs() {
	return {
		flags: {
			service: flag.string({
				brief: "Service id or name",
				placeholder: "name"
			}),
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch name",
				placeholder: "name"
			})
		},
		positionals: { hostname: positional.string({
			brief: "Custom domain hostname",
			placeholder: "hostname"
		}) }
	};
}
//#endregion
//#region src/commands/service/domain-add.ts
const serviceDomainAddCommand = defineCommand({
	help: {
		summary: "Register a custom domain on the service's production branch",
		examples: ["service domain add shop.acme.com --service my-service"]
	},
	args: domainTargetArgs(),
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const hostname = normalizeDomainHostname(args.positionals.hostname);
		const target = await resolveServiceDomainTarget(ctx, {
			serviceName: args.flags.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: `service domain add ${hostname}`
		});
		const added = await target.provider.addDomain({
			appId: target.service.id,
			hostname,
			signal: ctx.signal
		}).catch((error) => {
			throw domainCommandError("add", error, hostname);
		});
		const result = {
			...target.resultTarget,
			domain: toServiceDomainSummary(added.domain),
			existing: added.existing
		};
		return ok(ctx.present({ data: result }, domainAddPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/domain-delete.ts
const serviceDomainDeleteCommand = defineCommand({
	help: {
		summary: "Delete a custom domain from the service",
		examples: ["service domain delete shop.acme.com --service my-service", "service domain delete shop.acme.com --service my-service --confirm shop.acme.com"]
	},
	args: domainTargetArgs(),
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const hostname = normalizeDomainHostname(args.positionals.hostname);
		const target = await resolveServiceDomainTarget(ctx, {
			serviceName: args.flags.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: `service domain delete ${hostname}`
		});
		const domain = await resolveDomainByHostname(target.provider, target.service.id, hostname, "delete", ctx.signal);
		if (!await ctx.prompt.consent(`Delete ${hostname} from Service "${target.resultTarget.service.name}"?`, { token: hostname })) throw userCancelledError("Custom domain deletion canceled");
		await target.provider.removeDomain(domain.id, { signal: ctx.signal }).catch((error) => {
			throw domainCommandError("delete", error, hostname);
		});
		const result = {
			...target.resultTarget,
			hostname,
			deleted: true
		};
		return ok(ctx.present({ data: result }, domainDeletePresentations(result)));
	}
});
//#endregion
//#region src/commands/service/domain-retry.ts
const serviceDomainRetryCommand = defineCommand({
	help: {
		summary: "Retry custom domain DNS verification and TLS provisioning",
		examples: ["service domain retry shop.acme.com --service my-service"]
	},
	args: domainTargetArgs(),
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const hostname = normalizeDomainHostname(args.positionals.hostname);
		const target = await resolveServiceDomainTarget(ctx, {
			serviceName: args.flags.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: `service domain retry ${hostname}`
		});
		const domain = await resolveDomainByHostname(target.provider, target.service.id, hostname, "retry", ctx.signal);
		const retried = await target.provider.retryDomain(domain.id, { signal: ctx.signal }).catch((error) => {
			throw domainCommandError("retry", error, hostname);
		});
		const result = {
			...target.resultTarget,
			domain: toServiceDomainSummary(retried)
		};
		return ok(ctx.present({ data: result }, domainRetryPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/domain-show.ts
const serviceDomainShowCommand = defineCommand({
	help: {
		summary: "Show custom domain status and certificate details",
		examples: ["service domain show shop.acme.com --service my-service"]
	},
	args: domainTargetArgs(),
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const hostname = normalizeDomainHostname(args.positionals.hostname);
		const target = await resolveServiceDomainTarget(ctx, {
			serviceName: args.flags.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: `service domain show ${hostname}`
		});
		const domain = await resolveDomainByHostname(target.provider, target.service.id, hostname, "show", ctx.signal);
		const detail = await target.provider.showDomain(domain.id, { signal: ctx.signal }).catch((error) => {
			throw domainCommandError("show", error, hostname);
		});
		const result = {
			...target.resultTarget,
			domain: toServiceDomainSummary(detail)
		};
		return ok(ctx.present({ data: result }, domainShowPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/domain-wait.ts
const DEFAULT_TIMEOUT_MS = 900 * 1e3;
const DEFAULT_POLL_INTERVAL_MS$1 = 5e3;
const UNIT_MULTIPLIER_MS = {
	ms: 1,
	s: 1e3,
	m: 6e4,
	h: 36e5
};
function parseWaitTimeout(value) {
	if (!value) return DEFAULT_TIMEOUT_MS;
	const trimmed = value.trim().toLowerCase();
	if (trimmed === "0") return 0;
	const match = DURATION.exec(trimmed);
	if (!match) throw timeoutInvalidError(value);
	return Number.parseInt(match[1], 10) * (UNIT_MULTIPLIER_MS[match[2]] ?? 1);
}
function pollIntervalMs$1(ctx) {
	const raw = ctx.env.PRISMA_CLI_DOMAIN_WAIT_POLL_MS;
	if (!raw) return DEFAULT_POLL_INTERVAL_MS$1;
	const parsed = Number.parseInt(raw, 10);
	return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_POLL_INTERVAL_MS$1;
}
async function sleep$1(milliseconds, signal) {
	if (milliseconds <= 0) return;
	signal.throwIfAborted();
	await new Promise((resolve, reject) => {
		const onAbort = () => {
			clearTimeout(timeout);
			reject(signal.reason);
		};
		const timeout = setTimeout(() => {
			signal.removeEventListener("abort", onAbort);
			resolve();
		}, milliseconds);
		signal.addEventListener("abort", onAbort, { once: true });
	});
}
const DURATION = /^(\d+)(ms|s|m|h)$/;
const serviceDomainWaitCommand = defineCommand({
	help: {
		summary: "Wait until a custom domain is active or failed",
		examples: ["service domain wait shop.acme.com --service my-service", "service domain wait shop.acme.com --service my-service --timeout 30m"]
	},
	args: {
		flags: {
			...domainTargetArgs().flags,
			timeout: flag.string({
				brief: "Maximum time to wait",
				placeholder: "duration",
				default: "15m"
			})
		},
		positionals: domainTargetArgs().positionals
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const hostname = normalizeDomainHostname(args.positionals.hostname);
		const timeoutMs = parseWaitTimeout(args.flags.timeout);
		const target = await resolveServiceDomainTarget(ctx, {
			serviceName: args.flags.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: `service domain wait ${hostname}`
		});
		const domain = await resolveDomainByHostname(target.provider, target.service.id, hostname, "wait", ctx.signal);
		const start = Date.now();
		const deadline = start + timeoutMs;
		const interval = pollIntervalMs$1(ctx);
		let previousStatus = null;
		let current = domain;
		for (;;) {
			if (previousStatus !== current.status) ctx.report({
				kind: "status",
				subject: hostname,
				status: current.status,
				...previousStatus === null ? {} : { from: previousStatus },
				data: {
					domainId: current.id,
					elapsedMs: Date.now() - start
				}
			});
			previousStatus = current.status;
			if (current.status === "active") {
				const result = {
					...target.resultTarget,
					hostname,
					status: current.status,
					liveUrl: `https://${hostname}`
				};
				return ok(ctx.present({ data: result }, domainWaitPresentations(result)));
			}
			if (current.status === "failed") throw domainVerificationFailedError(hostname, current);
			if (timeoutMs === 0 || Date.now() >= deadline) throw domainVerificationTimeoutError(hostname, current.status);
			await sleep$1(Math.min(interval, Math.max(deadline - Date.now(), 0)), ctx.signal);
			current = await target.provider.showDomain(current.id, { signal: ctx.signal }).catch((error) => {
				throw domainCommandError("wait", error, hostname);
			});
		}
	}
});
//#endregion
//#region src/commands/service/list.ts
const serviceListCommand = defineCommand({
	help: {
		summary: "List the services in a project",
		examples: ["service list", "service list --project my-app --json"]
	},
	args: { flags: {
		project: flag.string({
			brief: "Project id or name",
			placeholder: "id-or-name"
		}),
		branch: flag.string({
			brief: "Branch name",
			placeholder: "branch"
		})
	} },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const target = await resolveServiceProjectContext(ctx, args.flags.project, {
			commandName: "service list",
			...args.flags.branch !== void 0 ? { branchName: args.flags.branch } : {}
		});
		const services = await listServices(ctx, serviceProvider(ctx), target.project.id, target.branch.name);
		const result = {
			projectId: target.project.id,
			projectName: target.project.name,
			branch: target.branch.name,
			services: services.map(toServiceListEntry)
		};
		return ok(ctx.present({ data: result }, listPresentations(result)));
	}
});
//#endregion
//#region src/lib/ndjson.ts
/**
* Reads a newline-delimited JSON body line by line.
*
* Shared by every command that reads an NDJSON log page, so the stream
* handling below is written and tested once. The two subtleties are the
* reason: a chunk boundary can fall inside a line, and a body can end
* without a trailing newline, so the last record arrives only if the
* leftover buffer is flushed at `done`.
*/
async function forEachNdjsonRecord(body, onRecord) {
	const reader = body.getReader();
	const decoder = new TextDecoder();
	let buffer = "";
	try {
		for (;;) {
			const { done, value } = await reader.read();
			if (value) buffer += decoder.decode(value, { stream: true });
			let newlineIndex = buffer.indexOf("\n");
			while (newlineIndex !== -1) {
				const line = buffer.slice(0, newlineIndex).trim();
				buffer = buffer.slice(newlineIndex + 1);
				if (line) onRecord(JSON.parse(line));
				newlineIndex = buffer.indexOf("\n");
			}
			if (done) {
				const tail = buffer.trim();
				if (tail) onRecord(JSON.parse(tail));
				return;
			}
		}
	} finally {
		await reader.cancel().catch(() => void 0);
		reader.releaseLock();
	}
}
//#endregion
//#region src/commands/service/logs.ts
const TRAILING_NEWLINE = /\n$/;
/** The endpoint's own default page size, restated so `--tail` and the
*  unflagged run send the same shape of request. */
const DEFAULT_TAIL = 100;
/** Contract: poll every 2s in --follow. Overridable so a test drives the
*  loop without waiting, the way `service domain wait` does. */
const DEFAULT_POLL_INTERVAL_MS = 2e3;
function pollIntervalMs(ctx) {
	const raw = ctx.env.PRISMA_CLI_SERVICE_LOGS_POLL_MS;
	if (!raw) return DEFAULT_POLL_INTERVAL_MS;
	const parsed = Number.parseInt(raw, 10);
	return Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_POLL_INTERVAL_MS;
}
async function sleep(milliseconds, signal) {
	if (milliseconds <= 0) {
		signal.throwIfAborted();
		return;
	}
	signal.throwIfAborted();
	await new Promise((resolve, reject) => {
		const onAbort = () => {
			clearTimeout(timeout);
			reject(signal.reason);
		};
		const timeout = setTimeout(() => {
			signal.removeEventListener("abort", onAbort);
			resolve();
		}, milliseconds);
		signal.addEventListener("abort", onAbort, { once: true });
	});
}
function logsFailedError(deploymentId, status) {
	return new CliStructuredError("SERVICE.LOGS_FAILED", `Failed to read logs for version ${deploymentId}`, {
		why: `The Management API returned HTTP ${status}.`,
		meta: { status },
		nextActions: [adviceAction("Retry the command, or rerun with --log-level verbose for more detail."), runCommandAction("Show the version", `service version show ${deploymentId}`)]
	});
}
/**
* The body ended mid-page, without the terminal record that closes one.
* Distinct from SERVICE.LOGS_NO_CURSOR, which is a page that closed
* properly and said there is nothing to resume from: this one is an
* incomplete read, and the lines already printed are not the whole page.
*/
function logsIncompleteError(deploymentId) {
	return new CliStructuredError("SERVICE.LOGS_INCOMPLETE", `Incomplete log page for version ${deploymentId}`, {
		why: "The response ended without the record that closes a page, so the lines shown may be only part of it.",
		nextActions: [adviceAction("Rerun the command to read the page again.")]
	});
}
/** An error terminal record is the platform reporting that the log read
*  itself failed, so it settles the run rather than printing. */
function logStreamFailedError(deploymentId, record) {
	return new CliStructuredError("SERVICE.LOGS_FAILED", `Log stream failed for version ${deploymentId}`, {
		why: record.message,
		meta: {
			code: record.code,
			retryable: record.retryable,
			...record.cursor === null ? {} : { cursor: record.cursor }
		},
		nextActions: [runCommandAction("Show the version", `service version show ${deploymentId}`)]
	});
}
function listDeployments(ctx, provider, service) {
	return provider.listDeployments(service.id, { signal: ctx.signal }).catch((error) => {
		throw deployFailedError("Failed to list service versions", error, [runCommandAction("List versions", `service version list ${service.name}`)]);
	});
}
/** `--version-id <id>` with a service target: the id must belong to
*  the resolved service. */
async function resolveVersionInService(ctx, state, deploymentId) {
	const deploymentsResult = await listDeployments(ctx, state.provider, state.service);
	const deployment = requireVersionForService(deploymentsResult.deployments, deploymentId, state.service.name);
	return {
		service: deploymentsResult.app,
		version: deployment
	};
}
/** No `--version-id`: read whatever is live for the resolved service. */
async function resolveLiveVersion(ctx, state) {
	const deploymentsResult = await listDeployments(ctx, state.provider, state.service);
	const currentLiveDeploymentId = resolveCurrentLiveVersionId(deploymentsResult.app, deploymentsResult.deployments);
	const deployments = applyLiveVersionHint(deploymentsResult.deployments, currentLiveDeploymentId);
	const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
	if (!deployment) throw noVersionsError("No versions available to read logs from", `The service "${deploymentsResult.app.name}" does not have a live version.`, deploymentsResult.app.name);
	return {
		service: deploymentsResult.app,
		version: deployment
	};
}
/**
* Reads one page and reports its log records. Returns the terminal
* record that closed it — the caller decides whether that ends the run
* or starts the next page.
*
* Every page ends with a terminal record, so a body that stops without
* one was truncated. The lines that did arrive have already been
* reported, but the run must not settle as though it had read the whole
* page: the user would have a partial log and no way to tell.
*/
async function readPage(ctx, deploymentId, query) {
	const { data, response } = await ctx.api.GET("/v1/deployments/{deploymentId}/logs", {
		params: {
			path: { deploymentId },
			query
		},
		parseAs: "stream",
		signal: ctx.signal
	});
	const body = data;
	if (!response.ok || !body) {
		await body?.cancel().catch(() => void 0);
		throw response.status === 404 ? versionNotFoundError(deploymentId) : logsFailedError(deploymentId, response.status);
	}
	let terminal = null;
	await forEachNdjsonRecord(body, (record) => {
		if (record.type === "terminal") {
			terminal = record;
			return;
		}
		ctx.report({
			kind: "output",
			source: "logs",
			channel: "data",
			line: record.text.replace(TRAILING_NEWLINE, ""),
			data: {
				byteStart: record.byteStart,
				byteEnd: record.byteEnd
			}
		});
	});
	if (terminal === null) throw logsIncompleteError(deploymentId);
	return terminal;
}
/**
* Following needs somewhere to resume from. Without a cursor the next
* request would carry no range at all, the endpoint would apply its
* default tail, and the same lines would print again every interval —
* silent duplication the user cannot act on. So the run stops and says
* why. It settles as an error rather than a clean end because `--follow`
* has no successful ending: it runs until interrupted (130) or fails,
* and an exit 0 here would be a novel outcome meaning "gave up".
*/
function requireResumeCursor(deploymentId, cursor) {
	if (cursor === null) throw new CliStructuredError("SERVICE.LOGS_NO_CURSOR", `Cannot follow logs for version ${deploymentId}`, {
		why: "The log page ended without a resume cursor, so there is no point to continue reading from.",
		nextActions: [adviceAction("Rerun without --follow to read the page, or retry if the version is still starting.")]
	});
	return cursor;
}
/**
* `--follow`: wait the poll interval, read the next page from the cursor
* the last one ended on, repeat until the user interrupts. Never
* returns — the run ends by abort (the engine settles 130) or by throw.
*/
async function followPages(ctx, deploymentId, startCursor) {
	const interval = pollIntervalMs(ctx);
	let cursor = requireResumeCursor(deploymentId, startCursor);
	let retriedAfterError = false;
	for (;;) {
		await sleep(interval, ctx.signal);
		const next = await readPage(ctx, deploymentId, { cursor });
		if (next.kind === "error") {
			if (!next.retryable || retriedAfterError) throw logStreamFailedError(deploymentId, next);
			retriedAfterError = true;
			continue;
		}
		retriedAfterError = false;
		cursor = requireResumeCursor(deploymentId, next.cursor);
	}
}
/**
* A globally-unique version id is a complete target on its own, so
* `--version-id` with no service argument resolves it directly, the
* way `service version show` does — no project resolution at all. A
* service argument scopes the lookup to that service.
*/
async function resolveLogsTarget(ctx, options) {
	const explicitVersionId = options.versionId;
	const serviceRequested = options.service !== void 0;
	if (explicitVersionId !== void 0 && !serviceRequested) {
		const subject = await resolveVersionSubject(ctx, explicitVersionId);
		return {
			projectId: null,
			target: {
				service: subject.service,
				version: subject.version
			}
		};
	}
	const readState = await resolveServiceReadState(ctx, {
		...options.service !== void 0 ? { serviceName: options.service } : {},
		...options.project !== void 0 ? { projectRef: options.project } : {},
		...options.branch !== void 0 ? { branchName: options.branch } : {},
		commandName: "service logs"
	});
	return {
		projectId: readState.projectId,
		target: explicitVersionId !== void 0 ? await resolveVersionInService(ctx, readState, explicitVersionId) : await resolveLiveVersion(ctx, readState)
	};
}
const serviceLogsCommand = defineSessionCommand({
	help: {
		summary: "Read logs for a version of the service",
		examples: [
			"service logs my-service",
			"service logs my-service --tail 500",
			"service logs my-service --follow",
			"service logs --version-id cpv_123 --from-start"
		]
	},
	args: {
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) },
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the service lives on (default: the default branch)",
				placeholder: "name"
			}),
			versionId: flag.string({
				brief: "Service version id to read (default: the live version)",
				placeholder: "id"
			}),
			tail: flag.number({
				brief: `Read the last N lines (default ${DEFAULT_TAIL})`,
				placeholder: "n"
			}),
			fromStart: flag.boolean({ brief: "Read from the beginning instead of the last lines" }),
			follow: flag.boolean({ brief: "Keep polling for new lines until interrupted" })
		}
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		if (args.flags.fromStart && args.flags.tail !== void 0) throw logsRangeConflictError();
		const { projectId, target } = await resolveLogsTarget(ctx, {
			service: args.positionals.service,
			...args.flags
		});
		const versionId = target.version.id;
		for (const line of [
			...projectId === null ? [] : [`project: ${projectId}`],
			`service: ${target.service.name}`,
			`version: ${versionId}`
		]) ctx.report({
			kind: "output",
			source: "logs",
			channel: "diagnostic",
			line
		});
		const terminal = await readPage(ctx, versionId, args.flags.fromStart ? { from_start: "true" } : { tail: args.flags.tail ?? DEFAULT_TAIL });
		if (terminal.kind === "error") throw logStreamFailedError(versionId, terminal);
		if (!args.flags.follow) return ok(void 0);
		return followPages(ctx, versionId, terminal.cursor);
	}
});
//#endregion
//#region src/commands/service/open.ts
const serviceOpenCommand = defineCommand({
	help: {
		summary: "Open the service's live URL",
		examples: ["service open my-service", "service open my-service --branch feature-x"]
	},
	args: {
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the service lives on (default: the default branch)",
				placeholder: "name"
			})
		},
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) }
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const state = await resolveServiceReadState(ctx, {
			serviceName: args.positionals.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: "service open"
		});
		const deploymentsResult = await state.provider.listDeployments(state.service.id, { signal: ctx.signal }).catch((error) => {
			throw deployFailedError("Failed to resolve service URL", error, [runCommandAction("Inspect the service", `service show ${state.service.name}`)]);
		});
		const currentLiveDeploymentId = resolveCurrentLiveVersionId(deploymentsResult.app, deploymentsResult.deployments);
		const deployments = sortVersionsNewestFirst(applyLiveVersionHint(deploymentsResult.deployments, currentLiveDeploymentId));
		const liveDeployment = currentLiveDeploymentId ? deployments.find((deployment) => deployment.id === currentLiveDeploymentId) ?? null : null;
		if (!liveDeployment) throw noVersionsError("No versions available to open", `The service "${deploymentsResult.app.name}" does not have any versions yet.`, deploymentsResult.app.name);
		if (!deploymentsResult.app.liveUrl) throw liveUrlUnavailableError(deploymentsResult.app.name);
		const url = deploymentsResult.app.liveUrl;
		const { opened } = await ctx.openUrl({
			url,
			message: "Live URL"
		});
		const result = {
			projectId: state.projectId,
			service: toServiceSummary(deploymentsResult.app),
			url,
			opened
		};
		return ok(ctx.present({ data: result }, openPresentations(result, liveDeployment.id)));
	}
});
//#endregion
//#region src/commands/service/show.ts
const serviceShowCommand = defineCommand({
	help: {
		summary: "Show the service and its current version",
		examples: ["service show my-service", "service show my-service --branch feature-x"]
	},
	args: {
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the service lives on (default: the default branch)",
				placeholder: "name"
			})
		},
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) }
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const state = await resolveServiceReadState(ctx, {
			serviceName: args.positionals.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: "service show"
		});
		const deploymentsResult = await state.provider.listDeployments(state.service.id, { signal: ctx.signal }).catch((error) => {
			throw deployFailedError("Failed to inspect service", error, [runCommandAction("List versions", `service version list ${state.service.name}`)]);
		});
		const currentLiveDeploymentId = resolveCurrentLiveVersionId(deploymentsResult.app, deploymentsResult.deployments);
		const deployments = sortVersionsNewestFirst(applyLiveVersionHint(deploymentsResult.deployments, currentLiveDeploymentId));
		const liveVersion = currentLiveDeploymentId ? deployments.find((deployment) => deployment.id === currentLiveDeploymentId) ?? null : null;
		const result = {
			projectId: state.projectId,
			service: toServiceSummary(deploymentsResult.app),
			liveVersion,
			liveUrl: liveVersion ? deploymentsResult.app.liveUrl : null,
			recentVersions: deployments.slice(0, 5)
		};
		return ok(ctx.present({ data: result }, showPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/version-delete.ts
const serviceVersionDeleteCommand = defineCommand({
	help: {
		summary: "Delete a service version and the artifact it holds",
		examples: ["service version delete cpv_123", "service version delete cpv_123 --confirm cpv_123"]
	},
	args: { positionals: { version: positional.string({
		brief: "Version id to delete",
		placeholder: "version"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, service, version } = await resolveVersionSubject(ctx, args.positionals.version);
		if (!await ctx.prompt.consent(`Delete version "${version.id}" from Service "${service.name}"?`, { token: version.id })) throw userCancelledError("Deployment deletion canceled");
		ctx.report({
			kind: "step-started",
			step: "delete"
		});
		try {
			await provider.deleteDeployment({
				deploymentId: version.id,
				signal: ctx.signal
			});
		} catch (error) {
			ctx.report({
				kind: "step-finished",
				step: "delete",
				outcome: "failed"
			});
			throw deployFailedError("Failed to delete version", error, [runCommandAction("List versions", `service version list ${service.name}`)]);
		}
		ctx.report({
			kind: "step-finished",
			step: "delete",
			outcome: "ok"
		});
		const result = {
			service: toServiceSummary(service),
			versionId: version.id,
			deleted: true
		};
		return ok(ctx.present({ data: result }, versionDeletePresentations(result)));
	}
});
//#endregion
//#region src/commands/service/version-list.ts
const serviceVersionListCommand = defineCommand({
	help: {
		summary: "List versions of the service",
		examples: ["service version list my-service", "service version list my-service --branch feature-x"]
	},
	args: {
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the service lives on (default: the default branch)",
				placeholder: "name"
			})
		},
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) }
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const state = await resolveServiceReadState(ctx, {
			serviceName: args.positionals.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: "service version list"
		});
		const deploymentsResult = await state.provider.listDeployments(state.service.id, { signal: ctx.signal }).catch((error) => {
			throw deployFailedError("Failed to list service versions", error, []);
		});
		const currentLiveDeploymentId = resolveCurrentLiveVersionId(deploymentsResult.app, deploymentsResult.deployments);
		const deployments = sortVersionsNewestFirst(applyLiveVersionHint(deploymentsResult.deployments, currentLiveDeploymentId));
		const result = {
			projectId: state.projectId,
			service: toServiceSummary(deploymentsResult.app),
			versions: deployments
		};
		return ok(ctx.present({ data: result }, versionListPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/version-promote.ts
const serviceVersionPromoteCommand = defineCommand({
	help: {
		summary: "Promote a service version to production by rebuilding with production env vars",
		examples: ["service version promote cpv_123"]
	},
	args: { positionals: { version: positional.string({
		brief: "Version id to promote",
		placeholder: "version"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { provider, service, version } = await resolveVersionSubject(ctx, args.positionals.version);
		const alreadyLive = service.liveDeploymentId === version.id;
		if (!alreadyLive) {
			ctx.report({
				kind: "step-started",
				step: "promote"
			});
			try {
				await provider.promoteDeployment({
					appId: service.id,
					deploymentId: version.id,
					signal: ctx.signal,
					progress: promoteProgressReporter(ctx, version.id)
				});
			} catch (error) {
				ctx.report({
					kind: "step-finished",
					step: "promote",
					outcome: "failed"
				});
				throw deployFailedError("Failed to promote version", error, [runCommandAction("List versions", `service version list ${service.name}`)]);
			}
			ctx.report({
				kind: "step-finished",
				step: "promote",
				outcome: "ok"
			});
		}
		const result = {
			service: toServiceSummary(service),
			version: {
				...version,
				status: "running",
				live: true
			}
		};
		const diagnostics = alreadyLive ? [{
			code: "SERVICE.VERSION_ALREADY_LIVE",
			severity: "warn",
			summary: "The selected version is already live for this service.",
			nextActions: []
		}] : [];
		return ok(ctx.present({
			data: result,
			diagnostics
		}, promotePresentations(result, alreadyLive)));
	}
});
//#endregion
//#region src/commands/service/version-rollback.ts
const serviceVersionRollbackCommand = defineCommand({
	help: {
		summary: "Roll back production to a previous service version",
		examples: [
			"service version rollback my-service",
			"service version rollback my-service --to cpv_123",
			"service version rollback my-service --to cpv_123 --confirm cpv_123"
		]
	},
	args: {
		flags: {
			project: flag.string({
				brief: "Project id or name",
				placeholder: "id-or-name"
			}),
			branch: flag.string({
				brief: "Branch the service lives on (default: the default branch)",
				placeholder: "name"
			}),
			to: flag.string({
				brief: "Version id to roll back to (default: the version before the live one)",
				placeholder: "version"
			})
		},
		positionals: { service: positional.optionalString({
			brief: "Service id or name",
			placeholder: "service"
		}) }
	},
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const state = await resolveServiceReadState(ctx, {
			serviceName: args.positionals.service,
			projectRef: args.flags.project,
			branchName: args.flags.branch,
			commandName: "service version rollback"
		});
		const deploymentsResult = await state.provider.listDeployments(state.service.id, { signal: ctx.signal }).catch((error) => {
			throw deployFailedError("Failed to list service versions", error, [runCommandAction("List versions", `service version list ${state.service.name}`)]);
		});
		const currentLiveDeploymentId = resolveCurrentLiveVersionId(deploymentsResult.app, deploymentsResult.deployments);
		const targetVersion = args.flags.to ? requireVersionForService(deploymentsResult.deployments, args.flags.to, state.service.name) : resolveRollbackTarget(deploymentsResult.deployments, currentLiveDeploymentId, state.service.name);
		if (!await ctx.prompt.consent(`Roll back Service "${state.service.name}" to version ${targetVersion.id} and make it live?`, { token: targetVersion.id })) throw userCancelledError("Service rollback canceled");
		const alreadyLive = currentLiveDeploymentId === targetVersion.id;
		if (!alreadyLive) {
			ctx.report({
				kind: "step-started",
				step: "rollback"
			});
			try {
				await state.provider.promoteDeployment({
					appId: state.service.id,
					deploymentId: targetVersion.id,
					signal: ctx.signal,
					progress: promoteProgressReporter(ctx, targetVersion.id)
				});
			} catch (error) {
				ctx.report({
					kind: "step-finished",
					step: "rollback",
					outcome: "failed"
				});
				throw deployFailedError("Failed to roll back version", error, [runCommandAction("List versions", `service version list ${state.service.name}`)]);
			}
			ctx.report({
				kind: "step-finished",
				step: "rollback",
				outcome: "ok"
			});
		}
		const result = {
			projectId: state.projectId,
			service: toServiceSummary(deploymentsResult.app),
			version: {
				...targetVersion,
				status: "running",
				live: true
			},
			previousLiveVersionId: currentLiveDeploymentId
		};
		const diagnostics = alreadyLive ? [{
			code: "SERVICE.VERSION_ALREADY_LIVE",
			severity: "warn",
			summary: "The selected version is already live for this service.",
			nextActions: []
		}] : [];
		return ok(ctx.present({
			data: result,
			diagnostics
		}, rollbackPresentations(result, alreadyLive)));
	}
});
//#endregion
//#region src/commands/service/version-show.ts
const serviceVersionShowCommand = defineCommand({
	help: {
		summary: "Show a service version in detail",
		examples: ["service version show cpv_123"]
	},
	args: { positionals: { version: positional.string({
		brief: "Version id",
		placeholder: "version"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const versionId = args.positionals.version;
		const shown = await serviceProvider(ctx).showDeployment(versionId, { signal: ctx.signal }).catch((error) => {
			throw deployFailedError("Failed to show version", error, [runCommandAction("List versions", "service version list <service>")]);
		});
		if (!shown) throw versionNotFoundError(versionId);
		const result = {
			service: shown.app ? toServiceSummary(shown.app) : null,
			version: {
				...shown.deployment,
				live: shown.app ? shown.app.liveDeploymentId === shown.deployment.id : null
			}
		};
		return ok(ctx.present({ data: result }, versionShowPresentations(result)));
	}
});
//#endregion
//#region src/commands/service/version-run-state.ts
/**
* `start` and `stop` are the same command with the direction reversed,
* so the verb decides every value that differs between them. Spelling
* that out here keeps the two from drifting apart, which is the risk
* with a body this long duplicated.
*/
const VERBS = {
	start: {
		/** The status the API reports once the version is up. */
		settledStatus: "running",
		diagnosticCode: "SERVICE.VERSION_ALREADY_RUNNING",
		diagnosticSummary: "The selected version is already running.",
		failureSummary: "Failed to start version",
		presentations: versionStartPresentations
	},
	stop: {
		settledStatus: "stopped",
		diagnosticCode: "SERVICE.VERSION_ALREADY_STOPPED",
		diagnosticSummary: "The selected version is already stopped.",
		failureSummary: "Failed to stop version",
		presentations: versionStopPresentations
	}
};
async function changeVersionRunState(ctx, versionId, verb) {
	const spec = VERBS[verb];
	const { provider, service, version } = await resolveVersionSubject(ctx, versionId);
	const alreadyInState = version.status === spec.settledStatus;
	let observed = version;
	if (!alreadyInState) {
		ctx.report({
			kind: "step-started",
			step: verb
		});
		try {
			await (verb === "start" ? provider.startDeployment({
				deploymentId: version.id,
				signal: ctx.signal
			}) : provider.stopDeployment({
				deploymentId: version.id,
				signal: ctx.signal
			}));
			observed = await provider.readDeployment({
				deploymentId: version.id,
				signal: ctx.signal
			});
		} catch (error) {
			ctx.report({
				kind: "step-finished",
				step: verb,
				outcome: "failed"
			});
			throw deployFailedError(spec.failureSummary, error, [runCommandAction("Show the version", `service version show ${version.id}`)]);
		}
		ctx.report({
			kind: "step-finished",
			step: verb,
			outcome: "ok"
		});
	}
	const result = {
		service: toServiceSummary(service),
		version: observed,
		alreadyInState
	};
	return {
		result,
		diagnostics: alreadyInState ? [{
			code: spec.diagnosticCode,
			severity: "warn",
			summary: spec.diagnosticSummary,
			nextActions: []
		}] : [],
		presentations: spec.presentations(result)
	};
}
//#endregion
//#region src/commands/service/version-start.ts
const serviceVersionStartCommand = defineCommand({
	help: {
		summary: "Start a stopped service version",
		examples: ["service version start cpv_123"]
	},
	args: { positionals: { version: positional.string({
		brief: "Version id to start",
		placeholder: "version"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { result, diagnostics, presentations } = await changeVersionRunState(ctx, args.positionals.version, "start");
		return ok(ctx.present({
			data: result,
			diagnostics
		}, presentations));
	}
});
//#endregion
//#region src/commands/service/version-stop.ts
const serviceVersionStopCommand = defineCommand({
	help: {
		summary: "Stop a running service version",
		examples: ["service version stop cpv_123"]
	},
	args: { positionals: { version: positional.string({
		brief: "Version id to stop",
		placeholder: "version"
	}) } },
	needs: { credentials: true },
	handler: async (args, ctx) => {
		const { result, diagnostics, presentations } = await changeVersionRunState(ctx, args.positionals.version, "stop");
		return ok(ctx.present({
			data: result,
			diagnostics
		}, presentations));
	}
});
//#endregion
//#region src/commands/skills/family.ts
/** Skill delivery is product-agnostic — the same two commands serve the
*  ORM's skills and Composer's — so it is its own family rather than
*  part of either product's. */
const skillsCommandFamily = defineCommandFamily({
	configSection: skillsConfigSection,
	docsBaseUrl: DOCS_ERRORS_BASE_URL,
	commands: {
		sync: skillsSyncCommand,
		list: defineCommand({
			help: {
				summary: "Show which Prisma agent skills are installed in this project",
				examples: ["skills list", "skills list --json"]
			},
			needs: { config: skillsConfigSection },
			handler: async (_args, ctx) => {
				const status = await readSkillsStatus(ctx.cwd, { agents: ctx.config.agents });
				const result = {
					projectRoot: status.projectRoot,
					agents: ctx.config.agents,
					packages: packageReports(status.packages),
					skills: status.skills.map((skill) => ({
						skill: skill.skill,
						library: skill.library,
						version: skill.version,
						upToDate: skill.upToDate,
						targets: skill.targets.map((target) => ({
							dir: target.dir,
							syncedVersion: target.syncedVersion,
							state: target.state
						}))
					})),
					orphaned: status.orphans.map((orphan) => ({
						skill: orphan.skill,
						library: orphan.library,
						dirs: orphan.dirs
					})),
					checkDisabled: status.checkDisabled || !ctx.config.check,
					upToDate: status.upToDate
				};
				return ok(ctx.present({
					data: result,
					diagnostics: versionConflictDiagnostics(status.packages)
				}, listPresentations$5(result)));
			}
		})
	}
});
//#endregion
//#region src/cli.ts
const platformCommandFamily = defineCommandFamily({
	docsBaseUrl: DOCS_ERRORS_BASE_URL,
	commands: {
		login: authLoginCommand,
		logout: authLogoutCommand,
		whoami: authWhoamiCommand,
		workspaceList: authWorkspaceListCommand,
		workspaceUse: authWorkspaceUseCommand,
		workspaceLogout: authWorkspaceLogoutCommand,
		projectList: projectListCommand,
		projectShow: projectShowCommand,
		projectCreate: projectCreateCommand,
		projectLink: projectLinkCommand,
		projectRename: projectRenameCommand,
		projectDelete: projectDeleteCommand,
		projectTransfer: projectTransferCommand,
		projectEnvAdd: projectEnvAddCommand,
		projectEnvUpdate: projectEnvUpdateCommand,
		projectEnvList: projectEnvListCommand,
		projectEnvDelete: projectEnvDeleteCommand,
		postgresList: postgresListCommand,
		postgresShow: postgresShowCommand,
		postgresCreate: postgresCreateCommand,
		postgresUsage: postgresUsageCommand,
		postgresBackupRestore: postgresBackupRestoreCommand,
		postgresDelete: postgresDeleteCommand,
		postgresBackupList: postgresBackupListCommand,
		postgresConnectionList: postgresConnectionListCommand,
		postgresConnectionCreate: postgresConnectionCreateCommand,
		postgresConnectionRotate: postgresConnectionRotateCommand,
		postgresConnectionDelete: postgresConnectionDeleteCommand,
		bucketList: bucketListCommand,
		bucketCreate: bucketCreateCommand,
		bucketDelete: bucketDeleteCommand,
		bucketKeyList: bucketKeyListCommand,
		bucketKeyCreate: bucketKeyCreateCommand,
		bucketKeyDelete: bucketKeyDeleteCommand,
		branchList: branchListCommand,
		gitConnect: gitConnectCommand,
		gitDisconnect: gitDisconnectCommand,
		serviceList: serviceListCommand,
		serviceLogs: serviceLogsCommand,
		serviceCreate: serviceCreateCommand,
		serviceShow: serviceShowCommand,
		serviceOpen: serviceOpenCommand,
		serviceVersionList: serviceVersionListCommand,
		serviceVersionShow: serviceVersionShowCommand,
		serviceVersionPromote: serviceVersionPromoteCommand,
		serviceVersionRollback: serviceVersionRollbackCommand,
		serviceVersionStart: serviceVersionStartCommand,
		serviceVersionStop: serviceVersionStopCommand,
		serviceVersionDelete: serviceVersionDeleteCommand,
		serviceDelete: serviceDeleteCommand,
		serviceDomainAdd: serviceDomainAddCommand,
		serviceDomainShow: serviceDomainShowCommand,
		serviceDomainDelete: serviceDomainDeleteCommand,
		serviceDomainRetry: serviceDomainRetryCommand,
		serviceDomainWait: serviceDomainWaitCommand
	}
});
/**
* Composer's commands, contributed by composer's own package and run by
* this process, mounted as shipped. Only the command definitions and
* their handler entry functions load here; the alchemy and effect
* constellation stays behind composer's dynamic executor imports, so
* mounting costs an unrelated command nothing.
*/
const composerCommandFamily = createComposerFamily();
/**
* The ORM commands, contributed by orm-toolchain's own package, mounted
* as shipped: the family keys are the mount paths, so the shell adds
* nothing. The family object carries its `orm` config section, its docs
* base and its redirect table. Unlike composer's, this family's entry
* module imports esbuild and arktype statically, so every invocation of
* this bin pays that import; fixing that is orm-toolchain's move.
*/
const ormCommandFamily$1 = ormCommandFamily;
/** The engine ships the three telemetry commands and the group help
*  text that belongs to them; both halves are spread in below. */
const telemetry = telemetryCommandGroup({ docsUrl: CLI_DOCS_URL });
const cliGroups = {
	auth: { brief: "Manage local authentication for the CLI" },
	project: { brief: "Manage and inspect your Prisma projects" },
	"project env": { brief: "Manage environment variables for the active project" },
	postgres: { brief: "Manage Prisma Postgres databases for a project" },
	"postgres backup": { brief: "Inspect and restore platform-created database backups" },
	"postgres connection": { brief: "Manage one-time-view database connection strings" },
	bucket: { brief: "Manage object-store buckets for a project" },
	"bucket key": { brief: "Manage access keys for an object-store bucket" },
	branch: { brief: "View your Platform branches" },
	git: { brief: "Manage Git repository connections for a project" },
	service: { brief: "Manage services and their versions for a project" },
	"service domain": { brief: "Manage custom domains for a service" },
	"service version": { brief: "Manage the versions of a service" },
	"auth workspace": { brief: "Manage local workspace sessions" },
	contract: { brief: "Define and emit your application data contract" },
	db: { brief: "Verify, sign and update your database against the contract" },
	migration: { brief: "Plan, inspect and scaffold on-disk migrations" },
	"migration ref": { brief: "Manage named refs that point at contracts" },
	orm: { brief: "Initialize a Prisma ORM project" },
	skills: { brief: "Keep this project's Prisma agent skills current" },
	...telemetry.groups
};
const mountedCommands = {
	"auth login": authLoginCommand,
	"auth logout": authLogoutCommand,
	"auth whoami": authWhoamiCommand,
	"auth workspace list": authWorkspaceListCommand,
	"auth workspace use": authWorkspaceUseCommand,
	"auth workspace logout": authWorkspaceLogoutCommand,
	"project list": projectListCommand,
	"project show": projectShowCommand,
	"project create": projectCreateCommand,
	"project link": projectLinkCommand,
	"project rename": projectRenameCommand,
	"project delete": projectDeleteCommand,
	"project transfer": projectTransferCommand,
	"project env add": projectEnvAddCommand,
	"project env update": projectEnvUpdateCommand,
	"project env list": projectEnvListCommand,
	"project env delete": projectEnvDeleteCommand,
	"postgres list": postgresListCommand,
	"postgres show": postgresShowCommand,
	"postgres create": postgresCreateCommand,
	"postgres usage": postgresUsageCommand,
	"postgres delete": postgresDeleteCommand,
	"postgres backup list": postgresBackupListCommand,
	"postgres backup restore": postgresBackupRestoreCommand,
	"postgres connection list": postgresConnectionListCommand,
	"postgres connection create": postgresConnectionCreateCommand,
	"postgres connection rotate": postgresConnectionRotateCommand,
	"postgres connection delete": postgresConnectionDeleteCommand,
	"bucket list": bucketListCommand,
	"bucket create": bucketCreateCommand,
	"bucket delete": bucketDeleteCommand,
	"bucket key list": bucketKeyListCommand,
	"bucket key create": bucketKeyCreateCommand,
	"bucket key delete": bucketKeyDeleteCommand,
	"branch list": branchListCommand,
	"git connect": gitConnectCommand,
	"git disconnect": gitDisconnectCommand,
	"service list": serviceListCommand,
	"service logs": serviceLogsCommand,
	"service create": serviceCreateCommand,
	"service show": serviceShowCommand,
	"service open": serviceOpenCommand,
	"service version list": serviceVersionListCommand,
	"service version show": serviceVersionShowCommand,
	"service version promote": serviceVersionPromoteCommand,
	"service version rollback": serviceVersionRollbackCommand,
	"service version start": serviceVersionStartCommand,
	"service version stop": serviceVersionStopCommand,
	"service version delete": serviceVersionDeleteCommand,
	"service delete": serviceDeleteCommand,
	"service domain add": serviceDomainAddCommand,
	"service domain show": serviceDomainShowCommand,
	"service domain delete": serviceDomainDeleteCommand,
	"service domain retry": serviceDomainRetryCommand,
	"service domain wait": serviceDomainWaitCommand,
	deploy: composerCommandFamily.commands.deploy,
	dev: composerCommandFamily.commands.dev,
	"contract emit": ormCommandFamily$1.commands["contract emit"],
	"contract infer": ormCommandFamily$1.commands["contract infer"],
	"db init": ormCommandFamily$1.commands["db init"],
	"db schema": ormCommandFamily$1.commands["db schema"],
	"db sign": ormCommandFamily$1.commands["db sign"],
	"db update": ormCommandFamily$1.commands["db update"],
	"db verify": ormCommandFamily$1.commands["db verify"],
	"db migrate": ormCommandFamily$1.commands["db migrate"],
	"contract format": ormCommandFamily$1.commands["contract format"],
	"orm init": ormCommandFamily$1.commands["orm init"],
	lsp: ormCommandFamily$1.commands.lsp,
	"migration check": ormCommandFamily$1.commands["migration check"],
	"migration graph": ormCommandFamily$1.commands["migration graph"],
	"migration list": ormCommandFamily$1.commands["migration list"],
	"migration log": ormCommandFamily$1.commands["migration log"],
	"migration new": ormCommandFamily$1.commands["migration new"],
	"migration plan": ormCommandFamily$1.commands["migration plan"],
	"migration show": ormCommandFamily$1.commands["migration show"],
	"migration status": ormCommandFamily$1.commands["migration status"],
	"migration ref delete": ormCommandFamily$1.commands["migration ref delete"],
	"migration ref list": ormCommandFamily$1.commands["migration ref list"],
	"migration ref set": ormCommandFamily$1.commands["migration ref set"],
	init: initCommand,
	"skills sync": skillsCommandFamily.commands.sync,
	"skills list": skillsCommandFamily.commands.list,
	feedback: feedbackCommand,
	...telemetry.commands
};
function buildCli() {
	return createCli({
		name: CLI_NAME,
		version: getCliVersion(),
		commandFamilies: [
			platformCommandFamily,
			composerCommandFamily,
			ormCommandFamily$1,
			skillsCommandFamily
		],
		groups: cliGroups,
		commands: mountedCommands,
		help: {
			tagline: "The Prisma Developer Platform, from your terminal",
			description: "Deploy your app with isolated infrastructure for every branch.",
			examples: [
				"auth login",
				"project list",
				"deploy"
			],
			docsUrl: CLI_DOCS_URL
		},
		telemetry: { docsUrl: CLI_DOCS_URL }
	});
}
//#endregion
//#region ../cli-telemetry/dist/index.js
/**
* Fork the detached sender and hand it one payload over IPC. Returns
* synchronously — the child runs in the background and never blocks the
* parent. Every failure mode is swallowed; the parent's stdout/stderr is
* untouched in normal operation, the only escape valve being
* `PRISMA_DEBUG=1` which routes diagnostics to stderr.
*/
function runTelemetry(inputs) {
	try {
		const child = fork(inputs.senderPath, [], {
			detached: true,
			stdio: [
				"pipe",
				"ignore",
				"ignore",
				"ipc"
			]
		});
		child.on("error", () => {});
		child.send(inputs.payload, (err) => {
			if (err !== null && process.env.PRISMA_DEBUG === "1") process.stderr.write(`[cli-telemetry] parent send error: ${String(err)}\n`);
		});
		child.disconnect();
		child.unref();
		return { spawned: true };
	} catch (err) {
		if (process.env.PRISMA_DEBUG === "1") process.stderr.write(`[cli-telemetry] parent fork failed: ${String(err)}\n`);
		return {
			spawned: false,
			reason: "fork-failed"
		};
	}
}
//#endregion
//#region src/auth/legacy-state.ts
const LEGACY_PLACEHOLDER_NAME = "Unknown workspace";
/**
* The sessions re-serialized in the legacy store's record shape. The
* 3.x CLI reads `tokens` from auth.json (`data.tokens || []`, silently
* empty for any other shape), so a write that dropped the key made
* every session invisible to `@prisma/cli@latest` on the same machine
* the moment this CLI first mutated the file (#204). Sessions without
* a refresh token still mirror; the legacy reader skips them, exactly
* as it skips its own unrefreshable records.
*/
function legacyTokensMirror(sessions) {
	return sessions.map((session) => ({
		workspaceId: session.workspaceId,
		token: session.token,
		...session.refreshToken === void 0 ? {} : { refreshToken: session.refreshToken }
	}));
}
/**
* Keeps auth.context.json's `activeWorkspaceId` — the pointer the 3.x
* CLI selects its session with — in step with `currentWorkspaceId`.
* The rest of the context file (the remembered-workspace name map) is
* preserved verbatim; only the pointer moves.
*/
async function syncLegacyContext(authFilePath, currentWorkspaceId) {
	const contextFilePath = getAuthContextFilePath(authFilePath);
	const context = await readLegacyContext(contextFilePath);
	if (context.exists && context.activeWorkspaceId === currentWorkspaceId) return;
	if (!context.exists && currentWorkspaceId === null) return;
	const raw = await fs.readFile(contextFilePath, "utf8").catch(() => null);
	let workspaces = {};
	if (raw !== null) try {
		const parsed = JSON.parse(raw);
		if (typeof parsed.workspaces === "object" && parsed.workspaces !== null && !Array.isArray(parsed.workspaces)) workspaces = parsed.workspaces;
	} catch {}
	const tempPath = `${contextFilePath}.${randomUUID()}.tmp`;
	const payload = `${JSON.stringify({
		activeWorkspaceId: currentWorkspaceId,
		workspaces
	}, null, 2)}\n`;
	try {
		await fs.writeFile(tempPath, payload, "utf8");
		await fs.rename(tempPath, contextFilePath);
	} catch (error) {
		await fs.unlink(tempPath).catch(() => {});
		throw error;
	}
}
/**
* The legacy store read as sessions. Pure: adoption never writes, and
* the legacy files stay untouched until a mutation materializes the
* adopted set in the new format.
*/
async function adoptLegacyState(parsedAuthFile, authFilePath) {
	const entries = parsedAuthFile.tokens;
	if (!Array.isArray(entries)) return {
		version: 1,
		sessions: [],
		currentWorkspaceId: null
	};
	const context = await readLegacyContext(getAuthContextFilePath(authFilePath));
	const adopted = /* @__PURE__ */ new Map();
	for (const entry of entries) {
		const session = adoptLegacyEntry(entry, context);
		if (session) adopted.set(session.workspaceId, session);
	}
	const sessions = [...adopted.values()];
	return {
		version: 1,
		sessions,
		currentWorkspaceId: adoptedCurrent(sessions, context)
	};
}
function adoptLegacyEntry(entry, context) {
	if (typeof entry !== "object" || entry === null) return void 0;
	const { token, refreshToken } = entry;
	if (typeof token !== "string" || token.length === 0) return void 0;
	const workspaceId = credentialWorkspaceId(token);
	if (workspaceId === void 0) return void 0;
	const name = adoptedName(context.names[workspaceId], workspaceId);
	const expiresAt = claimedExpiresAt(token);
	return {
		workspaceId,
		...name === void 0 ? {} : { name },
		token,
		...typeof refreshToken === "string" && refreshToken.length > 0 ? { refreshToken } : {},
		...expiresAt === void 0 ? {} : { expiresAt: expiresAt.toISOString() }
	};
}
/** Legacy placeholders do not adopt: a name equal to "Unknown
*  workspace" or to the workspace id adopts as no name at all. */
function adoptedName(name, workspaceId) {
	const trimmed = name?.trim();
	if (!trimmed) return void 0;
	if (trimmed === LEGACY_PLACEHOLDER_NAME) return void 0;
	if (trimmed === workspaceId) return void 0;
	return trimmed;
}
function adoptedCurrent(sessions, context) {
	if (context.exists) {
		const pointed = context.activeWorkspaceId;
		return pointed !== null && sessions.some((session) => session.workspaceId === pointed) ? pointed : null;
	}
	return sessions.length === 1 ? sessions[0].workspaceId : null;
}
async function readLegacyContext(contextFilePath) {
	const absent = {
		exists: false,
		activeWorkspaceId: null,
		names: {}
	};
	const raw = await fs.readFile(contextFilePath, "utf8").catch(() => null);
	if (raw === null) return absent;
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch {
		return absent;
	}
	if (typeof parsed !== "object" || parsed === null) return absent;
	const { activeWorkspaceId, workspaces } = parsed;
	const names = {};
	if (typeof workspaces === "object" && workspaces !== null && !Array.isArray(workspaces)) for (const [workspaceId, value] of Object.entries(workspaces)) {
		const name = value?.name;
		if (typeof name === "string") names[workspaceId] = name;
	}
	return {
		exists: true,
		activeWorkspaceId: typeof activeWorkspaceId === "string" && activeWorkspaceId.trim() ? activeWorkspaceId.trim() : null,
		names
	};
}
//#endregion
//#region src/auth/state-file.ts
const STATE_FILE_ENV_VAR = "PRISMA_AUTH_FILE";
const DEPRECATED_STATE_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
const FILE_MODE = 384;
const LOCK_STALE_MS = 5e3;
const LOCK_RETRY_MS = 10;
const LOCK_WAIT_TIMEOUT_MS = 1e4;
const REFRESH_LOCK_STALE_MS = 3e4;
const REFRESH_LOCK_RETRY_MS = 100;
const REFRESH_LOCK_WAIT_TIMEOUT_MS = 3e4;
const STATE_LOCK_TIMINGS = {
	staleMs: LOCK_STALE_MS,
	retryMs: LOCK_RETRY_MS,
	waitTimeoutMs: LOCK_WAIT_TIMEOUT_MS
};
const REFRESH_LOCK_TIMINGS = {
	staleMs: REFRESH_LOCK_STALE_MS,
	retryMs: REFRESH_LOCK_RETRY_MS,
	waitTimeoutMs: REFRESH_LOCK_WAIT_TIMEOUT_MS
};
const EMPTY_STATE = {
	version: 1,
	sessions: [],
	currentWorkspaceId: null
};
function makeDebugLog(env, write = (text) => {
	process.stderr.write(text);
}) {
	if (env.PRISMA_DEBUG !== "1") return () => {};
	return (message) => {
		write(`prisma auth: ${message}\n`);
	};
}
function resolveStateFilePath(env) {
	const configured = env[STATE_FILE_ENV_VAR];
	if (configured?.trim()) return {
		filePath: path.resolve(configured),
		fromDeprecatedEnvVar: false
	};
	const deprecated = env[DEPRECATED_STATE_FILE_ENV_VAR];
	if (deprecated?.trim()) return {
		filePath: path.resolve(deprecated),
		fromDeprecatedEnvVar: true
	};
	return {
		filePath: defaultAuthFilePath(env),
		fromDeprecatedEnvVar: false
	};
}
function credentialsUnreadableError(filePath, cause) {
	return new CliStructuredError("CLI.CREDENTIALS_UNREADABLE", "Your stored credentials could not be read.", {
		why: `The credentials file at ${filePath} exists but could not be read.`,
		nextActions: [{
			kind: "user-choice",
			label: "Check the file's permissions, then run the command again."
		}],
		cause
	});
}
/**
* The stored state: the new format as written, the legacy store adopted
* (§7 — a pure read that writes nothing), or empty. Reads take no lock:
* writes rename a complete file into place.
*/
async function readCredentialState(filePath) {
	let raw;
	try {
		raw = await fs.readFile(filePath, "utf8");
	} catch (error) {
		if (error.code === "ENOENT") return EMPTY_STATE;
		throw credentialsUnreadableError(filePath, error);
	}
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch {
		return EMPTY_STATE;
	}
	if (typeof parsed !== "object" || parsed === null) return EMPTY_STATE;
	const shape = parsed;
	if (Array.isArray(shape.sessions)) return {
		version: typeof shape.version === "number" ? shape.version : 1,
		sessions: shape.sessions.filter(isStoredSession).map(normalizeSession),
		currentWorkspaceId: typeof shape.currentWorkspaceId === "string" && shape.currentWorkspaceId.length > 0 ? shape.currentWorkspaceId : null
	};
	return adoptLegacyState(parsed, filePath);
}
function isStoredSession(value) {
	if (typeof value !== "object" || value === null) return false;
	const candidate = value;
	return typeof candidate.workspaceId === "string" && candidate.workspaceId.length > 0 && typeof candidate.token === "string" && candidate.token.length > 0;
}
function normalizeSession(session) {
	return {
		workspaceId: session.workspaceId,
		...typeof session.name === "string" && session.name.length > 0 ? { name: session.name } : {},
		token: session.token,
		...typeof session.refreshToken === "string" && session.refreshToken.length > 0 ? { refreshToken: session.refreshToken } : {},
		...typeof session.expiresAt === "string" && session.expiresAt.length > 0 ? { expiresAt: session.expiresAt } : {}
	};
}
/** Temp file in the same directory, fsync, rename, mode 0600 — a reader
*  only ever sees a complete state. The written file also carries the
*  legacy `tokens` mirror and the auth.context.json pointer stays in
*  step, so the 3.x CLI sharing this store keeps seeing the sessions
*  (#204). Our own reader branches on `sessions` before it ever looks
*  at `tokens`, so the mirror is invisible to this CLI. */
async function writeCredentialState(filePath, state) {
	await fs.mkdir(path.dirname(filePath), { recursive: true });
	const tempPath = `${filePath}.${randomUUID()}.tmp`;
	const payload = {
		...state,
		tokens: legacyTokensMirror(state.sessions)
	};
	try {
		const handle = await fs.open(tempPath, "wx", FILE_MODE);
		try {
			await handle.writeFile(`${JSON.stringify(payload, null, 2)}\n`, "utf8");
			await handle.sync();
		} finally {
			await handle.close();
		}
		await fs.rename(tempPath, filePath);
	} catch (error) {
		await fs.unlink(tempPath).catch(() => {});
		throw error;
	}
	await fs.chmod(filePath, FILE_MODE).catch(() => {});
	await syncLegacyContext(filePath, state.currentWorkspaceId);
}
var StateLockTimeoutError = class extends CliStructuredError {
	constructor(lockPath, waitTimeoutMs) {
		super("CLI.CREDENTIALS_LOCKED", "Another prisma process is still updating your credentials.", {
			why: `The credentials lock at ${lockPath} was held for longer than ${waitTimeoutMs}ms.`,
			nextActions: [{
				kind: "user-choice",
				label: "Wait for the other command to finish, then try again."
			}]
		});
	}
};
/**
* The short advisory lock every mutation takes: acquire, re-read, apply
* one slice, write, release. Its only job is lost-update prevention
* between processes. No network I/O ever runs under it, so holds are
* milliseconds and a crashed holder's lock is simply taken over after a
* small fixed staleness threshold.
*/
async function withStateLock(filePath, debug, run) {
	return withFileLock(`${filePath}.lock`, debug, STATE_LOCK_TIMINGS, run);
}
/**
* The cross-process lock the delegated refresh holds for its whole
* read → exchange → write sequence, so two processes never spend the
* same refresh token. Distinct from the state lock: it IS held across
* network I/O, so its staleness and wait budgets are larger, and it
* uses its own lock path so short mutations are not queued behind it.
*/
async function withRefreshFileLock(filePath, debug, run) {
	return withFileLock(`${filePath}.refresh-lock`, debug, REFRESH_LOCK_TIMINGS, run);
}
async function withFileLock(lockPath, debug, timings, run) {
	const lockId = await acquireStateLock(lockPath, debug, timings);
	debug(`lock acquired ${lockPath}`);
	try {
		return await run();
	} finally {
		await releaseStateLock(lockPath, lockId);
		debug(`lock released ${lockPath}`);
	}
}
async function acquireStateLock(lockPath, debug, timings) {
	const lockId = randomUUID();
	const startedAt = Date.now();
	await fs.mkdir(path.dirname(lockPath), { recursive: true });
	while (true) {
		if (await tryCreateStateLock(lockPath, lockId)) return lockId;
		const tookOver = await takeOverStaleStateLock(lockPath, debug, timings);
		if (Date.now() - startedAt >= timings.waitTimeoutMs) throw new StateLockTimeoutError(lockPath, timings.waitTimeoutMs);
		if (!tookOver) await new Promise((resolve) => setTimeout(resolve, timings.retryMs));
	}
}
async function tryCreateStateLock(lockPath, lockId) {
	let handle;
	try {
		handle = await fs.open(lockPath, "wx", FILE_MODE);
	} catch (error) {
		if (error.code === "EEXIST") return false;
		throw error;
	}
	try {
		await handle.writeFile(lockId, "utf8");
	} finally {
		await handle.close();
	}
	return true;
}
/**
* Clear a crashed holder's lock. Removing it by RENAME is what makes
* two waiting processes safe: only one of them can rename a given
* path, so only one clears the corpse. Unlinking instead lets the
* second process delete the FIRST one's freshly created lock — both
* then run their read-modify-write at once and one update is lost,
* which is the very thing the lock exists to prevent.
*/
async function takeOverStaleStateLock(lockPath, debug, timings) {
	const stale = await fs.stat(lockPath).catch(() => null);
	if (stale === null) return true;
	if (Date.now() - stale.mtimeMs <= timings.staleMs) return false;
	const takenPath = `${lockPath}.${randomUUID()}.stale`;
	try {
		await fs.rename(lockPath, takenPath);
	} catch {
		return false;
	}
	const taken = await fs.stat(takenPath).catch(() => null);
	if (taken !== null && taken.mtimeMs !== stale.mtimeMs) {
		await fs.link(takenPath, lockPath).catch(() => {});
		await fs.unlink(takenPath).catch(() => {});
		return false;
	}
	await fs.unlink(takenPath).catch(() => {});
	debug(`lock taken over from a crashed holder ${lockPath}`);
	return true;
}
async function releaseStateLock(lockPath, lockId) {
	if (await fs.readFile(lockPath, "utf8").catch(() => null) !== lockId) return;
	await fs.unlink(lockPath).catch(() => {});
}
//#endregion
//#region src/auth/credential-manager.ts
/** The SDK's Tokens requires a workspace id, so an environment
*  credential whose claims name no workspace is given this instead. It
*  never leaves the manager, and it is never the empty string. */
const NO_WORKSPACE_CLAIMED = "(no workspace)";
/**
* The memory-backed storage, for a credential with no home record: a
* free function closing over one local variable. It is never given the
* state file's path, so no method of it — clearTokens included — can
* reach the stored sessions, and an environment credential whose
* workspace matches a stored session cannot delete that session.
*/
function memoryBackedStorage(credential, withRefreshLock) {
	let tokens = {
		workspaceId: credentialWorkspaceId(credential.token) ?? NO_WORKSPACE_CLAIMED,
		accessToken: credential.token,
		refreshToken: credential.refreshToken,
		expiresAt: claimedExpiresAt(credential.token) ?? credential.expiresAt
	};
	return {
		getTokens: async () => tokens,
		setTokens: async (rotated, expiresAt) => {
			tokens = {
				...rotated,
				expiresAt: claimedExpiresAt(rotated.accessToken) ?? expiresAt ?? tokens?.expiresAt
			};
		},
		clearTokens: async () => {
			tokens = null;
		},
		withRefreshLock
	};
}
/**
* The credential manager over one state file. Sessions are keyed by
* workspace id; which credential this process acts as is decided once;
* every mutation takes a short file lock, re-reads, applies its slice,
* and writes atomically. Reads never write and take no lock.
*/
var FileCredentialManager = class {
	#env;
	#filePath;
	#debug;
	#fetchWorkspaceName;
	#refreshCredential;
	#actingAs = { kind: "unresolved" };
	/** Built for the credential the process acts as. Every mutation that
	*  changes that discards it, so a command that mutates and then
	*  reaches for ctx.api cannot be handed storage for the credential it
	*  used to be acting as. */
	#activeStorage;
	#refreshLock = Promise.resolve();
	constructor(options) {
		this.#env = options.env;
		this.#filePath = resolveStateFilePath(options.env).filePath;
		this.#debug = makeDebugLog(options.env, options.debugWrite);
		this.#fetchWorkspaceName = options.fetchWorkspaceName;
		this.#refreshCredential = options.refreshCredential;
		this.#debug(`state file ${this.#filePath}`);
	}
	get stateFilePath() {
		return this.#filePath;
	}
	async activeCredential() {
		const actingAs = await this.#resolveActingAs();
		if (actingAs.kind === "environment") return environmentCredential(this.#requireEnvironmentToken());
		const state = await readCredentialState(this.#filePath);
		if (actingAs.kind === "none") {
			if (state.sessions.length > 0) throw credentialsRequiredError("sessions-held-none-selected");
			return null;
		}
		const record = state.sessions.find((session) => session.workspaceId === actingAs.workspaceId);
		if (record === void 0) throw credentialsRequiredError("session-ended");
		return storedCredential(record);
	}
	async sessions() {
		const state = await readCredentialState(this.#filePath);
		return {
			sessions: state.sessions.map((record) => toSession(record)),
			selectedWorkspaceId: resolvedMarker(state) ?? void 0
		};
	}
	async createSession(credential, workspaceId) {
		const environmentInForce = this.#environmentToken() !== void 0;
		const claimed = credentialWorkspaceId(credential.token);
		if (claimed !== void 0 && claimed !== workspaceId) throw credentialWorkspaceMismatchError(workspaceId);
		const created = await this.#mutate((state) => {
			const existing = state.sessions.find((session) => session.workspaceId === workspaceId);
			const record = {
				workspaceId,
				...existing?.name === void 0 ? {} : { name: existing.name },
				token: credential.token,
				...credential.refreshToken === void 0 ? {} : { refreshToken: credential.refreshToken },
				...expiresAtSlice(credential.token, credential.expiresAt)
			};
			return {
				state: {
					...state,
					sessions: [...state.sessions.filter((session) => session.workspaceId !== workspaceId), record],
					currentWorkspaceId: workspaceId
				},
				result: toSession(record)
			};
		});
		if (!environmentInForce) this.#actAs({
			kind: "session",
			workspaceId
		});
		const name = await this.#lookUpWorkspaceName(credential, workspaceId);
		if (name === void 0) return created;
		return this.#mutate((state) => {
			const record = state.sessions.find((session) => session.workspaceId === workspaceId);
			if (record === void 0) return { result: created };
			const named = {
				...record,
				name
			};
			return {
				state: {
					...state,
					sessions: state.sessions.map((session) => session.workspaceId === workspaceId ? named : session)
				},
				result: toSession(named)
			};
		});
	}
	async selectSession(workspaceId) {
		const environmentInForce = this.#environmentToken() !== void 0;
		const selected = await this.#mutate((state) => {
			const record = requireRecord(state, workspaceId);
			return {
				state: {
					...state,
					currentWorkspaceId: workspaceId
				},
				result: toSession(record)
			};
		});
		if (!environmentInForce) this.#actAs({
			kind: "session",
			workspaceId
		});
		return selected;
	}
	/** Idempotent (§11.8): a workspace with no session is already in the
	*  state this asks for, so the slice writes nothing and succeeds. */
	async endSession(workspaceId) {
		this.#refuseBlankEnvironmentToken();
		await this.#mutate((state) => state.sessions.some((session) => session.workspaceId === workspaceId) ? {
			state: withoutRecord(state, workspaceId),
			result: void 0
		} : { result: void 0 });
		if (this.#actingAs.kind === "session" && this.#actingAs.workspaceId === workspaceId) this.#actAs({ kind: "none" });
	}
	async endAllSessions() {
		const environmentInForce = this.#environmentToken() !== void 0;
		await this.#mutate((state) => state.sessions.length === 0 && state.currentWorkspaceId === null ? { result: void 0 } : {
			state: EMPTY_STATE,
			result: void 0
		});
		await this.#reapLegacyContextFile();
		await this.#reapOrphanedWrites();
		if (!environmentInForce) this.#actAs({ kind: "none" });
	}
	async activeCredentialStorage() {
		this.#activeStorage ??= this.#buildActiveStorage();
		return this.#activeStorage;
	}
	/** The delegated path's read: the active credential's access token,
	*  fresh on every call, never the refresh token. Null when there is
	*  no active credential to read — storage exists only once
	*  activeCredential() has returned non-null. */
	async activeAccessToken(options) {
		if (await this.activeCredential() === null) return null;
		return readActiveAccessToken(await this.activeCredentialStorage(), this.#refreshCredential, options);
	}
	/** §11.2: which storage is chosen once, when the acting-as decision
	*  resolves. Each
	*  has exactly one source of truth — the file, or process memory. */
	#buildActiveStorage() {
		const actingAs = this.#actingAs;
		if (actingAs.kind === "environment") return memoryBackedStorage({
			token: this.#requireEnvironmentToken(),
			refreshToken: void 0,
			expiresAt: void 0
		}, (fn) => this.#withRefreshLock(fn));
		if (actingAs.kind === "session") return this.#fileBackedStorage(actingAs.workspaceId);
		throw new Error("@prisma/cli: activeCredentialStorage() is only valid once activeCredential() has returned non-null");
	}
	/**
	* The file-backed storage, for a credential with a home record.
	* getTokens re-reads the file on EVERY call with no memory layer in
	* front: that is what lets this process see a pair another process
	* already rotated to, skip the exchange, and retry.
	*/
	#fileBackedStorage(workspaceId) {
		return {
			getTokens: async () => {
				const record = (await readCredentialState(this.#filePath)).sessions.find((session) => session.workspaceId === workspaceId);
				if (record === void 0) return null;
				return {
					workspaceId,
					accessToken: record.token,
					...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken },
					...record.expiresAt === void 0 ? {} : { expiresAt: new Date(record.expiresAt) }
				};
			},
			setTokens: async (tokens, expiresAt) => {
				this.#debug(`rotation write for session ${workspaceId}`);
				const claimed = credentialWorkspaceId(tokens.accessToken);
				if (claimed !== void 0 && claimed !== workspaceId) throw credentialWorkspaceMismatchError(workspaceId);
				await this.#mutate((state) => {
					const record = state.sessions.find((session) => session.workspaceId === workspaceId);
					if (record === void 0) throw credentialsRequiredError("session-ended");
					const rotated = {
						workspaceId: record.workspaceId,
						...record.name === void 0 ? {} : { name: record.name },
						token: tokens.accessToken,
						...tokens.refreshToken === void 0 ? {} : { refreshToken: tokens.refreshToken },
						...expiresAtSlice(tokens.accessToken, expiresAt ?? (record.expiresAt === void 0 ? void 0 : new Date(record.expiresAt)))
					};
					return {
						state: {
							...state,
							sessions: state.sessions.map((session) => session.workspaceId === workspaceId ? rotated : session)
						},
						result: void 0
					};
				});
			},
			clearTokens: async () => {
				this.#debug(`clearing session ${workspaceId}`);
				await this.#mutate((state) => ({
					state: withoutRecord(state, workspaceId),
					result: void 0
				}));
			},
			clearTokensIfCurrent: async (tokens) => {
				this.#debug(`clearing session ${workspaceId} if its pair still matches`);
				await this.#mutate((state) => {
					const record = state.sessions.find((session) => session.workspaceId === workspaceId);
					if (!(record !== void 0 && tokens.workspaceId === workspaceId && tokens.accessToken === record.token && tokens.refreshToken === record.refreshToken)) return { result: void 0 };
					return {
						state: withoutRecord(state, workspaceId),
						result: void 0
					};
				});
			},
			withRefreshLock: (fn) => this.#withRefreshLock(() => withRefreshFileLock(this.#filePath, this.#debug, fn))
		};
	}
	#withRefreshLock(fn) {
		const run = this.#refreshLock.then(fn, fn);
		this.#refreshLock = run.then(() => void 0, () => void 0);
		return run;
	}
	async #resolveActingAs() {
		const decided = this.#actingAs;
		if (decided.kind !== "unresolved") return decided;
		if (this.#environmentToken() !== void 0) {
			this.#debug("acting as the environment credential");
			this.#actingAs = { kind: "environment" };
			return { kind: "environment" };
		}
		const selected = resolvedMarker(await readCredentialState(this.#filePath));
		this.#debug(`acting as session ${selected ?? "(none)"}`);
		const resolved = selected === null ? { kind: "none" } : {
			kind: "session",
			workspaceId: selected
		};
		this.#actingAs = resolved;
		return resolved;
	}
	/** Changes which credential the process acts as after a mutation,
	*  discarding storage built for the previous one. */
	#actAs(next) {
		this.#actingAs = next;
		this.#activeStorage = void 0;
	}
	#environmentToken() {
		return environmentServiceToken(this.#env);
	}
	#requireEnvironmentToken() {
		const token = this.#environmentToken();
		if (token === void 0) throw credentialsRequiredError();
		return token;
	}
	/** A blank env token is an error state everywhere the environment
	*  credential would be consulted, including the mutations that no
	*  longer care whether a valid one is set. */
	/** A blank PRISMA_SERVICE_TOKEN is an error state everywhere the
	*  environment credential would be consulted, including the two
	*  mutations that do not otherwise read it. Reading is what raises;
	*  the value is deliberately unused. */
	#refuseBlankEnvironmentToken() {
		this.#environmentToken();
	}
	/** endAllSessions clears everything, including the legacy context
	*  sidecar, which survives a store that was already empty. */
	async #reapLegacyContextFile() {
		await fs.unlink(getAuthContextFilePath(this.#filePath)).catch(() => {});
	}
	/** A write that died between creating its temp file and renaming it
	*  leaves a full copy of the state, tokens and all. Someone running
	*  `auth logout` to revoke local access must not be left holding a
	*  working refresh token in an orphan. Writes take the lock and last
	*  milliseconds, so anything still here is one. */
	async #reapOrphanedWrites() {
		const directory = path.dirname(this.#filePath);
		const prefix = `${path.basename(this.#filePath)}.`;
		const entries = await fs.readdir(directory).catch(() => []);
		await Promise.all(entries.filter((entry) => entry.startsWith(prefix) && entry.endsWith(".tmp")).map((entry) => fs.unlink(path.join(directory, entry)).catch(() => {})));
	}
	async #lookUpWorkspaceName(credential, workspaceId) {
		if (this.#fetchWorkspaceName === void 0) return void 0;
		try {
			const name = await this.#fetchWorkspaceName(credential, workspaceId);
			return name?.trim() ? name.trim() : void 0;
		} catch {
			return;
		}
	}
	/** One mutation: the short lock, a fresh read, one slice, one atomic
	*  write. A slice that returns no state writes nothing. */
	async #mutate(apply) {
		return withStateLock(this.#filePath, this.#debug, async () => {
			const applied = apply(await readCredentialState(this.#filePath));
			if (applied.state !== void 0) await writeCredentialState(this.#filePath, applied.state);
			return applied.result;
		});
	}
};
function requireRecord(state, workspaceId) {
	const record = state.sessions.find((session) => session.workspaceId === workspaceId);
	if (record === void 0) throw noSessionForWorkspaceError(workspaceId);
	return record;
}
function withoutRecord(state, workspaceId) {
	return {
		...state,
		sessions: state.sessions.filter((session) => session.workspaceId !== workspaceId),
		currentWorkspaceId: state.currentWorkspaceId === workspaceId ? null : state.currentWorkspaceId
	};
}
function expiresAtSlice(token, fallback) {
	const expiresAt = claimedExpiresAt(token) ?? fallback;
	return expiresAt === void 0 ? {} : { expiresAt: expiresAt.toISOString() };
}
/** The selection the manager will admit to: one that names a stored
*  session, or none. A dangling selection never escapes. */
function resolvedMarker(state) {
	const marked = state.currentWorkspaceId;
	if (marked !== null && state.sessions.some((session) => session.workspaceId === marked)) return marked;
	return null;
}
function toSession(record) {
	return {
		workspaceId: record.workspaceId,
		workspaceName: record.name,
		expiresAt: record.expiresAt === void 0 ? void 0 : new Date(record.expiresAt)
	};
}
function storedCredential(record) {
	return {
		workspaceId: record.workspaceId,
		workspaceName: record.name,
		expiresAt: record.expiresAt === void 0 ? void 0 : new Date(record.expiresAt),
		identity: claimedIdentity(record.token),
		origin: { source: "stored" }
	};
}
/** An environment token whose claims name no workspace reports no
*  workspace id — never the empty string. */
function environmentCredential(token) {
	return {
		workspaceId: credentialWorkspaceId(token),
		workspaceName: void 0,
		expiresAt: claimedExpiresAt(token),
		identity: claimedIdentity(token),
		origin: { source: "environment" }
	};
}
//#endregion
//#region src/auth/refresh.ts
const TRAILING_SLASH = /\/$/;
const CREDENTIAL_REFRESH_TIMEOUT_MS = 1e4;
/** The dumb HTTP adapter behind the engine's delegated-credential policy. */
function makeCredentialRefresher(authBaseUrl) {
	const endpoint = `${authBaseUrl.replace(TRAILING_SLASH, "")}/token`;
	return async ({ refreshToken, signal }) => {
		signal.throwIfAborted();
		const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(CREDENTIAL_REFRESH_TIMEOUT_MS)]);
		const response = await fetch(endpoint, {
			method: "POST",
			headers: { "content-type": "application/x-www-form-urlencoded" },
			body: new URLSearchParams({
				grant_type: "refresh_token",
				refresh_token: refreshToken,
				client_id: CLIENT_ID
			}),
			signal: refreshSignal
		});
		const body = await readBody(response);
		if (response.status >= 400 && response.status < 500 && body?.error === "invalid_grant") return { kind: "invalid" };
		if (!response.ok || typeof body?.access_token !== "string" || typeof body.refresh_token !== "string" || typeof body.expires_in !== "number" || !Number.isFinite(body.expires_in) || body.expires_in < 0) throw new Error(`OAuth token refresh failed (status ${String(response.status)})`);
		return {
			kind: "success",
			accessToken: body.access_token,
			refreshToken: body.refresh_token,
			expiresAt: new Date(Date.now() + body.expires_in * 1e3)
		};
	};
}
async function readBody(response) {
	try {
		const body = await response.json();
		return typeof body === "object" && body !== null ? body : null;
	} catch {
		return null;
	}
}
//#endregion
//#region src/auth/workspace-name.ts
/** The manager's injected name lookup: a static-token client over the
*  credential just minted. The manager constructs no API client and
*  treats any failure here as "no name". */
function fetchWorkspaceName(apiBaseUrl) {
	return async (credential, workspaceId) => {
		const { data } = await createManagementApiClient({
			baseUrl: apiBaseUrl,
			token: credential.token
		}).GET("/v1/workspaces/{id}", { params: { path: { id: workspaceId } } });
		const name = data?.data?.name;
		return typeof name === "string" && name.trim().length > 0 ? name.trim() : void 0;
	};
}
//#endregion
//#region src/package-manager-runner.ts
/** How much of a manager's stderr the failure carries, per the seam's
*  contract. */
const STDERR_TAIL_BYTES = 64 * 1024;
/** A missing executable or a signal kill leaves the child with no exit
*  code of its own; the run still failed, and the engine reports it. */
const NO_EXIT_CODE = 1;
const LF = 10;
const CR = 13;
const LINE_BREAKS = [LF, CR];
/**
* The engine redacts a URL by its scheme, and the bound cuts at an
* arbitrary byte: a cut inside `https://` leaves `user:secret@host`,
* which no pattern recognises. So the tail starts after the first line
* break in what was kept. A window with no line break at all is one
* truncated line whose start cannot be trusted, and is dropped.
*/
function fromLineStart(window) {
	const breaks = LINE_BREAKS.map((byte) => window.indexOf(byte)).filter((at) => at !== -1);
	if (breaks.length === 0) return Buffer.alloc(0);
	const at = Math.min(...breaks);
	const span = window[at] === CR && window[at + 1] === LF ? 2 : 1;
	return window.subarray(at + span);
}
/** Keeps the last `limit` bytes by dropping whole chunks off the front,
*  so a manager writing megabytes in small pieces does not recopy the
*  window once per piece. */
function boundedTail(limit) {
	const chunks = [];
	let written = 0;
	let kept = 0;
	return {
		push(chunk) {
			chunks.push(chunk);
			written += chunk.length;
			kept += chunk.length;
			let first = chunks[0];
			while (first !== void 0 && kept - first.length >= limit) {
				chunks.shift();
				kept -= first.length;
				first = chunks[0];
			}
		},
		/** Every byte the child wrote, whether or not it was kept. */
		get bytes() {
			return written;
		},
		text() {
			const all = Buffer.concat(chunks);
			if (written <= limit) return all.toString("utf8");
			return fromLineStart(all.subarray(all.length - limit)).toString("utf8");
		}
	};
}
/** Decodes across chunk boundaries, so a multi-byte character split by
*  the pipe is not delivered as two replacement characters. Returns the
*  final decode: a child that died part-way through a character leaves
*  bytes the decoder is still holding, and without it they are lost. */
function forward(source, emit, keep) {
	const decoder = new TextDecoder();
	const emitDecoded = (text) => {
		if (text !== "") emit(text);
	};
	source.on("data", (chunk) => {
		keep?.(chunk);
		emitDecoded(decoder.decode(chunk, { stream: true }));
	});
	return () => {
		emitDecoded(decoder.decode());
	};
}
/**
* Spawns the package manager the engine composed. Its output streams
* out as the child writes it; its stderr also comes back bounded for
* the caller's failure predicate. Every failure — a non-zero exit, an
* executable that is not installed, an abort — resolves.
*/
const runPackageManager = async ({ file, args, cwd, signal, onOutput }) => {
	const tail = boundedTail(STDERR_TAIL_BYTES);
	const subprocess = execa(file, [...args], {
		cwd,
		cancelSignal: signal,
		stdin: "ignore",
		buffer: false,
		reject: false
	});
	const flushStdout = forward(subprocess.stdout, (text) => onOutput("data", text));
	const flushStderr = forward(subprocess.stderr, (text) => onOutput("diagnostic", text), tail.push);
	const result = await subprocess;
	flushStdout();
	flushStderr();
	return {
		exitCode: result.exitCode ?? NO_EXIT_CODE,
		stderr: tail.bytes === 0 ? result.shortMessage ?? "" : tail.text()
	};
};
//#endregion
//#region src/spawn.ts
/** How long after the child exits the relay keeps reading its pipes. A
*  grandchild that inherited them can hold EOF back forever; settlement
*  must not wait on it, so the pipes are destroyed after this grace. */
const POST_EXIT_DRAIN_GRACE_MS = 5e3;
/**
* The engine's spawn seam, adapted to node:child_process. Human mode
* inherits stdio; structured mode pipes both child output streams to
* diagnostics. Neither mode detaches or opens a new console, so the child
* stays in this process's group (POSIX) or console (Windows).
*
* The child's own status settles the run: `ended` resolves from the
* process `exit` event, waits for the diagnostic relay only up to the
* drain grace, and never rejects for a relay failure — rejection is
* reserved for a child that could not be launched at all.
*/
function makeSpawnChild(diagnostics, options) {
	const drainGraceMs = options?.drainGraceMs ?? POST_EXIT_DRAIN_GRACE_MS;
	return (request) => {
		const structured = request.output === "diagnostic";
		const child = spawn(request.command, [...request.args], {
			cwd: request.cwd,
			env: request.env,
			stdio: structured ? [
				"inherit",
				"pipe",
				"pipe"
			] : "inherit"
		});
		const processEnded = new Promise((resolve, reject) => {
			child.on("error", reject);
			child.on("exit", (exitCode, signal) => {
				resolve({
					exitCode,
					signal
				});
			});
		});
		if (!structured) return {
			ended: processEnded,
			kill: (signal) => {
				child.kill(signal);
			}
		};
		const forwarding = forwardStructuredOutput(child.stdout, child.stderr, diagnostics);
		return {
			ended: processEnded.then(async (result) => {
				const drainDeadline = setTimeout(() => {
					child.stdout?.destroy();
					child.stderr?.destroy();
				}, drainGraceMs);
				await forwarding;
				clearTimeout(drainDeadline);
				return result;
			}),
			kill: (signal) => {
				child.kill(signal);
			}
		};
	};
}
/** Best-effort relay: a forwarding failure never rejects, so the child's
*  real status still settles the run when the diagnostic sink dies. */
function forwardStructuredOutput(stdout, stderr, diagnostics) {
	const sources = [stdout, stderr].filter((source) => source !== null);
	return Promise.all(sources.map((source) => forwardOutput(source, diagnostics))).then(() => void 0, () => void 0);
}
/** Decode each child stream continuously and stop reading while the
*  diagnostic destination applies backpressure. A destination that
*  errors or closes instead of draining fails the relay rather than
*  stalling it. */
function forwardOutput(source, diagnostics) {
	let pendingDone;
	let pendingDrain;
	let failure;
	const fail = (cause) => {
		failure ??= cause;
		const done = pendingDone;
		pendingDone = void 0;
		done?.(cause);
	};
	const onSinkError = (cause) => {
		fail(toError(cause));
	};
	const onSinkClose = () => {
		fail(/* @__PURE__ */ new Error("the diagnostic stream closed during child output"));
	};
	diagnostics.once?.("error", onSinkError);
	diagnostics.once?.("close", onSinkClose);
	const destination = new Writable({
		decodeStrings: false,
		write: (text, _encoding, done) => {
			if (failure !== void 0) {
				done(failure);
				return;
			}
			try {
				if (diagnostics.write(text) === false && diagnostics.once !== void 0) {
					pendingDone = done;
					const onDrain = () => {
						pendingDrain = void 0;
						if (pendingDone !== done) return;
						pendingDone = void 0;
						done();
					};
					pendingDrain = onDrain;
					diagnostics.once("drain", onDrain);
				} else done();
			} catch (cause) {
				done(toError(cause));
			}
		}
	});
	return pipeline(source.setEncoding("utf8"), destination).finally(() => {
		diagnostics.off?.("error", onSinkError);
		diagnostics.off?.("close", onSinkClose);
		if (pendingDrain !== void 0) diagnostics.off?.("drain", pendingDrain);
	});
}
function toError(cause) {
	return cause instanceof Error ? cause : new Error(String(cause));
}
//#endregion
//#region src/runtime.ts
/** Dumb wiring: forwards process signals to the engine's subscribers.
*  The signal policy (first aborts, second force-exits) is the engine's. */
function makeOnSignal(proc) {
	return (cb) => {
		const onInt = () => cb("SIGINT");
		const onTerm = () => cb("SIGTERM");
		proc.on("SIGINT", onInt);
		proc.on("SIGTERM", onTerm);
		return () => {
			proc.off("SIGINT", onInt);
			proc.off("SIGTERM", onTerm);
		};
	};
}
/** Bun and Deno both announce themselves in process.versions; nothing
*  else does, so an absent marker means Node. */
function describeHost(proc) {
	const name = ["bun", "deno"].find((candidate) => proc.versions[candidate]) ?? "node";
	return {
		runtime: {
			name,
			version: proc.versions[name] ?? proc.version
		},
		platform: proc.platform,
		arch: proc.arch
	};
}
/**
* Path to the compiled sender entry. In the workspace (dev runs and the
* monorepo dist) the package specifier resolves to
* `packages/cli-telemetry/dist/sender.js`; in the published cli the
* telemetry package is bundled away, so the fallback resolves the copy
* tsdown emits next to the CLI entry (`dist/sender.js`).
*/
function resolveSenderPath() {
	try {
		return fileURLToPath(import.meta.resolve("@repo/cli-telemetry/sender"));
	} catch {
		return fileURLToPath(new URL("./sender.js", import.meta.url));
	}
}
/** PRISMA_COMPUTE_AUTH_FILE still names the credentials file, but
*  PRISMA_AUTH_FILE is the supported name. Warned once per process. */
function warnOnDeprecatedStateFileEnvVar(proc) {
	if (!resolveStateFilePath(proc.env).fromDeprecatedEnvVar) return;
	proc.stderr.write(`${DEPRECATED_STATE_FILE_ENV_VAR} is deprecated; use ${STATE_FILE_ENV_VAR} instead.\n`);
}
/** Whether fd 1 and fd 2 are the same open device. Distinguishes one
*  terminal (mirror suppressed) from a harness that allocated separate
*  PTYs for the two streams (mirror kept). Undefined when the fds
*  cannot be inspected — the engine then assumes one terminal. */
function outputStreamsShareDevice() {
	try {
		const out = fstatSync(1);
		const err = fstatSync(2);
		return out.dev === err.dev && out.ino === err.ino && out.rdev === err.rdev;
	} catch {
		return;
	}
}
async function assembleRuntime(proc) {
	const stdin = {
		setRawMode: proc.stdin.isTTY === true && proc.stdin.setRawMode !== void 0 ? (enabled) => {
			proc.stdin.setRawMode?.(enabled);
		} : void 0,
		[Symbol.asyncIterator]: () => proc.stdin[Symbol.asyncIterator]()
	};
	warnOnDeprecatedStateFileEnvVar(proc);
	const apiBaseUrl = getApiBaseUrl(proc.env);
	const authBaseUrl = getAuthBaseUrl(proc.env);
	return {
		stdout: { write: (text) => {
			proc.stdout.write(text);
		} },
		stderr: {
			write: (text) => {
				proc.stderr.write(text);
			},
			get columns() {
				return proc.stderr.columns;
			}
		},
		stdin,
		cwd: proc.cwd(),
		env: proc.env,
		isTty: {
			stdin: proc.stdin.isTTY === true,
			stdout: proc.stdout.isTTY === true,
			stderr: proc.stderr.isTTY === true
		},
		outputStreamsShareDevice: outputStreamsShareDevice(),
		exit: (code) => proc.exit(code),
		onSignal: makeOnSignal(proc),
		loadConfig: (configPath) => loadConfig(proc.cwd(), configPath, getCliVersion()),
		credentialManager: new FileCredentialManager({
			env: proc.env,
			fetchWorkspaceName: fetchWorkspaceName(apiBaseUrl),
			refreshCredential: makeCredentialRefresher(authBaseUrl)
		}),
		managementApiClientConfig: {
			clientId: CLIENT_ID,
			redirectUri: DEFAULT_REDIRECT_URI,
			apiBaseUrl,
			authBaseUrl
		},
		spawn: makeSpawnChild(proc.stderr),
		/** The engine has already decided and composed; the bin only forks
		*  the detached sender and hands the payload over. Every failure is
		*  swallowed inside runTelemetry. */
		spawnTelemetry: (payload) => {
			runTelemetry({
				payload,
				senderPath: resolveSenderPath()
			});
		},
		openUrl: async (url) => {
			await open(url);
		},
		managementApi: { baseUrl: apiBaseUrl },
		runPackageManager,
		host: describeHost(proc)
	};
}
async function maybeWriteSkillsStaleNotice(runtime) {
	if (isSuppressedByInvocation(runtime)) return;
	try {
		if (await readSkillsCheckDisabled(runtime.cwd)) return;
		const status = await readSkillsStatus(runtime.cwd, {
			orphans: false,
			checkDisabled: false
		});
		if (status.upToDate) return;
		const config = await readProjectSkillsConfig(runtime.cwd, configPathFromArgv(runtime.argv));
		if (config !== null && !config.check) return;
		const agents = config?.agents ?? DEFAULT_AGENTS;
		if (agents.length === 0) return;
		const notice = renderStaleNotice(status, agentSkillDirs(agents));
		if (notice !== null) runtime.stderr.write(notice);
	} catch {
		return;
	}
}
/** The first skill with a stale or never-synced copy in one of the
*  configured directories — what the check names in its one line. */
function firstOutdatedSkillIn(status, dirs) {
	return status.skills.find((skill) => skill.targets.some((target) => dirs.includes(target.dir) && (target.state === "stale" || target.state === "absent"))) ?? null;
}
function renderStaleNotice(status, dirs) {
	const outdated = firstOutdatedSkillIn(status, dirs);
	if (outdated === null) return null;
	const synced = outdated.targets.find((target) => dirs.includes(target.dir) && target.state === "stale")?.syncedVersion;
	return `Prisma agent skills are out of date (installed ${outdated.library} ${outdated.version}, synced ${synced ?? "none"}). Run: ${getCliName()} skills sync\n`;
}
/** The shared flags that take a separate value, so the word after them
*  is that value rather than the command being invoked. */
const FLAGS_TAKING_A_VALUE = new Set([
	"--format",
	"--log-level",
	"--config",
	"--confirm"
]);
/** The first word of the invocation — the group, or the command when it
*  is mounted top-level — skipping the shared flags that may precede it. */
function invokedGroup(argv) {
	for (let index = 0; index < argv.length; index += 1) {
		const token = argv[index];
		if (!token.startsWith("-")) return token;
		if (FLAGS_TAKING_A_VALUE.has(token)) index += 1;
	}
}
/** Tokens before a bare `--`; everything after it is positional data,
*  never a flag. */
function flagTokens(argv) {
	const end = argv.indexOf("--");
	return end === -1 ? argv : argv.slice(0, end);
}
/** The off switches that cost nothing to read. */
function isSuppressedByInvocation(runtime) {
	const env = runtime.env;
	if (env["PRISMA_SKILLS_CHECK"] === "0") return true;
	if (detectCI(env)) return true;
	const argv = flagTokens(runtime.argv);
	const group = invokedGroup(argv);
	if (group === "skills" || group === "init") return true;
	if (argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q")) return true;
	if (argv.includes("--version")) return true;
	return argv.some((token, index) => token === "--format=json" || token === "--format" && argv[index + 1] === "json");
}
/** The file an explicit --config names, so the check reads the same
*  config the command did. Discovery is otherwise cwd-only. */
function configPathFromArgv(argv) {
	const tokens = flagTokens(argv);
	for (let index = 0; index < tokens.length; index += 1) {
		const token = tokens[index];
		if (token === "--config") return tokens[index + 1];
		if (token.startsWith("--config=")) return token.slice(9);
	}
}
//#endregion
//#region src/update-check.ts
const UPDATE_CHECK_FILE_NAME = "update-check.json";
const FALLBACK_INSTALL_DOCS_URL = CLI_DOCS_URL;
const NOTIFICATION_INTERVAL_MS = 1440 * 60 * 1e3;
const REGISTRY_URL = "https://registry.npmjs.org/@prisma%2fcli";
const REGISTRY_TIMEOUT_MS = 3e3;
var UpdateCheckStore = class {
	filePath;
	constructor(cacheDir) {
		this.filePath = path.join(cacheDir, UPDATE_CHECK_FILE_NAME);
	}
	async read() {
		try {
			return JSON.parse(await readFile(this.filePath, "utf8"));
		} catch (error) {
			if (isUnreadableCacheError(error)) return null;
			throw error;
		}
	}
	async write(state) {
		const dir = path.dirname(this.filePath);
		const tempPath = path.join(dir, `${UPDATE_CHECK_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`);
		await mkdir(dir, { recursive: true });
		await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
		await rename(tempPath, this.filePath);
	}
};
async function maybeWriteCachedUpdateNotification(runtime) {
	if (!canRunUpdateCheck(runtime)) return;
	try {
		const cacheDir = resolveUpdateCheckCacheDir(runtime);
		const store = new UpdateCheckStore(cacheDir);
		const state = await store.read();
		const latestVersion = state?.latestVersion;
		if (latestVersion && isInstalledVersionStale(getCliVersion(), latestVersion) && shouldNotify(state)) {
			runtime.stderr.write(renderUpdateNotification(latestVersion, selectUpdateInstruction(runtime.env)));
			await store.write({
				...state,
				packageName: "@prisma/cli",
				installedVersion: getCliVersion(),
				notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
			});
		}
		await scheduleRemoteDiscovery(runtime, store, state, cacheDir);
	} catch {
		return;
	}
}
async function runUpdateDiscovery(options) {
	try {
		const latestVersion = await fetchLatestVersion(options.registryUrl ?? REGISTRY_URL, options.fetchImpl ?? fetch);
		if (!latestVersion) return;
		const store = new UpdateCheckStore(options.cacheDir);
		const previousState = await store.read();
		await store.write({
			...previousState,
			packageName: "@prisma/cli",
			installedVersion: options.installedVersion,
			latestVersion,
			checkedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString()
		});
	} catch {
		return;
	}
}
function isUnreadableCacheError(error) {
	const code = error.code;
	return code === "ENOENT" || code === "EACCES" || code === "EPERM" || error instanceof SyntaxError;
}
async function runUpdateDiscoveryWorker(env = process.env) {
	const cacheDir = env.PRISMA_CLI_UPDATE_CHECK_DIR;
	const installedVersion = env.PRISMA_CLI_UPDATE_CHECK_INSTALLED_VERSION;
	if (!cacheDir || !installedVersion) return;
	await runUpdateDiscovery({
		cacheDir,
		installedVersion,
		registryUrl: env.PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL
	});
}
function canRunUpdateCheck(runtime) {
	if (runtime.env.NO_UPDATE_NOTIFIER !== void 0) return false;
	if (isTestRuntime(runtime.env) && runtime.env.PRISMA_CLI_TEST_ENABLE_UPDATE_CHECK !== "1") return false;
	if (runtime.env.CI || runtime.env.GITHUB_ACTIONS) return false;
	if (!runtime.stderr.isTTY) return false;
	if (runtime.argv.includes("--json") || runtime.argv.includes("--quiet") || runtime.argv.includes("-q")) return false;
	if (runtime.argv.includes("--version")) return false;
	return true;
}
function shouldNotify(state) {
	return !state.notifiedAt || isAtLeastIntervalAgo(state.notifiedAt);
}
async function scheduleRemoteDiscovery(runtime, store, state, cacheDir) {
	if (state?.checkedAt && !isAtLeastIntervalAgo(state.checkedAt)) return;
	const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
	await store.write({
		...state,
		packageName: "@prisma/cli",
		installedVersion: getCliVersion(),
		checkedAt
	});
	if (isTestRuntime(runtime.env)) return;
	const entrypoint = process.argv[1];
	if (!entrypoint) return;
	spawn(process.execPath, [entrypoint], {
		detached: true,
		stdio: "ignore",
		env: {
			...process.env,
			PRISMA_CLI_RUN_UPDATE_CHECK_WORKER: "1",
			PRISMA_CLI_UPDATE_CHECK_DIR: cacheDir,
			PRISMA_CLI_UPDATE_CHECK_INSTALLED_VERSION: getCliVersion(),
			PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL: runtime.env.PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL ?? REGISTRY_URL
		}
	}).unref();
}
function selectUpdateInstruction(env, processArgv = process.argv) {
	const entrypoint = (processArgv[1] ?? "").replace(/\\/g, "/").toLowerCase();
	const userAgent = env.npm_config_user_agent?.toLowerCase() ?? "";
	if (isEphemeralInvocation(entrypoint, env.npm_lifecycle_event?.toLowerCase() ?? "")) return docsInstruction();
	if (entrypoint.includes("/node_modules/.bin/")) {
		if (userAgent.startsWith("pnpm")) return commandInstruction("pnpm add -D @prisma/cli@latest");
		if (userAgent.startsWith("bun")) return commandInstruction("bun add -d @prisma/cli@latest");
		if (userAgent.startsWith("npm")) return commandInstruction("npm install --save-dev @prisma/cli@latest");
	}
	if (env.npm_config_global === "true" || isLikelyGlobalNpmEntrypoint(entrypoint)) return commandInstruction("npm install --global @prisma/cli@latest");
	return docsInstruction();
}
function renderUpdateNotification(latestVersion, instruction) {
	return [
		`Update available: ${getCliName()} ${getCliVersion()} -> ${latestVersion}`,
		renderUpdateInstruction(instruction),
		""
	].join("\n");
}
function renderUpdateInstruction(instruction) {
	if (instruction.type === "command") return `Run ${instruction.value} to update.`;
	return `See ${instruction.value} for update instructions.`;
}
function isEphemeralInvocation(entrypoint, lifecycle) {
	return lifecycle === "npx" || lifecycle === "pnpx" || entrypoint.includes("/_npx/") || entrypoint.includes("/.bun/");
}
function isLikelyGlobalNpmEntrypoint(entrypoint) {
	return /\/npm\/prisma-cli(\.cmd|\.exe)?$/.test(entrypoint) || /\/npm-global\/bin\/prisma-cli$/.test(entrypoint);
}
function commandInstruction(value) {
	return {
		type: "command",
		value
	};
}
function docsInstruction() {
	return {
		type: "docs",
		value: FALLBACK_INSTALL_DOCS_URL
	};
}
function resolveUpdateCheckCacheDir(runtime) {
	const configured = runtime.env.PRISMA_CLI_UPDATE_CHECK_DIR;
	if (configured?.trim()) return path.resolve(configured);
	if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Caches", "prisma-cli");
	if (process.platform === "win32") {
		const localAppData = runtime.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
		return path.join(localAppData, "prisma-cli", "cache");
	}
	const xdgCacheHome = runtime.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
	return path.join(xdgCacheHome, "prisma-cli");
}
function isTestRuntime(env) {
	return env.VITEST !== void 0 || env.NODE_ENV === "test";
}
function isAtLeastIntervalAgo(value) {
	const timestamp = Date.parse(value);
	return Number.isNaN(timestamp) || Date.now() - timestamp >= NOTIFICATION_INTERVAL_MS;
}
function isInstalledVersionStale(installedVersion, latestVersion) {
	const order = compareVersionStrings(installedVersion, latestVersion);
	return order !== null && order < 0;
}
async function fetchLatestVersion(registryUrl, fetchImpl) {
	const controller = new AbortController();
	const timeout = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
	try {
		const response = await fetchImpl(registryUrl, {
			signal: controller.signal,
			headers: { accept: "application/json" }
		});
		if (!response.ok) return null;
		const latest = (await response.json())["dist-tags"]?.latest;
		return typeof latest === "string" ? latest : null;
	} finally {
		clearTimeout(timeout);
	}
}
//#endregion
//#region src/main.ts
/** The bin body: build, run, return the exit code. Signal policy lives
*  in the engine; the bin only forwards signals and provides
*  process.exit. A construction error prints one line to stderr and
*  exits 1. Telemetry is the engine's: it decides and composes at
*  command start, and the runtime's spawnTelemetry seam forks the
*  sender. */
async function main(proc, buildCliForRun = buildCli) {
	let cli;
	try {
		cli = buildCliForRun();
	} catch (cause) {
		proc.stderr.write(`${cause instanceof Error ? cause.message : String(cause)}\n`);
		return 1;
	}
	await maybeWriteCachedUpdateNotification({
		env: proc.env,
		argv: proc.argv.slice(2),
		stderr: proc.stderr
	});
	const runtime = await assembleRuntime(proc);
	const exitCode = await cli.run(proc.argv.slice(2), runtime);
	await maybeWriteSkillsStaleNotice({
		env: proc.env,
		argv: proc.argv.slice(2),
		cwd: proc.cwd(),
		stderr: proc.stderr
	});
	return exitCode;
}
//#endregion
//#region src/bin.ts
if (process$1.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1") {
	await runUpdateDiscoveryWorker();
	process$1.exitCode = 0;
} else process$1.exitCode = await main(process$1);
//#endregion
export {};