UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

1,481 lines • 61.3 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const require_auth_types_gen = require('../auth-types-gen.cjs');
const require_http_client = require('../http-client.cjs');
const require_fastkv = require('../fastkv.cjs');
const require_config = require('../config.cjs');
const require_serialize = require('../descriptor/serialize.cjs');
const require_workspace_catalog = require('../workspace-catalog.cjs');
const require_manifest_normalizer = require('../internal/manifest-normalizer.cjs');
const require_save_config = require('../utils/save-config.cjs');
const require_snapshot_hash = require('../utils/snapshot-hash.cjs');
const require_snapshot = require('./snapshot.cjs');
const require_helpers = require('./utils/helpers.cjs');
const require_docker = require('../infra/docker.cjs');
const require_resolution_session = require('../resolution/session.cjs');
let node_fs = require("node:fs");
let node_path = require("node:path");
let execa = require("execa");
let node_os = require("node:os");
let node_stream_promises = require("node:stream/promises");
let glob = require("glob");
let tar = require("tar");
let yaml = require("yaml");

//#region src/cli/init.ts
const PNPM_PACKAGE_MANAGER = "pnpm@12.10.1";
/**
* Transitive-singleton overrides every workspace in the fleet must agree on
* (ticket 05's deviation log: isolated resolution splits better-auth
* peer-context identities across the plugin fleet). Values derive from the
* catalog so sync keeps them current; keys absent from the catalog are left
* unpinned.
*/
const CHILD_OVERRIDE_KEYS = [
	"@better-fetch/fetch",
	"@rspack/core",
	"better-auth",
	"react",
	"react-dom"
];
/**
* The child's workspace manifest: pnpm-workspace.yaml is canonical (v2, ADR
* 0026) — packages globs synthesized from the child's override sections, the
* catalog merged over any local entry, plus the fleet-wide linker posture and
* singleton overrides. Re-running (sync/upgrade) merges instead of clobbering.
*/
function writeChildWorkspaceYaml(projectDir, next) {
	const yamlPath = (0, node_path.join)(projectDir, "pnpm-workspace.yaml");
	let doc = {};
	if ((0, node_fs.existsSync)(yamlPath)) try {
		const parsed = (0, yaml.parse)((0, node_fs.readFileSync)(yamlPath, "utf-8"));
		if (parsed && typeof parsed === "object") doc = parsed;
	} catch {
		doc = {};
	}
	let changed = !(0, node_fs.existsSync)(yamlPath);
	const packages = [.../* @__PURE__ */ new Set([...doc.packages ?? [], ...next.packages])].sort();
	if (JSON.stringify(packages) !== JSON.stringify(doc.packages)) {
		doc.packages = packages;
		changed = true;
	}
	const catalog = { ...doc.catalog };
	for (const [name, version] of Object.entries(next.catalog)) if (catalog[name] !== version) {
		catalog[name] = version;
		changed = true;
	}
	if (Object.keys(catalog).length > 0 && Object.keys(catalog).length !== Object.keys(doc.catalog ?? {}).length) changed = true;
	if (changed || !doc.catalog) doc.catalog = Object.fromEntries(Object.entries(catalog).sort(([a], [b]) => a.localeCompare(b)));
	if (doc.nodeLinker !== "hoisted") {
		doc.nodeLinker = "hoisted";
		changed = true;
	}
	if (doc.linkWorkspacePackages !== true) {
		doc.linkWorkspacePackages = true;
		changed = true;
	}
	if (doc.ignoreScripts !== true) {
		doc.ignoreScripts = true;
		changed = true;
	}
	const overrides = { ...doc.overrides };
	for (const key of CHILD_OVERRIDE_KEYS) if (catalog[key] && overrides[key] !== catalog[key]) {
		overrides[key] = catalog[key];
		changed = true;
	}
	if (Object.keys(overrides).length > 0) doc.overrides = Object.fromEntries(Object.entries(overrides).sort(([a], [b]) => a.localeCompare(b)));
	if (changed) {
		(0, node_fs.writeFileSync)(yamlPath, `${(0, yaml.stringify)(doc, { lineWidth: 0 }).trimEnd()}\n`);
		return true;
	}
	return false;
}
/**
* Init must never reuse a lockfile left in the target (copied from a parent
* checkout or left by a previous run) — stale framework resolutions would
* survive the fresh install. Bun's bun.lock is deleted on sight (ADR 0026's
* silent-revert footgun); the child's own pnpm-lock.yaml is re-resolved.
*/
function removeStrayLockfiles(projectDir) {
	for (const lockfile of ["pnpm-lock.yaml", "bun.lock"]) {
		const lockfilePath = (0, node_path.join)(projectDir, lockfile);
		if ((0, node_fs.existsSync)(lockfilePath)) (0, node_fs.rmSync)(lockfilePath, { force: true });
	}
}
const INIT_ROOT_PATTERNS = [
	"bos.config.json",
	"package.json",
	".env.example",
	".gitignore",
	"biome.json",
	"railway.json",
	"railway.toml",
	"AGENTS.md",
	".agents/skills/**",
	"skills-lock.json",
	"docs/agents/**",
	".changeset/config.json",
	".changeset/README.md",
	"README.md",
	"CONTRIBUTING.md",
	".github/templates/**"
];
const OVERRIDE_WORKSPACE_MAP = {
	ui: ["ui"],
	api: ["api"],
	host: ["host"],
	plugins: []
};
function readWorkspaceCatalog(sourceDir) {
	return require_workspace_catalog.readWorkspaceCatalog(sourceDir);
}
async function resolveCatalogChainSource(opts) {
	const entry = opts.sourceDir ? require_config.findConfigPath((0, node_path.resolve)(opts.sourceDir)) ?? (0, node_path.join)((0, node_path.resolve)(opts.sourceDir), "bos.config.json") : `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
	const catalogs = [];
	const cleanups = [];
	const resolvedRefs = [];
	let repository;
	try {
		await require_resolution_session.walkExtendsChain(entry, {
			env: "production",
			collectCatalogs: true,
			registerCleanup: (fn) => cleanups.push(fn),
			visit: async (link) => {
				resolvedRefs.unshift(link.ref.startsWith("bos://") ? link.ref : (0, node_path.join)(link.baseDir, (0, node_path.basename)(link.ref)));
				catalogs.push(readWorkspaceCatalog(link.sourceDir ?? link.baseDir));
				const repositoryValue = link.config.repository;
				if (typeof repositoryValue === "string" && repository === void 0) repository = repositoryValue;
			}
		});
	} finally {
		for (const cleanup of [...cleanups].reverse()) await cleanup();
	}
	return {
		catalog: Object.assign({}, ...catalogs),
		repository,
		extendsChain: opts.sourceDir ? [`bos://${opts.extendsAccount}/${opts.extendsGateway}`, ...resolvedRefs.slice(1)] : resolvedRefs
	};
}
async function resolveSourceDir(opts) {
	if (opts.source) {
		const sourceDir = (0, node_path.resolve)(opts.source);
		const configPath = require_config.findConfigPath(sourceDir);
		if (!configPath) throw new Error(`No authored config (bos.app.ts or bos.config.json) found in source directory: ${sourceDir}`);
		return {
			sourceDir,
			parentConfig: require_config.isAppDescriptorPath(configPath) ? await require_config.loadAppDescriptorConfig(configPath) : JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8")),
			cleanup: async () => {}
		};
	}
	const parentConfig = await fetchParentConfig(opts.extendsAccount, opts.extendsGateway);
	if (parentConfig.repository) {
		const { dir: sourceDir, cleanup } = await downloadTarball(parentConfig.repository);
		return {
			sourceDir,
			parentConfig,
			cleanup
		};
	}
	const chainResult = await resolveRepositoryViaExtendsChain(opts.extendsAccount, opts.extendsGateway);
	if (chainResult?.repository) {
		const { dir: sourceDir, cleanup } = await downloadTarball(chainResult.repository);
		return {
			sourceDir,
			parentConfig: chainResult.config,
			cleanup
		};
	}
	return {
		sourceDir: "",
		parentConfig,
		cleanup: async () => {}
	};
}
function buildInitPatterns(overrides, plugins, pluginDirMap) {
	const has = (section) => overrides.includes(section);
	const patterns = [...INIT_ROOT_PATTERNS];
	if (has("ui")) patterns.push("ui/**");
	if (has("api")) patterns.push(API_TEMPLATE_PATTERN);
	if (has("api") || has("host")) patterns.push(COMPOSE_TEMPLATE_PATTERN);
	if (has("host")) patterns.push("host/**");
	if (has("plugins")) for (const plugin of plugins ?? []) {
		const dirName = pluginDirMap?.[plugin] ?? plugin;
		patterns.push(`plugins/${dirName}/**`);
	}
	return patterns;
}
/** api-override children get the slim generic shell, never the parent's domain API. */
const API_TEMPLATE_PATTERN = ".github/templates/api/**";
/** Child-sized compose (api + api-test databases) for local compute overrides. */
const COMPOSE_TEMPLATE_PATTERN = ".github/templates/docker-compose.yml";
function isApiTemplatePath(filePath) {
	return filePath.startsWith(".github/templates/api/");
}
function isComposeTemplatePath(filePath) {
	return filePath === COMPOSE_TEMPLATE_PATTERN;
}
function buildPluginRouteExclusions(parentConfig, selectedPlugins) {
	if (!parentConfig?.plugins) return [];
	const selected = new Set(selectedPlugins);
	const claimedBySelected = /* @__PURE__ */ new Set();
	const claimedByUnselected = [];
	for (const [pluginKey, entry] of Object.entries(parentConfig.plugins)) {
		const routes = extractPluginRoutes(entry);
		if (!routes) continue;
		if (selected.has(pluginKey)) for (const route of routes) claimedBySelected.add(route);
		else for (const route of routes) claimedByUnselected.push(route);
	}
	return claimedByUnselected.filter((route) => !claimedBySelected.has(route));
}
const STARTER_PRODUCT_EXCLUSIONS = [
	"_public/explore.tsx",
	"_public/stake.tsx",
	"_public/n/**",
	"_public/$accountId.tsx",
	"_public/$accountId/**",
	"_public/activity/**",
	"_public/-stake-*",
	"_authenticated/_dashboard/dashboard/node/**",
	"_authenticated/_dashboard/nodes/**",
	"_authenticated/_dashboard/tenant.*",
	"_authenticated/_dashboard/discover.tsx",
	"_authenticated/_dashboard/apply.tsx",
	"_authenticated/_dashboard/prototype-staking-poc.tsx",
	"_authenticated/onboarding/**",
	"_admin/_dashboard/_dashboard/admin/nodes/**",
	"_admin/_dashboard/_dashboard/admin/proposals/**",
	"_admin/_dashboard/_dashboard/admin/tenants/**",
	"_admin/_dashboard/_dashboard/admin/relayer.tsx",
	"_admin/_dashboard/_dashboard/admin/organizations.tsx"
];
const STARTER_SIMPLE_EXCLUSIONS = [
	"_authenticated.tsx",
	"_authenticated/**",
	"_admin.tsx",
	"_admin/**"
];
/**
* Route-file globs (relative to the child's `ui/src/routes/`) that a starter
* of the given level must not receive. Parent `starter` config can add
* exclusions (`exclude`, `levels[level].exclude`) or reclaim routes for a
* level (`levels[level].include`). Entries are prefixed with
* `ui/src/routes/` so they compose with `copyFilteredFiles`'s ignore list.
*/
function buildStarterRouteExclusions(level, parentConfig) {
	const excluded = new Set(STARTER_PRODUCT_EXCLUSIONS);
	if (level === "simple") for (const entry of STARTER_SIMPLE_EXCLUSIONS) excluded.add(entry);
	const starter = parentConfig?.starter;
	if (starter) {
		for (const entry of starter.exclude ?? []) excluded.add(entry);
		const levelConfig = starter.levels?.[level];
		if (levelConfig) {
			for (const entry of levelConfig.exclude ?? []) excluded.add(entry);
			for (const entry of levelConfig.include ?? []) excluded.delete(entry);
		}
	}
	return [...excluded].map((entry) => `ui/src/routes/${entry}`);
}
function extractPluginRoutes(entry) {
	if (typeof entry !== "object" || entry === null) return void 0;
	const routes = entry.routes;
	if (!Array.isArray(routes)) return void 0;
	return routes.filter((r) => typeof r === "string");
}
function sourcePathToDestinationPath(filePath) {
	if (isApiTemplatePath(filePath)) return filePath.replace(/^\.github\/templates\/api\//, "api/");
	if (isComposeTemplatePath(filePath)) return "docker-compose.yml";
	return filePath.startsWith(".github/templates/") ? filePath.replace(/^\.github\/templates\//, ".github/") : filePath;
}
async function fetchParentConfig(extendsAccount, extendsGateway) {
	const bosUrl = `bos://${extendsAccount}/${extendsGateway}`;
	return require_fastkv.fetchBosConfigFromFastKv(bosUrl);
}
async function resolveRepositoryViaExtendsChain(extendsAccount, extendsGateway, visited = /* @__PURE__ */ new Set()) {
	const key = `bos://${extendsAccount}/${extendsGateway}`;
	if (visited.has(key)) return null;
	visited.add(key);
	try {
		const config = await fetchParentConfig(extendsAccount, extendsGateway);
		if (config.repository) return {
			repository: config.repository,
			config
		};
		const extendsRef = require_helpers.getExtendsRef(config);
		if (extendsRef) {
			const normalized = extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`;
			const parsed = require_helpers.parseBosRef(normalized);
			if (parsed) {
				const result = await resolveRepositoryViaExtendsChain(parsed.account, parsed.gateway, visited);
				if (result) return result;
			}
		}
		return null;
	} catch {
		return null;
	}
}
async function detectGitRemoteUrl(directory) {
	try {
		const { stdout } = await (0, execa.execa)("git", [
			"remote",
			"get-url",
			"origin"
		], {
			cwd: directory,
			stdio: "pipe"
		});
		const url = stdout.trim();
		if (!url) return void 0;
		return normalizeGitUrl(url);
	} catch {
		return;
	}
}
function normalizeGitUrl(url) {
	const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
	if (sshMatch) return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
	const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
	if (httpsMatch) return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
	return url.endsWith(".git") ? url.slice(0, -4) : url;
}
async function downloadTarball(repoUrl) {
	const parsed = parseGitHubUrl(repoUrl);
	if (!parsed) throw new Error(`Cannot parse repository URL: ${repoUrl}`);
	const { owner, repo } = parsed;
	let response = null;
	for (const branch of ["main", "master"]) {
		const candidate = await require_http_client.fetchResponse(`https://api.github.com/repos/${owner}/${repo}/tarball/${branch}`, {
			headers: { "User-Agent": "everything-dev" },
			redirect: "follow",
			timeout: "60 seconds"
		});
		if (candidate.ok) {
			response = candidate;
			break;
		}
		if (candidate.status !== 404) throw new Error(`GitHub tarball download failed: ${candidate.status} ${candidate.statusText}`);
	}
	if (!response) throw new Error(`GitHub tarball download failed for ${repoUrl}: tried main and master`);
	if (!response.body) throw new Error("GitHub tarball download returned empty body");
	const tmpDir = mkTmpDir("bos-init-tarball-");
	const tarballPath = (0, node_path.join)(tmpDir, "source.tar.gz");
	const fileStream = (0, node_fs.createWriteStream)(tarballPath);
	const reader = response.body;
	await (0, node_stream_promises.pipeline)(reader, fileStream);
	const extractDir = mkTmpDir("bos-init-extract-");
	try {
		await (0, tar.extract)({
			cwd: extractDir,
			file: tarballPath,
			strip: 1
		});
	} catch {
		await execCommand("tar", [
			"-xzf",
			tarballPath,
			"--strip-components=1",
			"-C",
			extractDir
		]);
	}
	(0, node_fs.rmSync)(tmpDir, {
		recursive: true,
		force: true
	});
	return {
		dir: extractDir,
		cleanup: async () => {
			(0, node_fs.rmSync)(extractDir, {
				recursive: true,
				force: true
			});
		}
	};
}
function parseGitHubUrl(url) {
	const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
	if (httpsMatch?.[1] && httpsMatch[2]) return {
		owner: httpsMatch[1],
		repo: httpsMatch[2]
	};
	const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
	if (sshMatch?.[1] && sshMatch[2]) return {
		owner: sshMatch[1],
		repo: sshMatch[2]
	};
	return null;
}
async function copyFilteredFiles(sourceDir, destination, patterns, options) {
	if (patterns.length === 0) return 0;
	const baseIgnore = [
		"**/node_modules/**",
		"**/.git/**",
		"**/dist/**",
		"**/.bos/**"
	];
	const ignore = options.ignore ? [...baseIgnore, ...options.ignore] : baseIgnore;
	const allFiles = /* @__PURE__ */ new Set();
	for (const pattern of patterns) {
		const matches = await (0, glob.glob)(pattern, {
			cwd: sourceDir,
			nodir: true,
			dot: true,
			absolute: false,
			ignore
		});
		for (const match of matches) allFiles.add(match);
	}
	if (!options.overrides.includes("api")) {
		for (const match of allFiles) if (isApiTemplatePath(match)) allFiles.delete(match);
	}
	if (!options.overrides.includes("api") && !options.overrides.includes("host")) {
		for (const match of allFiles) if (isComposeTemplatePath(match)) allFiles.delete(match);
	}
	(0, node_fs.mkdirSync)(destination, { recursive: true });
	let count = 0;
	for (const filePath of allFiles) {
		const src = (0, node_path.join)(sourceDir, filePath);
		if (!(0, node_fs.lstatSync)(src).isFile()) continue;
		const destPath = sourcePathToDestinationPath(filePath);
		const dest = (0, node_path.join)(destination, destPath);
		(0, node_fs.mkdirSync)((0, node_path.dirname)(dest), { recursive: true });
		const content = (0, node_fs.readFileSync)(src);
		(0, node_fs.writeFileSync)(dest, content);
		count++;
	}
	return count;
}
function stripProductionFields(entry) {
	delete entry.production;
	delete entry.integrity;
	delete entry.ssr;
	delete entry.ssrIntegrity;
	delete entry.pin;
}
/**
* Scaffold the authored config as the TS form: the personalized
* bos.config.json materializes into an authored `bos.app.ts` descriptor and
* the JSON copy is removed — publish/sync still canonicalize to JSON for
* FastKV from the resolved config. Returns the config that was converted, so
* callers that need it (before the child has node_modules to import the
* descriptor) can pass it onward.
*/
async function convertChildConfigToAppForm(destination) {
	const configPath = (0, node_path.join)(destination, "bos.config.json");
	if (!(0, node_fs.existsSync)(configPath)) return null;
	const config = JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8"));
	(0, node_fs.writeFileSync)((0, node_path.join)(destination, "bos.app.ts"), require_serialize.serializeAppDescriptorSource(config));
	(0, node_fs.rmSync)(configPath);
	return config;
}
/**
* Scaffold the root dev-overlay starter (`bos.dev.ts`). Idempotent — never
* overwrites an existing overlay. Dev overlays are merged child-wins over
* the resolved config in development and are never published.
*/
function writeDevOverlayTemplate(destination, opts = {}) {
	const overlayPath = (0, node_path.join)(destination, "bos.dev.ts");
	if ((0, node_fs.existsSync)(overlayPath)) return;
	const extendsLine = opts.extendsRef ? `\n// Inherited base: ${opts.extendsRef}\n` : "";
	(0, node_fs.writeFileSync)(overlayPath, `import type { AppDescriptor } from "everything-dev/descriptor";
${extendsLine}
/**
 * Development-only overlay for bos.app.ts — merged child-wins over the
 * resolved config when the environment is development. Never published.
 * Example: pin the auth attachment to a running dev server instead of
 * letting the dev harness spawn it.
 */
export default {
  // auth: { development: "http://localhost:3006" },
} satisfies Partial<AppDescriptor>;
`);
}
function buildRootTypecheckScript(sections) {
	const commands = ["pnpm run types:gen"];
	if (sections.ui) commands.push("if [ -d ui ]; then pnpm --dir ui run typecheck; fi");
	if (sections.api) commands.push("if [ -d api ]; then pnpm --dir api run typecheck; fi");
	if (sections.host) commands.push("if [ -d host ]; then pnpm --dir host run typecheck; fi");
	if (sections.plugins) commands.push("if [ -d plugins ]; then for dir in plugins/*; do if [ -f \"$dir/package.json\" ]; then pnpm --dir \"$dir\" run typecheck; fi; done; fi");
	return commands.join(" && ");
}
function getParentOnlyScriptKeys(projectDir) {
	const parentPkgPath = (0, node_path.join)(projectDir, "node_modules", "everything-dev", "package.json");
	if (!(0, node_fs.existsSync)(parentPkgPath)) return [];
	try {
		const parentScripts = JSON.parse((0, node_fs.readFileSync)(parentPkgPath, "utf-8")).scripts ?? {};
		const allChildKeys = new Set(Object.keys(buildChildRootScripts({
			ui: true,
			api: true,
			host: true,
			plugins: true
		})));
		allChildKeys.add("test:integration");
		return Object.keys(parentScripts).filter((key) => !allChildKeys.has(key));
	} catch {
		return [];
	}
}
function buildChildRootScripts(sections) {
	const scripts = {
		dev: "bos dev",
		"dev:proxy": "bos dev --proxy",
		build: "bos build",
		deploy: "bos deploy",
		publish: "bos publish",
		start: "bos start",
		typecheck: buildRootTypecheckScript(sections),
		lint: "biome check .",
		"lint:fix": "biome check --write .",
		format: "biome format --write .",
		"format:check": "biome format .",
		changeset: "changeset",
		version: "changeset version",
		release: "echo 'Packages versioned - app release handled by workflow'",
		"types:gen": "bos types gen",
		bos: "bos"
	};
	if (sections.api) {
		scripts["db:push"] = "pnpm --dir api exec drizzle-kit push";
		scripts["db:studio"] = "bos db:studio";
		scripts["db:doctor"] = "bos db:doctor";
		scripts["db:repair"] = "bos db:repair";
		scripts["db:generate"] = "pnpm --dir api exec drizzle-kit generate";
		scripts["db:migrate"] = "pnpm --dir api exec drizzle-kit migrate";
		scripts["test:api"] = "pnpm --dir api run test --if-present";
		scripts["test:integration"] = "pnpm --dir api run test tests/integration/ --if-present";
	}
	if (sections.host) scripts["test:e2e"] = "pnpm --dir host run test --if-present";
	const testTargets = [];
	if (sections.api) testTargets.push("pnpm --dir api run test --if-present");
	if (sections.host) testTargets.push("pnpm --dir host run test --if-present");
	if (sections.ui) testTargets.push("pnpm --dir ui run test --if-present");
	if (sections.plugins) testTargets.push("for d in plugins/*; do [ -f \"$d/package.json\" ] && pnpm --dir \"$d\" run test --if-present; done");
	scripts.test = testTargets.length > 0 ? testTargets.join(" && ") : "echo \"No workspace directories configured\"";
	if (sections.api || sections.host) {
		scripts["dev:postgres"] = "docker compose up -d --wait && pnpm run dev";
		scripts["dev:postgres:down"] = "docker compose down";
		scripts["dev:postgres:reset"] = "docker compose down -v && docker compose up -d --wait";
	}
	if (sections.ui) scripts["dev:ui"] = "bos dev --ui local --api remote";
	if (sections.api) scripts["dev:api"] = "bos dev --ui remote --api local";
	return scripts;
}
async function personalizeConfig(destination, opts) {
	const has = (section) => opts.overrides.includes(section);
	const preservedAuth = (opts.mode === "sync" && opts.existingConfig?.app && typeof opts.existingConfig.app === "object" ? opts.existingConfig.app : void 0)?.auth;
	const applyStarter = (config) => {
		if (opts.starter) config.starter = opts.starter;
		else if (opts.mode !== "sync") delete config.starter;
	};
	const explicitRootKeys = new Set(Object.entries(opts).filter(([key, value]) => value !== void 0 && ![
		"extendsAccount",
		"extendsGateway",
		"plugins",
		"overrides",
		"pluginRoutes",
		"workspaceOpts",
		"mode",
		"existingConfig"
	].includes(key)).map(([key]) => key));
	const jsonConfigPath = (0, node_path.join)(destination, "bos.config.json");
	const appConfigPath = (0, node_path.join)(destination, "bos.app.ts");
	if (!(0, node_fs.existsSync)(jsonConfigPath) && (0, node_fs.existsSync)(appConfigPath)) {
		const config = await require_config.loadAppDescriptorConfig(appConfigPath);
		config.extends = `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
		if (opts.account) config.account = opts.account;
		if (opts.domain) config.domain = opts.domain;
		if (opts.repository) config.repository = opts.repository;
		else delete config.repository;
		for (const field of [
			"title",
			"description",
			"testnet",
			"staging"
		]) if (!(field in opts)) delete config[field];
		applyStarter(config);
		if (config.app && typeof config.app === "object") {
			const app = config.app;
			for (const entryKey of Object.keys(app)) {
				if (!has(entryKey) && (entryKey === "host" || entryKey === "ui" || entryKey === "api")) {
					delete app[entryKey];
					continue;
				}
				if (entryKey === "auth") {
					delete app[entryKey];
					continue;
				}
				const entry = app[entryKey];
				if (entry && typeof entry === "object") stripProductionFields(entry);
			}
			if (preservedAuth !== void 0) app.auth = preservedAuth;
			if (Object.keys(app).length === 0) delete config.app;
		}
		if (has("plugins")) {
			if (config.plugins && typeof config.plugins === "object") {
				const plugins = config.plugins;
				if (opts.plugins !== void 0) {
					for (const pluginKey of Object.keys(plugins)) if (!opts.plugins.includes(pluginKey)) delete plugins[pluginKey];
				}
				for (const pluginKey of Object.keys(plugins)) {
					const plugin = plugins[pluginKey];
					let pluginObj;
					if (typeof plugin === "string") {
						pluginObj = { extends: plugin };
						plugins[pluginKey] = pluginObj;
					} else if (plugin && typeof plugin === "object") {
						pluginObj = { ...plugin };
						plugins[pluginKey] = pluginObj;
					} else continue;
					stripProductionFields(pluginObj);
				}
				if (Object.keys(plugins).length === 0) config.plugins = {};
			}
		} else config.plugins = {};
		(0, node_fs.writeFileSync)(appConfigPath, require_serialize.serializeAppDescriptorSource(config));
		return;
	}
	const configPath = (0, node_path.join)(destination, "bos.config.json");
	if ((0, node_fs.existsSync)(configPath)) {
		const config = JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8"));
		config.extends = `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
		if (opts.account) config.account = opts.account;
		if (opts.domain) config.domain = opts.domain;
		if (opts.repository) config.repository = opts.repository;
		else delete config.repository;
		for (const field of [
			"title",
			"description",
			"testnet",
			"staging"
		]) if (!(field in opts)) delete config[field];
		applyStarter(config);
		if (config.app && typeof config.app === "object") {
			const app = config.app;
			for (const entryKey of Object.keys(app)) {
				if (!has(entryKey) && (entryKey === "host" || entryKey === "ui" || entryKey === "api")) {
					delete app[entryKey];
					continue;
				}
				if (entryKey === "auth") {
					delete app[entryKey];
					continue;
				}
				const entry = app[entryKey];
				if (entry && typeof entry === "object") stripProductionFields(entry);
			}
			if (preservedAuth !== void 0) app.auth = preservedAuth;
			if (Object.keys(app).length === 0) delete config.app;
		}
		if (has("plugins")) {
			if (config.plugins && typeof config.plugins === "object") {
				const plugins = config.plugins;
				if (opts.plugins !== void 0) {
					for (const pluginKey of Object.keys(plugins)) if (!opts.plugins.includes(pluginKey)) delete plugins[pluginKey];
				}
				for (const pluginKey of Object.keys(plugins)) {
					const plugin = plugins[pluginKey];
					let pluginObj;
					if (typeof plugin === "string") {
						pluginObj = { extends: plugin };
						plugins[pluginKey] = pluginObj;
					} else if (plugin && typeof plugin === "object") {
						pluginObj = { ...plugin };
						plugins[pluginKey] = pluginObj;
					} else continue;
					stripProductionFields(pluginObj);
				}
				if (Object.keys(plugins).length === 0) config.plugins = {};
			}
		} else config.plugins = {};
		if (opts.mode === "sync" && opts.existingConfig) {
			const managedRootKeys = /* @__PURE__ */ new Set([
				"extends",
				"account",
				"domain",
				"app",
				"plugins"
			]);
			const preservedRootKeys = /* @__PURE__ */ new Set([
				...managedRootKeys,
				...Object.keys(opts.existingConfig),
				...explicitRootKeys
			]);
			for (const key of Object.keys(config)) if (!preservedRootKeys.has(key)) delete config[key];
			for (const [key, value] of Object.entries(opts.existingConfig)) if (!(key in config) && !managedRootKeys.has(key) && !explicitRootKeys.has(key)) config[key] = value;
		}
		await require_save_config.saveBosConfig(destination, config);
	}
	for (const relPath of ["ui/src/lib/api-types.gen.ts", "api/src/lib/plugins-types.gen.ts"]) {
		const absolutePath = (0, node_path.join)(destination, relPath);
		try {
			(0, node_fs.rmSync)(absolutePath, { force: true });
		} catch {}
	}
	const pkgPath = (0, node_path.join)(destination, "package.json");
	if ((0, node_fs.existsSync)(pkgPath)) {
		const pkg = JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8"));
		const childScripts = buildChildRootScripts({
			ui: has("ui"),
			api: has("api"),
			host: has("host"),
			plugins: has("plugins")
		});
		if (typeof pkg.name !== "string" || pkg.name.length === 0) pkg.name = "monorepo";
		pkg.private = true;
		pkg.type = "module";
		pkg.packageManager = PNPM_PACKAGE_MANAGER;
		delete pkg.module;
		delete pkg.peerDependencies;
		delete pkg.patchedDependencies;
		const childWorkspaces = [];
		if (has("host")) childWorkspaces.push("host");
		if (has("ui")) childWorkspaces.push("ui");
		if (has("api")) childWorkspaces.push("api");
		if (has("plugins")) childWorkspaces.push("plugins/*");
		const localCatalog = pkg.workspaces && typeof pkg.workspaces === "object" ? pkg.workspaces.catalog ?? {} : {};
		delete pkg.workspaces;
		if (!pkg.scripts || typeof pkg.scripts !== "object") pkg.scripts = {};
		const scripts = pkg.scripts;
		for (const [key, value] of Object.entries(childScripts)) scripts[key] = value;
		for (const obsoleteScript of [
			...getParentOnlyScriptKeys(destination),
			"init",
			"prepare",
			"sync-catalog",
			"db:push",
			"db:studio",
			"db:doctor",
			"db:repair",
			"db:generate",
			"db:migrate",
			"test",
			"test:api",
			"test:integration",
			"test:e2e",
			"dev:postgres",
			"dev:postgres:down",
			"dev:postgres:reset",
			"dev:ui",
			"dev:api"
		]) if (!(obsoleteScript in childScripts)) delete scripts[obsoleteScript];
		if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
			const deps = pkg.devDependencies;
			delete deps["every-plugin"];
			delete deps["everything-dev"];
		}
		const catalog = { ...localCatalog };
		pkg.dependencies = {
			"everything-dev": "catalog:",
			"every-plugin": "catalog:"
		};
		const deps = pkg.dependencies;
		const spec = opts.workspaceOpts?.sourceDir ? require_manifest_normalizer.loadManifestNormalizationSpec(opts.workspaceOpts.sourceDir) : null;
		if (spec) {
			const rootCatalogEverythingDev = spec.rootCatalog["everything-dev"];
			const rootCatalogEveryPlugin = spec.rootCatalog["every-plugin"];
			if (rootCatalogEverythingDev) catalog["everything-dev"] = rootCatalogEverythingDev;
			if (rootCatalogEveryPlugin) catalog["every-plugin"] = rootCatalogEveryPlugin;
		}
		const frameworkCatalog = (await resolveCatalogChainSource({
			extendsAccount: opts.extendsAccount,
			extendsGateway: opts.extendsGateway,
			sourceDir: opts.workspaceOpts?.sourceDir
		})).catalog;
		for (const [name, version] of Object.entries(frameworkCatalog)) catalog[name] = version;
		if (!deps["everything-dev"]) deps["everything-dev"] = "catalog:";
		if (!deps["every-plugin"]) deps["every-plugin"] = "catalog:";
		(0, node_fs.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
		writeChildWorkspaceYaml(destination, {
			packages: childWorkspaces,
			catalog
		});
	}
	const apiTsConfigPath = (0, node_path.join)(destination, "api", "tsconfig.json");
	if ((0, node_fs.existsSync)(apiTsConfigPath)) {
		const apiTsConfig = JSON.parse((0, node_fs.readFileSync)(apiTsConfigPath, "utf-8"));
		if (apiTsConfig.files) {
			const validFiles = apiTsConfig.files.filter((f) => (0, node_fs.existsSync)((0, node_path.join)(destination, "api", f)));
			if (validFiles.length !== apiTsConfig.files.length) {
				if (validFiles.length === 0) delete apiTsConfig.files;
				else apiTsConfig.files = validFiles;
				(0, node_fs.writeFileSync)(apiTsConfigPath, `${JSON.stringify(apiTsConfig, null, 2)}\n`);
			}
		}
	}
	await resolveWorkspaceRefs(destination, opts.workspaceOpts);
	if (has("ui")) {
		const genContractPath = (0, node_path.join)(destination, "ui", "src", "lib", "api-types.gen.ts");
		if (!(0, node_fs.existsSync)(genContractPath)) {
			(0, node_fs.mkdirSync)((0, node_path.dirname)(genContractPath), { recursive: true });
			(0, node_fs.writeFileSync)(genContractPath, `export type ApiContract = Record<string, never>;\n`);
		}
		const publicDir = (0, node_path.join)(destination, "ui", "public");
		if (!(0, node_fs.existsSync)(publicDir)) (0, node_fs.mkdirSync)(publicDir, { recursive: true });
		const llmsTxtPath = (0, node_path.join)(publicDir, "llms.txt");
		if (!(0, node_fs.existsSync)(llmsTxtPath)) {
			const title = opts.title ?? opts.account ?? "app";
			(0, node_fs.writeFileSync)(llmsTxtPath, buildChildLlmsTxt(title));
		}
		const skillMdPath = (0, node_path.join)(publicDir, "skill.md");
		if (!(0, node_fs.existsSync)(skillMdPath)) {
			const title = opts.title ?? opts.account ?? "app";
			const repository = opts.repository ?? "";
			(0, node_fs.writeFileSync)(skillMdPath, buildChildSkillMd(title, repository));
		}
		for (const agentFilePath of [llmsTxtPath, skillMdPath]) {
			const content = (0, node_fs.readFileSync)(agentFilePath, "utf-8");
			if (content.includes(WORKFLOW_SKILLS_MARKER)) continue;
			(0, node_fs.writeFileSync)(agentFilePath, `${content.replace(/\n*$/, "\n")}${WORKFLOW_SKILLS_NOTE}`);
		}
	}
	if (has("api")) {
		const pluginsClientGenPath = (0, node_path.join)(destination, "api", "src", "lib", "plugins-types.gen.ts");
		if (!(0, node_fs.existsSync)(pluginsClientGenPath)) {
			(0, node_fs.mkdirSync)((0, node_path.dirname)(pluginsClientGenPath), { recursive: true });
			(0, node_fs.writeFileSync)(pluginsClientGenPath, `import type { RouterContractClient, RouterContract } from "@orpc/contract";\ntype ClientFactory<C extends RouterContract> = (context?: Record<string, unknown>) => RouterContractClient<C>;\nexport type PluginsClient = Record<string, never>;\n`);
		}
	}
	const authTypesPaths = [];
	if (has("ui")) authTypesPaths.push((0, node_path.join)(destination, "ui", "src", "lib", "auth-types.gen.ts"));
	if (has("api")) authTypesPaths.push((0, node_path.join)(destination, "api", "src", "lib", "auth-types.gen.ts"));
	if (has("host") && (0, node_fs.existsSync)((0, node_path.join)(destination, "host", "src"))) authTypesPaths.push((0, node_path.join)(destination, "host", "src", "lib", "auth-types.gen.ts"));
	for (const authTypesGenPath of authTypesPaths) if (!(0, node_fs.existsSync)(authTypesGenPath)) {
		(0, node_fs.mkdirSync)((0, node_path.dirname)(authTypesGenPath), { recursive: true });
		const authExportRel = toRelativeImportPath((0, node_path.join)(destination, ".bos", "generated", "auth", "auth-export.d.ts"), authTypesGenPath);
		const contractRel = toRelativeImportPath((0, node_path.join)(destination, ".bos", "generated", "auth", "contract.d.ts"), authTypesGenPath);
		(0, node_fs.writeFileSync)(authTypesGenPath, require_auth_types_gen.buildAuthTypesGenContent(authExportRel, contractRel));
	}
	if (authTypesPaths.length > 0) {
		const authDir = (0, node_path.join)(destination, ".bos", "generated", "auth");
		if (!(0, node_fs.existsSync)(authDir)) (0, node_fs.mkdirSync)(authDir, { recursive: true });
		const authExportStubPath = (0, node_path.join)(authDir, "auth-export.d.ts");
		if (!(0, node_fs.existsSync)(authExportStubPath)) (0, node_fs.writeFileSync)(authExportStubPath, require_auth_types_gen.buildAuthExportStub());
		const contractStubPath = (0, node_path.join)(authDir, "contract.d.ts");
		if (!(0, node_fs.existsSync)(contractStubPath)) (0, node_fs.writeFileSync)(contractStubPath, require_auth_types_gen.buildAuthContractStub());
	}
	if (has("plugins")) for (const plugin of opts.plugins ?? []) {
		const pluginSrcDir = (0, node_path.join)(destination, "plugins", plugin, "src");
		const pluginIndexPath = (0, node_path.join)(pluginSrcDir, "index.ts");
		const pluginClientGenPath = (0, node_path.join)(pluginSrcDir, "lib", "plugins-client.gen.ts");
		if (!(0, node_fs.existsSync)(pluginIndexPath) || (0, node_fs.existsSync)(pluginClientGenPath)) continue;
		if (!(0, node_fs.readFileSync)(pluginIndexPath, "utf-8").includes("./lib/plugins-client.gen")) continue;
		(0, node_fs.writeFileSync)(pluginClientGenPath, "export type PluginsClient = Record<string, never>;\n");
	}
}
function toRelativeImportPath(fromPath, toPath) {
	const rel = (0, node_path.relative)((0, node_path.dirname)(toPath), fromPath);
	return rel.startsWith(".") ? rel : `./${rel}`;
}
async function runPnpmInstall(destination, opts) {
	await runWithProgress("pnpm", ["install", "--ignore-scripts"], destination, opts?.spinner, "Installing dependencies");
}
async function runPnpmInstallForUpgrade(destination, opts) {
	await runWithProgress("pnpm", ["install", "--force"], destination, opts?.spinner, "Installing dependencies");
}
async function runTypesGen(destination, opts) {
	const bosModule = (0, node_path.join)(destination, "node_modules", "everything-dev", "dist", "cli.mjs");
	if ((0, node_fs.existsSync)(bosModule)) {
		const args = [
			bosModule,
			"types",
			"gen"
		];
		if (opts?.remotePlugins && opts.remotePlugins.length > 0) args.push("--remote-plugins", opts.remotePlugins.join(","));
		await runWithProgress(process.execPath, args, destination, opts?.spinner, "Generating types");
		return;
	}
	throw new Error("Unable to locate bos CLI for types generation");
}
async function runWithProgress(command, args, cwd, spinner, label) {
	const timeout = COMMAND_TIMEOUTS[command] ?? 12e4;
	const child = (0, execa.execa)(command, args, {
		cwd,
		stdio: "inherit",
		timeout,
		env: {
			...process.env,
			BOS_NO_BANNER: "1"
		}
	});
	if (spinner) {
		const start = Date.now();
		const interval = setInterval(() => {
			const elapsed = Math.round((Date.now() - start) / 1e3);
			spinner.message(`${label}... (${elapsed}s)`);
		}, 2e3);
		try {
			await child;
		} finally {
			clearInterval(interval);
		}
	} else await child;
}
async function scaffoldMinimalProject(destination, parentConfig, opts) {
	(0, node_fs.mkdirSync)(destination, { recursive: true });
	const has = (section) => opts.overrides.includes(section);
	const config = {
		extends: `bos://${opts.extendsAccount}/${opts.extendsGateway}`,
		account: opts.account || opts.extendsAccount,
		...opts.domain ? { domain: opts.domain } : {},
		...opts.repository ? { repository: opts.repository } : {},
		...opts.title ? { title: opts.title } : {},
		...opts.description ? { description: opts.description } : {},
		...opts.starter ? { starter: opts.starter } : {}
	};
	if (parentConfig.app && typeof parentConfig.app === "object") {
		const app = {};
		const parentApp = parentConfig.app;
		if (has("host") && parentApp.host) {
			app.host = { ...parentApp.host };
			stripProductionFields(app.host);
		}
		if (has("ui") && parentApp.ui) {
			app.ui = { ...parentApp.ui };
			stripProductionFields(app.ui);
		}
		if (has("api") && parentApp.api) {
			app.api = { ...parentApp.api };
			stripProductionFields(app.api);
		}
		if (Object.keys(app).length > 0) config.app = app;
	}
	if (has("plugins") && opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
		const plugins = {};
		for (const key of opts.plugins) {
			const parentPlugin = parentConfig.plugins?.[key];
			if (parentPlugin) {
				if (typeof parentPlugin === "string") plugins[key] = { extends: parentPlugin };
				else {
					const pluginCopy = { ...parentPlugin };
					stripProductionFields(pluginCopy);
					plugins[key] = pluginCopy;
				}
			}
		}
		config.plugins = plugins;
	} else if (has("plugins")) config.plugins = {};
	await require_save_config.saveBosConfig(destination, config);
	const workspacePackages = [];
	for (const section of opts.overrides) workspacePackages.push(...OVERRIDE_WORKSPACE_MAP[section]);
	if (has("plugins")) workspacePackages.push("plugins/*");
	const catalog = (await resolveCatalogChainSource({
		extendsAccount: opts.extendsAccount,
		extendsGateway: opts.extendsGateway,
		sourceDir: opts.catalogSourceDir
	})).catalog;
	const pkg = {
		name: "monorepo",
		private: true,
		type: "module",
		packageManager: PNPM_PACKAGE_MANAGER,
		scripts: buildChildRootScripts({
			ui: has("ui"),
			api: has("api"),
			host: has("host"),
			plugins: has("plugins")
		}),
		dependencies: {
			"everything-dev": "catalog:",
			"every-plugin": "catalog:"
		},
		devDependencies: {}
	};
	(0, node_fs.writeFileSync)((0, node_path.join)(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
	writeChildWorkspaceYaml(destination, {
		packages: workspacePackages,
		catalog
	});
	(0, node_fs.writeFileSync)((0, node_path.join)(destination, ".gitignore"), generateGitignore());
	return 4;
}
async function resolveWorkspaceRefs(destination, options) {
	await require_manifest_normalizer.normalizePackageManifestsInTree({
		sourceRootDir: options?.sourceDir ?? destination,
		targetDir: destination,
		resolveCatalogRefs: false,
		preserveCatalogRefs: true,
		removeWorkspaceDeps: ["host"]
	});
}
async function writeInitSnapshot(destination, extendsAccount, extendsGateway, sourceDir, patterns, options) {
	const baseIgnore = [
		"**/node_modules/**",
		"**/.git/**",
		"**/dist/**",
		"**/.bos/**"
	];
	const ignore = options.ignore ? [...baseIgnore, ...options.ignore] : baseIgnore;
	const allFiles = /* @__PURE__ */ new Set();
	for (const pattern of patterns) {
		const matches = await (0, glob.glob)(pattern, {
			cwd: sourceDir,
			nodir: true,
			dot: true,
			absolute: false,
			ignore
		});
		for (const match of matches) allFiles.add(match);
	}
	if (!options.overrides.includes("api")) {
		for (const match of allFiles) if (isApiTemplatePath(match)) allFiles.delete(match);
	}
	if (!options.overrides.includes("api") && !options.overrides.includes("host")) {
		for (const match of allFiles) if (isComposeTemplatePath(match)) allFiles.delete(match);
	}
	const fileHashes = {};
	for (const filePath of allFiles) {
		const src = (0, node_path.join)(sourceDir, filePath);
		if (!(0, node_fs.lstatSync)(src).isFile()) continue;
		const destPath = sourcePathToDestinationPath(filePath);
		if (!(0, node_fs.existsSync)((0, node_path.join)(destination, destPath))) continue;
		const content = (0, node_fs.readFileSync)(src);
		fileHashes[destPath] = require_snapshot_hash.computeSnapshotHash(content);
	}
	await require_snapshot.writeSnapshot(destination, {
		parentRef: `bos://${extendsAccount}/${extendsGateway}`,
		files: fileHashes,
		starter: options.starter
	});
}
function mkTmpDir(prefix) {
	return (0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), `${prefix}-`));
}
async function generateDatabaseMigrations(destination) {
	const drizzleConfigs = await (0, glob.glob)("**/drizzle.config.ts", {
		cwd: destination,
		nodir: true,
		dot: false,
		absolute: false,
		ignore: ["**/node_modules/**"]
	});
	for (const configPath of drizzleConfigs) {
		const workspaceDir = (0, node_path.dirname)(configPath);
		const pkgPath = (0, node_path.join)(destination, workspaceDir, "package.json");
		if (!(0, node_fs.existsSync)(pkgPath)) continue;
		if (!JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8")).scripts?.["db:generate"]) continue;
		await execCommand("pnpm", [
			"--dir",
			"api",
			"exec",
			"drizzle-kit",
			"generate"
		], (0, node_path.join)(destination, workspaceDir));
	}
}
const COMMAND_TIMEOUTS = {
	pnpm: 3e5,
	docker: 3e5,
	node_modules: 12e4,
	tar: 6e4
};
async function execCommand(command, args, cwd, options) {
	const timeout = COMMAND_TIMEOUTS[command] ?? 12e4;
	await (0, execa.execa)(command, args, {
		cwd,
		stdio: options?.stdio ?? "pipe",
		timeout
	});
}
function extractSkillsBlock(content) {
	const match = content.match(/<!-- intent-skills:start -->[\s\S]*?<!-- intent-skills:end -->/);
	return match ? match[0] : "";
}
function buildChildAgentsInstructions(opts) {
	const has = (section) => opts.overrides.includes(section);
	const parts = [];
	parts.push(`# Agent Instructions

This document provides operational guidance for AI agents working in this everything.dev project.

## Quick Reference

**Start Development:**
\`\`\`bash
pnpm install
pnpm run dev
\`\`\`

**Check Status:**
\`\`\`bash
bos ps        # List running processes
bos status    # Project health check
bos info      # Show configuration
\`\`\`

**Deploy:**

[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/everything-dev-template?referralCode=MuB_vg&utm_medium=integration&utm_source=template&utm_campaign=generic)

The Railway template deploys the everything.dev Docker image. Set these variables:

| Variable | Description | Example |
|----------|-------------|---------|
| \`BOS_ACCOUNT\` | The NEAR account that owns this app's published configuration on-chain | \`myapp.near\` |
| \`BOS_GATEWAY\` | The core domain where this app is served | \`myapp.com\` |
| \`BETTER_AUTH_SECRET\` | Secret for session encryption — generate with \`openssl rand -base64 32\` | (random) |

**Self-deployed production:**

You don't need to wait for a PR to merge and run through CI/CD. Publish your own config on-chain under your own NEAR account and run your own host instance, inheriting the base platform via \`extends\`.

1. **Install near-cli-rs** (the \`bos\` CLI shells out to it for \`bos publish\` and \`bos key generate\`):
   \`\`\`bash
   curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/download/v0.23.5/near-cli-rs-installer.sh | sh
   \`\`\`
2. **Create a NEAR account** (testnet or mainnet). Named accounts can own subaccounts; implicit hex accounts cannot:
   \`\`\`bash
   near account create-account fund-my-account <your-account>.testnet use-faucet network-config testnet
   \`\`\`
3. **Generate a publish key** — a function-call key scoped to the FastKV registry contract:
   \`\`\`bash
   bos key generate
   # Output: NEAR_PRIVATE_KEY=ed25519:...
   \`\`\`
   Add the key to your account via near-cli-rs, then set \`NEAR_PRIVATE_KEY\` in \`.env\` or CI secrets.
4. **Update \`bos.app.ts\`** — set \`account\` to your NEAR account, add \`extends\` to inherit the base platform, keep \`domain\` as the gateway:
   \`\`\`ts
   { "extends": "bos://<parent-account>/<parent-gateway>", "account": "<your-account>.near", "domain": "<parent-gateway>" }
   \`\`\`
 5. **Publish and deploy:**
    \`\`\`bash
    bos deploy           # preflight → build → upload bundles → publish config to FastKV at bos://<your-account>/<gateway> → image/Railway when configured
    \`\`\`
6. **Deploy to Railway** (one-click template or \`railway up\`), set \`BOS_ACCOUNT\`, \`BOS_GATEWAY\` (same gateway as parent), and \`BETTER_AUTH_SECRET\`. Your Railway host fetches your config from FastKV and serves live.

\`BOS_GATEWAY\` is the **FastKV lookup key**, not the DNS domain your Railway instance serves on. By keeping the same gateway while using your own \`BOS_ACCOUNT\`, your config lives at a separate FastKV path that \`extends\` the base runtime — you inherit the full platform and override only what you change.

**Tenant creation** (for the admin wizard) is DAO-owned: connect a sputnik-dao account via the Trezu wallet in the admin wizard; the wizard publishes the tenant runtime config under \`bos://<dao-account>/<gateway>\`. No server-side subaccount keys are needed.`);
	const archLines = ["This is an everything.dev child project. Depending on your overrides, it may include:"];
	if (has("ui")) archLines.push("- **UI** — React 19 + TanStack Router frontend, loaded via Module Federation");
	if (has("api")) archLines.push("- **API** — Hono.js + oRPC backend with Effect services");
	if (has("host")) archLines.push("- **Host** — Server-side runtime with Module Federation orchestration");
	if (has("plugins")) archLines.push("- **Plugins** — Self-contained business logic modules with oRPC contracts");
	archLines.push("", "The parent runtime provides the shared framework; your project provides custom overrides.");
	parts.push(`## Architecture\n\n${archLines.join("\n")}`);
	parts.push(`## Development Workflow

### Starting Development
1. \`pnpm install\`
2. \`pnpm run dev\`
3. \`bos dev\` creates \`.env\` on first run and starts local Postgres via docker compose when it is down`);
	parts.push(`### Debugging Issues

**API not responding:**
- Check \`bos ps\` to see if the API process is running
- Check \`.bos/logs/api.log\` for errors

**UI not loading:**
- Verify the dev server is running: \`bos ps\`
- Check browser console for Module Federation errors
- Clear browser cache and retry

**Type errors:**
- Run \`pnpm run typecheck\``);
	const changeLines = ["### Making Changes"];
	if (has("ui")) changeLines.push("- **UI Changes**: Edit `ui/src/` files → hot reload automatically");
	if (has("api")) changeLines.push("- **API Changes**: Edit `api/src/` files → hot reload automatically");
	if (has("host")) changeLines.push("- **Host Changes**: Edit `host/src/` when changing runtime resolution, auth wiring, SSR, proxying, or plugin mounting");
	changeLines.push("- **New Components**: Create in `ui/src/components/ui/`, export from `ui/src/components/index.ts`");
	changeLines.push("- **New Routes**: Create file in `ui/src/routes/`, TanStack Router auto-generates tree");
	parts.push(`## Code Changes\n\n${changeLines.join("\n")}`);
	parts.push(`### Style Requirements
- Use semantic Tailwind classes: \`bg-background\`, \`text-foreground\`, \`text-muted-foreground\`
- No hardcoded colors like \`bg-blue-600\`
- No code comments in implementation
- Component file naming: lowercase kebab-case (\`data-table.tsx\`, \`user-profile.tsx\`)
- Follow existing patterns in neighboring files`);
	if (has("api")) parts.push(`### Adding API Endpoints
1. Define in \`api/src/contract.ts\` — the oRPC route definitions and Zod schemas
2. Implement in \`api/src/index.ts\` — the \`createRouter\` function
3. Use in UI via \`apiClient\` from \`useApiClient()\` in \`@/app\``);
	if (has("plugins")) parts.push(`### Plugin Architecture

Business logic is organized into independent plugins loaded via Module Federation:
- Each plugin has its own \`contract.ts\` — oRPC route definitions and Zod schemas
- Each plugin has its own \`index.ts\` — \`createPlugin\` with variables, secrets, context, router
- Each plugin has its own rspack config for independent deployment

The UI accesses plugin routes via namespaced clients: \`apiClient.<plugin>.<method>()\`.

### Plugin Client (pluginsClient)

The API plugin receives typed client factories for all other plugins via \`createPlugin.withPlugins<PluginsClient>()\`, enabling in-process composition without HTTP roundtrips.

### Generated Types

\`api/src/lib/plugins-types.gen.ts\`, \`api/src/lib/auth-types.gen.ts\`, \`ui/src/lib/api-types.gen.ts\`, and \`ui/src/lib/auth-types.gen.ts\` are generated by \`bos types gen\` from \`bos.app.ts\`. These files are gitignored and auto-regenerated on \`pnpm install\`, \`typecheck\`, \`bos dev\`, \`bos build\`, and bos plugin management commands.

If you hand-edit \`bos.app.ts\`, run \`bos types gen\` or restart \`bos dev\` to regenerate.`);
	const testCommands = [];
	if (has("api") || has("host") || has("ui")) {
		testCommands.push("pnpm run test    # Run all tests");
		testCommands.push("pnpm run typecheck   # Type check all packages");
		testCommands.push("pnpm run lint        # Run linting");
	}
	if (testCommands.length > 0) parts.push(`## Testing & Quality

**Before committing:**
\`\`\`bash
${testCommands.join("\n")}
\`\`\``);
	parts.push(`## Common Patterns

### Authentication Check

Routes requiring auth use \`_authenticated.tsx\` layout:
\`\`\`typescript
export const Route = createFileRoute('/_layout/_authenticated')({
  beforeLoad: async ({ location }) => {
    const { data: session } = await authClient.getSession();
    if (!session?.user) {
      throw redirect({ to: '/login', search: { redirect: location.pathname } });
    }
  },
});
\`\`\``);
	if (has("ui")) parts.push(`### API Client Usage
\`\`\`typescript
import { useApiClient } from "@/app";

function MyComponent() {
  const apiClient = useApiClient();
  const { data } = await apiClient.ping();
}
\`\`\``);
	parts.push(`## Workflow Skills

This repo ships agent workflow skills in \`.agents/skills/\` — the ordered development flow (grill → spec → tickets → implement/tdd → code-review). Start \`/everything-dev-app\` to orient and pick the right next step; \`/ask-matt\` is the router if unsure.

- \`/grill-with-docs\` — sharpen an idea by interview, leaving a paper trail in \`GLOSSARY.md\` and ADRs
- \`/to-spec\` / \`/to-tickets\` — turn a plan into a spec, then tracer-bullet tickets under \`.scratch/<feature>/issues/\`
- \`/implement\` + \`/tdd\` — build a ticket test-first at pre-agreed seams
- \`/code-review\` — two-axis review (Standards + Spec) of the diff since a fixed point
- \`/diagnosing-bugs\` — diagnosis loop for hard bugs and performance regressions

Run \`/setup-matt-pocock-skills\` once before first use. Tracker and triage conventions live in \`docs/agents/\`.`);
	parts.push(`## Agent Communication Surface

The host exposes several surfaces for programmatic agent access:

| Surface | Endpoint | Auth | Use |
|---------|----------|------|-----|
| MCP | \`POST /api/mcp\` | \`x-api-key\` header or session cookie | MCP clients — auto-generated tools from OpenAPI spec, stateless Streamable HTTP transport |
| REST/OpenAPI | \`GET/POST/... /api/{path}\` | \`x-api-key\` header or session cookie | Standard REST; Scalar docs at \`GET /api\`, spec at \`GET /api/spec.json\` |
| oRPC RPC | \`POST /api/rpc/{procedure}\` | \`x-api-key\` header or session cookie | Typed JSON-RPC for all API procedures |
| Plugin RPC | \`POST /api/rpc/{plugin}/{procedure}\` | \`x-api-key\` header or session cookie | Per-plugin RPC (e.g. \`/api/rpc/auth/getSession\`) |
| MCP discovery | \`GET /.well-known/mcp.json\` | None | JSON descriptor with server name, endpoint, and auth scheme |

### Authentication for agents

1. Sign in with your NEAR wallet (SIWN) at the website.
2. Navigate to **Settings → API Keys** at \`/settings/api-keys\`.
3. Create a new key — the full secret (\`edk_...\`) is shown once. Copy it immediately.
4. Pass it on every request: \`x-api-key: edk_your_key_here\`

### Architecture note: remotes are code bundles

Remotes in \`bos.app.ts\` are **not hosted APIs** — they are code bundles loaded via Module Federation at runtime. The UI, API, auth, and plugins all run in the same host process. There is no remote server to call; everything is loaded in-process through Module Federation and \`every-plugin\`.`);
	parts.push(`## Troubleshooting

**Process won't start:**
\`\`\`bash
bos kill        # Kill all tracked processes
pnpm install    # Ensure dependencies
pnpm run dev    # Restart
\`\`\`

**Module Federation errors:**
- Check \`bos.app.ts\` URLs are accessible
- Verify shared dependency versions match in package.json
- Clear browser cache

**Database issues:**
\`\`\`bash
pnpm run db:push   # Push schema changes
pnpm run db:studio # Open Drizzle Studio
\`\`\`

## Environment

**Required files:**
- \`.env\` — Secrets (see \`.env.example\`)
- \`bos.app.ts\` — Runtime configuration (committed)`);
	return `${parts.join("\n\n")}\n`;
}
function buildChildAgentsMd(skillsBlock, opts) {
	return `${skillsBlock}\n\n${buildChildAgentsInstructions(opts)}`;
}
async function personalizeAgentsMd(destination, opts) {
	const agentsMdPath = (0, node_path.join)(destination, "AGENTS.md");
	if (!(0, node_fs.existsSync)(agentsMdPath)) return;
	const skillsBlock = extractSkillsBlock((0, node_fs.readFileSync)(agentsMdPath, "utf-8"));
	if (!skillsBlock) return;
	const childContent = buildChildAgentsMd(skillsBlock, opts);
	(0, node_fs.writeFileSync)(agentsMdPath, childContent);
}
function generateGitignore() {
	return `node_modules/
dist/
.env
.bos/
docker-compose.yml
*.gen.ts
*.gen.tsx
`;
}
const WORKFLOW_SKILLS_MARKER = "everything-dev-app";
const WORKFLOW_SKILLS_NOTE = `

## Workflow skills

This repo ships agent workflow skills in \`.agents/skills/\` — the ordered development flow (grill → spec → tickets → implement/tdd → code-review). Start with \`/everything-dev-app\` to orient and pick the right next step. See \`AGENTS.md\` → Workflow Skills.
`;
function buildChildLlmsTxt(title) {
	return `# ${title}

> Application running on the everything.dev runtime.

## Skills

- [Skill](/skill.md): Agent-ready prompt for talking to, running, editing, and publishing this runtime.
- Workflow skills (in the repo): \`.agents/skills/\` — start with \`everything-dev-app\` for the ordered development flow.

## API

- [OpenAPI docs](/api): Interactive API reference (Scalar)
- [OpenAPI spec](/api/spec.json): Machine-readable OpenAPI JSON
- [oRPC RPC](/api/rpc): Typed JSON-RPC endpoint for all API procedures
- [Plugin RPC](/api/rpc/auth): Auth plugin RPC (session, NEAR SIWN, relay, API keys, organizations)

## MCP

- [MCP server](/api/mcp): Model Context Protocol server (Streamable HTTP, stateless). Auto-generates tools from the API's OpenAPI spec.
- [MCP discovery](/.well-known/mcp.json): JSON descriptor with server name, endpoint URL, and auth scheme.

## Auth

Authenticate to the API using an API key:

1. Sign in with your NEAR wallet (SIWN) at the website.
2. Go to **Settings → API Keys** at \`/settings/api-keys\`.
3. Create a key — the full secret (\`edk_...\`) is shown once.
4. Pass it on every request: \`x-api-key: edk_your_key_here\`

The \`x-api-key\` header works for \`/api/*\` (REST), \`/api/rpc/*\` (oRPC), and \`/api/mcp\` (MCP).

## Source

- [Repository](https://github.com/NEARBuilders/everything-dev): Clone and read \`AGENTS.md\` for full development instructions, TanStack Intent skills, and workflow guidance.
`;
}
function buildChildSkillMd(title, repository) {
	return `# ${title} skill

Use this when you want an agent to run, edit, and publish **${title}** — an everything.dev app composed at runtime from \`bos.app.ts\`.

There are two ways to work with this app:

1. **Talk to the app** — use the API via MCP or REST to read/write data without cloning anything.
2. **Clone and modify** — clone the repository, run locally, edit code, and publish.

## Mode 1: Talk to the app

### MCP endpoint

\`\`\`
POST /api/mcp
\`\`\`

Transport: Streamable HTTP (stateless). Connect your MCP client to \`{origin}/api/mcp\` to discover all available tools automatically.

### Authentication

Use an **API key**:

1. Sign in with your NEAR wallet at the website (SIWN).
2. Navigate to **Settings → API Keys** at \`/settings/api-keys\`.
3. Create a key — the full secret (\`edk_...\`) is shown once.
4. Pass it on every request: \`x-api-key: edk_your_key_here\`

### REST / OpenAPI

- **API docs**: \`GET /api\`
- **OpenAPI spec**: \`GET /api/spec.json\`
- **oRPC RPC**: \`POST /api/rpc/{procedure}\`
- **MCP discovery**: \`GET /.well-known/mcp.json\`

## Mode 2: Clone and modify

### TanStack Intent

- Registry entry: \`https://tanstack.com/intent/registry/everything-dev\`
- Load with TanStack Intent: \`npx @tanstack/intent@latest load everything-dev\`

### Read AGENTS.md first

After cloning, read **\`AGENTS.md\`** at the repo root. It contains operational guidance, TanStack Intent skills, and workflow instructions.

### Workflow skills

The repo ships agent workflow skills in \`.agents/skills/\` — start with \`everything-dev-app\` for the ordered development flow (grill → spec → tickets → implement/tdd → code-review).

### Architecture note

Remotes in \`bos.app.ts\` are **not hosted APIs** — they are code bundles loaded via Module Federation at runtime. Everything runs in the same host process.

### Run locally

\`\`\`bash
pnpm install
bos dev
\`\`\`

### Publish

\`\`\`bash
bos deploy
\`\`\`

## Source

${repository ? `- [Repository](${repository}): Clone and read \`AGENTS.md\` for full development instructions.` : `- Clone the repository and read \`AGENTS.md\` for full development instructions.`}
`;
}

//#endregion
exports.INIT_ROOT_PATTERNS = INIT_ROOT_PATTERNS;
exports.PNPM_PACKAGE_MANAGER = PNPM_PACKAGE_MANAGER;
exports.buildChildAgentsMd = buildChildAgentsMd;
exports.buildChildLlmsTxt = buildChildLlmsTxt;
exports.buildChildRootScripts = buildChildRootScripts;
exports.buildChildSkillMd = buildChildSkillMd;
exports.buildInitPatterns = buildInitPatterns;
exports.buildPluginRouteExclusions = buildPluginRouteExclusions;
exports.buildStarterRouteExclusions = buildStarterRouteExclusions;
exports.convertChildConfigToAppForm = convertChildConfigToAppForm;
exports.copyFilteredFiles = copyFilteredFiles;
exports.detectGitRemoteUrl = detectGitRemoteUrl;
exports.downloadTarball = downloadTarball;
exports.execCommand = execCommand;
exports.extractSkillsBlock = extractSkillsBlock;
exports.fetchParentConfig = fetchParentConfig;
exports.generateDatabaseMigrations = generateDatabaseMigrations;
exports.getParentOnlyScriptKeys = getParentOnlyScriptKeys;
exports.isApiTemplatePath = isApiTemplatePath;
exports.isComposeTemplatePath = isComposeTemplatePath;
exports.personalizeAgentsMd = personalizeAgentsMd;
exports.personalizeConfig = personalizeConfig;
exports.readWorkspaceCatalog = readWorkspaceCatalog;
exports.removeStrayLockfiles = removeStrayLockfiles;
exports.resolveCatalogChainSource = resolveCatalogChainSource;
exports.resolveRepositoryViaExtendsChain = resolveRepositoryViaExtendsChain;
exports.resolveSourceDir = resolveSourceDir;
exports.runDockerComposeUp = require_docker.runDockerComposeUp;
exports.runPnpmInstall = runPnpmInstall;
exports.runPnpmInstallForUpgrade = runPnpmInstallForUpgrade;
exports.runTypesGen = runTypesGen;
exports.scaffoldMinimalProject = scaffoldMinimalProject;
exports.sourcePathToDestinationPath = sourcePathToDestinationPath;
exports.writeChildWorkspaceYaml = writeChildWorkspaceYaml;
exports.writeDevOverlayTemplate = writeDevOverlayTemplate;
exports.writeInitSnapshot = writeInitSnapshot;
//# sourceMappingURL=init.cjs.map