import { V as getCleoPlatformPaths, W as WHOAMI_ROLE_IDS, o as resolveOrCwd, y as resolveCleoDir } from './paths-2M_UxD_H.js';
import { b as runFrontDoorLogin, S as SENTIENT_STATE_FILE } from './local-model-fit2-J1xCIhC_.js';
import { s as setConfigValue, a as getConfigValue, b as applyStrictnessPreset, l as loadConfig } from './config-B0nEjgSh.js';
import { b as getCredentialPool, c as addCredential } from './credential-pool-B04qO8at.js';
import { r as resolveProviderDefaultModel, c as catalogKeyForProvider, l as listProviderModels } from './catalog-model-resolver2-Cexk2TN0.js';
import { dirname, join } from 'node:path';
import { readFile, mkdir, writeFile, rename, access } from 'node:fs/promises';
import { e as err, p as parseJsonBody, i as isParseError, o as ok } from './_lafs-C3_fBF_X.js';
import { json } from '@sveltejs/kit';
import { existsSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import 'node:os';
import 'node:events';
import 'node:diagnostics_channel';
import 'fs';
import 'events';
import 'util';
import 'path';
import 'assert';
import 'worker_threads';
import 'module';
import 'node:url';
import 'url';
import 'buffer';
import 'node:module';
import 'node:child_process';
import 'node:process';
import 'node:async_hooks';
import './chunk-DDP5xfzh.js';
import './src2-DK8DKufK.js';
import './branch-lock-qP1EkZ7p.js';
import './conduit-Ca_OFWR_.js';
import './indexes-aYlULWG3.js';
import './utils-BvHPJOVk.js';
import './unique-constraint-BM-Apx1H.js';
import './sqlite-Dsbe0wec.js';
import './dual-scope-db-ATrwq0LH.js';
import './memory-schema-Bi7cChwo.js';
import './agent-registry-BtE5uhMO.js';
import './nexus-sqlite2-DgTuv9BQ.js';
import './nexus-schema2-BEd-ol6e.js';
import './logger-ChCwlV5k.js';
import 'node:sqlite';
import './lock-BoHF25mx.js';
import './index-IVoIz4D5.js';
import 'constants';
import 'stream';
import 'node:util';
import './index-Ca4C2M45.js';
import './role-resolver-DF9BQ_U6.js';
import './identity2-DeAB5RXE.js';
import './json-DXl7iDYg.js';

/** Default (empty) sentient state for fresh initialisation. */
var DEFAULT_SENTIENT_STATE = {
	schemaVersion: "1.0",
	pid: null,
	startedAt: null,
	lastTickAt: null,
	lastCronFiredAt: null,
	killSwitch: false,
	killSwitchReason: null,
	pausedByRevert: false,
	revertReceiptId: null,
	stats: {
		tasksPicked: 0,
		tasksCompleted: 0,
		tasksFailed: 0,
		ticksExecuted: 0,
		ticksKilled: 0
	},
	stuckTasks: {},
	stuckTimestamps: [],
	activeTaskId: null,
	tier2Enabled: false,
	tier2Stats: {
		proposalsGenerated: 0,
		proposalsAccepted: 0,
		proposalsRejected: 0
	},
	tier3Enabled: false,
	tier3LastTickAt: null,
	tier3Stats: {
		ticksKilled: 0,
		abortsTotal: 0,
		mergesCompleted: 0
	},
	hygieneLastRunAt: null,
	hygieneSummary: null,
	hygieneStats: {
		projectsChecked: 0,
		projectsHealthy: 0,
		tempGcCandidates: 0,
		duplicateEpicGroups: 0,
		worktreesPruned: 0
	}
};
/**
* Read the sentient state from disk.
*
* Returns the default state if the file does not exist or is malformed.
* Never throws — absence is not an error.
*
* @param statePath - Absolute path to sentient-state.json
*/
async function readSentientState(statePath) {
	try {
		const raw = await readFile(statePath, "utf-8");
		const parsed = JSON.parse(raw);
		return {
			...DEFAULT_SENTIENT_STATE,
			...parsed,
			stats: {
				...DEFAULT_SENTIENT_STATE.stats,
				...parsed.stats ?? {}
			},
			stuckTasks: parsed.stuckTasks ?? {},
			stuckTimestamps: parsed.stuckTimestamps ?? [],
			tier2Enabled: parsed.tier2Enabled ?? false,
			tier2Stats: {
				...DEFAULT_SENTIENT_STATE.tier2Stats,
				...parsed.tier2Stats ?? {}
			},
			pausedByRevert: parsed.pausedByRevert ?? false,
			revertReceiptId: parsed.revertReceiptId ?? null,
			tier3Enabled: parsed.tier3Enabled ?? false,
			tier3LastTickAt: parsed.tier3LastTickAt ?? null,
			tier3Stats: {
				...DEFAULT_SENTIENT_STATE.tier3Stats,
				...parsed.tier3Stats ?? {}
			},
			hygieneLastRunAt: parsed.hygieneLastRunAt ?? null,
			hygieneSummary: parsed.hygieneSummary ?? null,
			hygieneStats: {
				...DEFAULT_SENTIENT_STATE.hygieneStats,
				...parsed.hygieneStats ?? {}
			}
		};
	} catch {
		return { ...DEFAULT_SENTIENT_STATE };
	}
}
/**
* Write the sentient state to disk atomically via tmp-then-rename.
*
* Atomic write prevents partial reads if the daemon crashes mid-write.
*
* @param statePath - Absolute path to sentient-state.json
* @param state - State to persist
*/
async function writeSentientState(statePath, state) {
	const dir = dirname(statePath);
	await mkdir(dir, { recursive: true });
	const tmpPath = join(dir, `.sentient-state-${process.pid}.tmp`);
	await writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
	await rename(tmpPath, statePath);
}
/**
* Patch a subset of fields in the sentient state file.
*
* Reads current state, merges patch, writes back. Nested `stats` merges
* with existing stats (never clobbered wholesale).
*
* @param statePath - Absolute path to sentient-state.json
* @param patch - Partial state to merge over the existing state
* @returns The merged state that was written to disk.
*/
async function patchSentientState(statePath, patch) {
	const current = await readSentientState(statePath);
	const updated = {
		...current,
		...patch,
		stats: {
			...current.stats,
			...patch.stats ?? {}
		}
	};
	await writeSentientState(statePath, updated);
	return updated;
}

//#region ../core/dist/setup/sections/brain.js
/**
* `brain` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9425).
*
* Controls how BRAIN context is surfaced inside AGENTS.md by toggling
* `brain.memoryBridge.mode` across three operator-visible values:
*
*   - `'digest'`   — AGENTS.md gets a `cleo memory digest --brief` directive
*                    (no `.cleo/memory-bridge.md` written). Wire value stays
*                    `'cli'` so existing project configs continue to resolve
*                    correctly; the wizard label was renamed to "digest" to
*                    match the user-facing CLI command.
*   - `'file'`     — Legacy behaviour: write `.cleo/memory-bridge.md` and
*                    `.cleo/nexus-bridge.md` so they get `@`-injected.
*   - `'disabled'` — Suppress BRAIN-driven AGENTS.md augmentation entirely.
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when `brain.memoryBridge.mode` is set (BRAIN-6).
*   - Retention days prompt (BRAIN-3).
*   - Embedding toggle prompt (BRAIN-4).
*   - Displays current bridge mode, embedding state, and retention days (BRAIN-1).
*   - Section description printed before prompts (GEN-6).
*
* Non-interactive contract:
*   - `options.nonInteractive === true` + `options.brainBridgeMode` →
*     persist that value; no prompts.
*   - Missing `--brain-bridge-mode` under `--non-interactive` →
*     section short-circuits with error.
*
* @task T9425
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.6 (BRAIN-1 through BRAIN-6)
* @see docs/plans/E-CONFIG-AUTH-UNIFY.md §5.3 T-E3-6
*/
/**
* Operator-facing mode labels the wizard offers.
*
* `'digest'` is a label-only synonym for the on-disk `'cli'` mode — see the
* file-level doc comment for the rationale. The wizard normalises every
* answer through {@link toWireMode} before persisting.
*/
var WIZARD_MODE_CHOICES = [
	"digest",
	"file",
	"disabled"
];
/**
* Translate an operator-facing label to the wire value persisted into
* `brain.memoryBridge.mode`.
*
* Keeps existing project configs (which use `'cli'`) functioning unchanged
* while exposing the more discoverable `'digest'` label to operators.
*
* @internal
*/
function toWireMode(label) {
	switch (label) {
		case "digest": return "cli";
		case "file": return "file";
		case "disabled": return "disabled";
	}
}
/**
* Translate the on-disk wire value back to its operator-facing label so
* the wizard can echo the current state without confusing the user with
* `'cli'`.
*
* @internal
*/
function fromWireMode(mode) {
	switch (mode) {
		case "file": return "file";
		case "disabled": return "disabled";
		default: return "digest";
	}
}
/**
* Parse retention days from a string input, returning the numeric value or
* `null` if the input is invalid.
*
* Valid input: non-negative integer (0 = forever).
*
* @param raw - String to parse.
* @returns Parsed non-negative integer or `null` on invalid input.
* @internal
*/
function parseRetentionDays(raw) {
	if (raw.trim() === "") return 0;
	const n = Number(raw.trim());
	if (!Number.isInteger(n) || n < 0 || !Number.isFinite(n)) return null;
	return n;
}
/**
* Build the `brain` section runner.
*
* @returns A {@link WizardSectionRunner} for the BRAIN section.
* @task T9425
* @task T9610
*/
function createBrainSection() {
	return {
		section: "brain",
		title: "BRAIN memory bridge (digest|file|disabled)",
		optional: true,
		async isConfigured(options) {
			const resolved = await getConfigValue("brain.memoryBridge.mode", options.projectRoot);
			return resolved.source !== "default" && resolved.value !== void 0;
		},
		async run(io, options) {
			io.info("Configures the BRAIN memory bridge mode, retention policy, and embedding index.\nSettings are stored in the global config (~/.cleo/config.json).\n\"digest\" mode injects a live memory digest; \"file\" mode writes static bridge files.");
			const resolved = await getConfigValue("brain.memoryBridge.mode", options.projectRoot);
			const currentLabel = fromWireMode(resolved.value);
			io.info(`Current BRAIN bridge mode: ${currentLabel} (source: ${resolved.source})`);
			const embeddingResolved = await getConfigValue("brain.embedding.enabled", options.projectRoot);
			const embeddingEnabled = embeddingResolved.value ?? false;
			io.info(`Current BRAIN embedding index: ${embeddingEnabled ? "enabled" : "disabled"} (source: ${embeddingResolved.source})`);
			const retentionResolved = await getConfigValue("brain.retention.days", options.projectRoot);
			const retentionDays = retentionResolved.value ?? 0;
			io.info(`Current BRAIN retention: ${retentionDays === 0 ? "forever (0)" : `${retentionDays} days`} (source: ${retentionResolved.source})`);
			let choice;
			let chosenRetentionDays;
			let chosenEmbeddingEnabled;
			if (options.nonInteractive === true) {
				if (!options.brainBridgeMode) throw new Error("E_SETUP_MISSING_FLAG: --section brain --non-interactive requires --brain-bridge-mode <digest|file|disabled>");
				choice = options.brainBridgeMode;
				if (options.brainRetentionDays !== void 0) chosenRetentionDays = options.brainRetentionDays;
				if (options.brainEmbeddingEnabled !== void 0) chosenEmbeddingEnabled = options.brainEmbeddingEnabled;
			} else {
				choice = await io.select("Pick BRAIN memory bridge mode (digest|file|disabled)", WIZARD_MODE_CHOICES);
				let validRetention = false;
				while (!validRetention) {
					const rawDays = await io.prompt("How long should BRAIN retain memory entries? [days, 0 = forever, default 0]");
					const parsed = parseRetentionDays(rawDays);
					if (parsed === null) io.warn(`Invalid value '${rawDays}' — must be a non-negative integer. Try again.`);
					else {
						chosenRetentionDays = parsed;
						validRetention = true;
					}
				}
				chosenEmbeddingEnabled = await io.confirm("Enable BRAIN embedding index (enables semantic search, requires local disk)?", true);
			}
			await setConfigValue("brain.memoryBridge.mode", toWireMode(choice), options.projectRoot, { global: true });
			const fragments = [`set brain.memoryBridge.mode=${choice} (was ${currentLabel})`];
			if (chosenRetentionDays !== void 0) {
				await setConfigValue("brain.retention.days", chosenRetentionDays, options.projectRoot, { global: true });
				fragments.push(`set brain.retention.days=${chosenRetentionDays === 0 ? "forever (0)" : chosenRetentionDays}`);
			}
			if (chosenEmbeddingEnabled !== void 0) {
				await setConfigValue("brain.embedding.enabled", chosenEmbeddingEnabled, options.projectRoot, { global: true });
				fragments.push(`set brain.embedding.enabled=${chosenEmbeddingEnabled}`);
			}
			return {
				changed: true,
				summary: fragments.join(" + ")
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/sections/harness.js
/**
* `harness` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9425).
*
* Captures which harness CLEO should consider "active" for spawn-prompt
* resolution and status reporting. Two operator-visible values are
* accepted today:
*   - `'pi'`          — the native CLEO Pi binary harness (default for
*                       headless installs and Docker tasks).
*   - `'claude-code'` — Claude Code CLI as the harness (auto-injects
*                       `AGENTS.md`, triggers tier-1 dedup).
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when `harness.active` is set (HARN-6).
*   - Pi URL prompt when `pi` is selected (HARN-3).
*   - Claude Code note when `claude-code` is selected (HARN-4).
*   - Section description printed before prompts (GEN-6).
*
* The wizard surfaces the *currently active* harness from `CLEO_HARNESS`
* (falling back to `'unknown'` — mirrors `packages/core/src/status/index.ts`)
* and then asks the operator to pick the canonical value. The selection
* lands in the **global** config under `harness.active` via
* {@link setConfigValue} — the same scope `identity` uses for `agent.name`
* so the choice persists across projects without leaking into per-project
* configs.
*
* Non-interactive contract:
*   - `options.nonInteractive === true` + `options.harness` →
*     persist that value to global config; no prompts.
*   - Missing `--harness` under `--non-interactive` →
*     section short-circuits with error.
*
* @task T9425
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.5 (HARN-1 through HARN-6)
* @see docs/plans/E-CONFIG-AUTH-UNIFY.md §5.3 T-E3-6
*/
/**
* Default Pi process URL (HARN-3).
*
* Used as the default when prompting for the Pi URL.
*/
var DEFAULT_PI_URL = "http://localhost:7800";
/**
* Allowed harness values the wizard offers + persists.
*
* Kept aligned with {@link CleoStatus.harness.active} so any value written
* here round-trips cleanly through `cleo status`.
*/
var HARNESS_CHOICES = ["pi", "claude-code"];
/**
* Validate a URL string is a valid HTTP(S) URL.
*
* Used for Pi URL validation per HARN-3.
*
* @param raw - String to validate.
* @returns `true` when the string is a valid HTTP or HTTPS URL.
* @internal
*/
function isValidHttpUrl$1(raw) {
	try {
		const url = new URL(raw);
		return url.protocol === "http:" || url.protocol === "https:";
	} catch {
		return false;
	}
}
/**
* Build the `harness` section runner.
*
* @returns A {@link WizardSectionRunner} for the harness section.
* @task T9425
* @task T9610
*/
function createHarnessSection() {
	return {
		section: "harness",
		title: "Active harness (Pi vs Claude Code)",
		optional: true,
		async isConfigured(options) {
			const resolved = await getConfigValue("harness.active", options.projectRoot);
			return typeof resolved.value === "string" && resolved.value.trim().length > 0;
		},
		async run(io, options) {
			io.info("Configures the active harness written to `harness.active` in the global config.\nPi harness is recommended for headless/Docker environments.\nClaude Code harness auto-injects AGENTS.md and triggers tier-1 dedup.");
			const current = readActiveHarness();
			io.info(`Current harness: ${current}`);
			let choice;
			if (options.nonInteractive === true) {
				if (!options.harness) throw new Error("E_SETUP_MISSING_FLAG: --section harness --non-interactive requires --harness <pi|claude-code>");
				choice = options.harness;
			} else choice = await io.select("Pick the active harness (pi|claude-code)", HARNESS_CHOICES);
			await setConfigValue("harness.active", choice, options.projectRoot, { global: true });
			const fragments = [`set harness.active=${choice} (was ${current})`];
			if (choice === "pi") if (options.nonInteractive === true) io.info(`Pi URL defaults to ${DEFAULT_PI_URL}. Set CLEO_PI_URL or run 'cleo setup --section harness' to change.`);
			else {
				const currentPiUrl = await getConfigValue("harness.piUrl", options.projectRoot);
				const piUrlDefault = typeof currentPiUrl.value === "string" && currentPiUrl.value.trim().length > 0 ? currentPiUrl.value : DEFAULT_PI_URL;
				io.info(`Current Pi URL: ${piUrlDefault}`);
				const rawPiUrl = (await io.prompt(`Pi process URL [default: ${piUrlDefault}]:`)).trim();
				const piUrl = rawPiUrl === "" ? piUrlDefault : rawPiUrl;
				if (!isValidHttpUrl$1(piUrl)) {
					io.warn(`Invalid Pi URL '${piUrl}' — must be a valid HTTP(S) URL. Using default: ${piUrlDefault}`);
					await setConfigValue("harness.piUrl", piUrlDefault, options.projectRoot, { global: true });
					fragments.push(`set harness.piUrl=${piUrlDefault} (invalid input, used default)`);
				} else {
					await setConfigValue("harness.piUrl", piUrl, options.projectRoot, { global: true });
					fragments.push(`set harness.piUrl=${piUrl}`);
				}
			}
			if (choice === "claude-code") io.info("Ensure `claude` is on your PATH. Run `cleo harness doctor` to verify.");
			return {
				changed: true,
				summary: fragments.join(" + ")
			};
		}
	};
}
/**
* Read the active harness for display purposes using the same layered chain as
* {@link detectHarness} in `packages/core/src/status/index.ts`.
*
* Synchronous (reads env only; skips the async global-config layer) so the
* wizard can call it without `await` before the prompt is shown.  The
* full async chain runs at status-snapshot time via `cleo status`.
*
* Resolution order (first match wins):
* 1. `CLEO_HARNESS` env var — explicit override.
* 2. `CLAUDECODE=1` → `'claude-code'`.
* 3. `CLEO_PI=1`    → `'pi'`.
* 4. Fallback: `'unknown'`.
*
* @internal
*/
function readActiveHarness() {
	const raw = process.env["CLEO_HARNESS"];
	if (raw === "pi" || raw === "claude-code") return raw;
	if (process.env["CLAUDECODE"] === "1") return "claude-code";
	if (process.env["CLEO_PI"] === "1") return "pi";
	return "unknown";
}
//#endregion
//#region ../core/dist/setup/sections/identity.js
/**
* `identity` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Captures the operator-visible agent display name and (optionally)
* a SOUL.md persona block. Both pieces are stored away from secrets:
*   - `agent.name` lands in the global config via `setConfigValue`.
*   - SOUL.md content lands in `<projectRoot>/.cleo/SOUL.md` so it is
*     scoped to the current project. Future Studio/CLI surfaces can
*     read it via the standard config layer.
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when `agent.name` is already set.
*   - Current-value display before prompting (GEN-7).
*   - SignalDock registration note (IDENT-5).
*   - Section description printed before prompts (GEN-6).
*
* Non-interactive contract:
*   - `options.nonInteractive === true` + `options.agentName` →
*     writes the name and (optionally) SOUL.md content; no prompts.
*   - Missing `--agent-name` under `--non-interactive` →
*     section short-circuits silently (`changed: false`).
*
* @task T9420
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.2
*/
/** Relative path inside `.cleo/` where SOUL.md is persisted. */
var SOUL_RELATIVE_PATH = "SOUL.md";
/**
* Build the `identity` section runner.
*
* @returns A {@link WizardSectionRunner} for the identity section.
* @task T9420
* @task T9610
*/
function createIdentitySection() {
	return {
		section: "identity",
		title: "Agent identity (name + optional SOUL.md persona)",
		optional: true,
		async isConfigured(options) {
			const resolved = await getConfigValue("agent.name", options.projectRoot);
			return typeof resolved.value === "string" && resolved.value.trim().length > 0;
		},
		async run(io, options) {
			io.info("Configures the agent display name written to `agent.name` in the global config\nand an optional SOUL.md persona block scoped to this project (.cleo/SOUL.md).\nSignalDock identity registration is prompted at the end of this section.");
			const currentName = await getConfigValue("agent.name", options.projectRoot);
			if (typeof currentName.value === "string" && currentName.value.trim().length > 0) io.info(`Current agent name: ${currentName.value} (source: ${currentName.source})`);
			else io.info("Current agent name: (not set)");
			if (options.nonInteractive === true) {
				if (!options.agentName) return {
					changed: false,
					summary: "skipped (non-interactive: --agent-name required)"
				};
				const writes = await applyIdentity(options.agentName, options.soulMdContent ?? null, options.projectRoot);
				if (options.signaldockAutoConnect === true) io.info("To register a SignalDock identity, run: cleo signaldock connect");
				return summariseWrites(writes);
			}
			const name = (await io.prompt("Agent display name (e.g. \"Atlas\")")).trim();
			if (name === "") {
				io.info("No name supplied — leaving identity unchanged.");
				return {
					changed: false,
					summary: "skipped (no name provided)"
				};
			}
			let soulContent = null;
			if (await io.confirm("Add a SOUL.md persona block now?", false)) {
				const entered = (await io.prompt("Paste SOUL.md content (single line ok):")).trim();
				soulContent = entered === "" ? null : entered;
			}
			if (await io.confirm("Register a SignalDock identity for this agent?", false)) io.info("To register a SignalDock identity, run: cleo signaldock connect");
			return summariseWrites(await applyIdentity(name, soulContent, options.projectRoot));
		}
	};
}
/**
* Apply identity writes to disk.
*
* Routed through {@link setConfigValue} (global scope) for the name and
* through a direct atomic file write for SOUL.md. Both writes are best-effort
* — caller of {@link createIdentitySection} surfaces failures via
* {@link WizardIO.error} through the {@link WizardRunner} guard.
*
* @internal
*/
async function applyIdentity(name, soulContent, projectRoot) {
	await setConfigValue("agent.name", name, projectRoot, { global: true });
	let soulPath = null;
	if (soulContent && soulContent.length > 0) {
		soulPath = join(resolveCleoDir(projectRoot), SOUL_RELATIVE_PATH);
		await mkdir(dirname(soulPath), { recursive: true });
		await writeFile(soulPath, soulContent.endsWith("\n") ? soulContent : `${soulContent}\n`, {
			encoding: "utf-8",
			mode: 420
		});
	}
	return {
		nameWritten: true,
		soulPath
	};
}
/**
* Compose a single-line summary from the {@link applyIdentity} write report.
*
* @internal
*/
function summariseWrites(writes) {
	const fragments = [];
	if (writes.nameWritten) fragments.push("set agent.name");
	if (writes.soulPath !== null) fragments.push(`wrote ${writes.soulPath}`);
	return {
		changed: fragments.length > 0,
		summary: fragments.length > 0 ? fragments.join(" + ") : "no changes"
	};
}
//#endregion
//#region ../core/dist/setup/sections/integrations.js
/**
* `integrations` setup wizard section (E-CLEO-SETUP-V2 / T9608).
*
* Captures operator intent for three integration subsystems:
*
*   1. **SignalDock** — the cloud messaging transport.
*      - `signaldock.enabled` (global config, boolean)
*      - `signaldock.endpoint` (global config, string — defaults to
*        `http://localhost:4000` when not supplied)
*
*   2. **Studio** — the local web UI.
*      - `studio.enabled` (global config, boolean)
*      - After enabling, the section emits: "Start Studio with `cleo studio start`."
*
*   3. **Conduit** — the local message-bus DB.
*      - `conduit.dbPath` (project config, string — must be an absolute path)
*      - Blank input ⇒ the built-in default is used; nothing is persisted.
*
* The section emits the current state of all three subsystems before
* prompting so operators can confirm idempotently.
*
* No network calls are made inside this section — connectivity validation
* is delegated to the `verification` section (T9594).
*
* Non-interactive contract (INTG-5):
*   - `options.nonInteractive === true` → apply each of
*     `signaldockEnabled`, `signaldockEndpoint`, `studioEnabled`, and
*     `conduitPath` when present; fields not supplied are left untouched.
*   - All four absent under non-interactive → short-circuit silently.
*
* `isConfigured()` (INTG-6):
*   - Returns `true` when `signaldock.enabled` is **explicitly set** in
*     the global config (even when the stored value is `false`), because
*     an explicit `false` means the operator intentionally disabled it.
*
* @task T9608
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.8, §5.2 T9593
*/
/** Default SignalDock endpoint shown to the operator. */
var DEFAULT_SIGNALDOCK_ENDPOINT = "http://localhost:4000";
/**
* Validate that `url` is an HTTP or HTTPS URL.
*
* Used to guard the SignalDock endpoint prompt — the section MUST NOT
* make network calls (INTG-7), so only syntactic validation is performed.
*
* @param url - Raw user input to validate.
* @returns `true` when `url` is a valid HTTP(S) URL.
* @internal
*/
function isValidHttpUrl(url) {
	try {
		const parsed = new URL(url);
		return parsed.protocol === "http:" || parsed.protocol === "https:";
	} catch {
		return false;
	}
}
/**
* Validate that `path` is an absolute filesystem path.
*
* @param p - Raw user input to validate.
* @returns `true` when `p` starts with `/` (Unix absolute path).
* @internal
*/
function isAbsolutePath(p) {
	return p.startsWith("/");
}
/**
* Build the `integrations` section runner.
*
* @returns A {@link WizardSectionRunner} for the integrations section.
* @task T9608
*/
function createIntegrationsSection() {
	return {
		section: "integrations",
		title: "Integrations (SignalDock + Studio + Conduit)",
		optional: true,
		async isConfigured(options) {
			const resolved = await getConfigValue("signaldock.enabled", options.projectRoot);
			return resolved.source !== "default" && resolved.value !== void 0;
		},
		async run(io, options) {
			const [sdEnabled, sdEndpoint, stEnabled] = await Promise.all([
				getConfigValue("signaldock.enabled", options.projectRoot),
				getConfigValue("signaldock.endpoint", options.projectRoot),
				getConfigValue("studio.enabled", options.projectRoot)
			]);
			const sdEnabledLabel = sdEnabled.value === void 0 ? "not set" : String(sdEnabled.value);
			const sdEndpointLabel = sdEndpoint.value ?? "(default)";
			const stEnabledLabel = stEnabled.value === void 0 ? "not set" : String(stEnabled.value);
			io.info(`Current integrations state:\n  signaldock.enabled  = ${sdEnabledLabel} (source: ${sdEnabled.source})\n  signaldock.endpoint = ${sdEndpointLabel} (source: ${sdEndpoint.source})\n  studio.enabled      = ${stEnabledLabel} (source: ${stEnabled.source})`);
			const fragments = [];
			if (options.nonInteractive === true) {
				const { signaldockEnabled, signaldockEndpoint, studioEnabled, conduitPath } = options;
				if (signaldockEnabled === void 0 && signaldockEndpoint === void 0 && studioEnabled === void 0 && conduitPath === void 0) return {
					changed: false,
					summary: "skipped (non-interactive: no integrations flags supplied)"
				};
				if (signaldockEnabled !== void 0) {
					await setConfigValue("signaldock.enabled", signaldockEnabled, options.projectRoot, { global: true });
					fragments.push(`signaldock.enabled=${signaldockEnabled}`);
				}
				if (signaldockEndpoint !== void 0) {
					if (!isValidHttpUrl(signaldockEndpoint)) throw new Error(`E_SETUP_INVALID_VALUE: signaldockEndpoint '${signaldockEndpoint}' is not a valid HTTP(S) URL`);
					await setConfigValue("signaldock.endpoint", signaldockEndpoint, options.projectRoot, { global: true });
					fragments.push(`signaldock.endpoint=${signaldockEndpoint}`);
				}
				if (studioEnabled !== void 0) {
					await setConfigValue("studio.enabled", studioEnabled, options.projectRoot, { global: true });
					fragments.push(`studio.enabled=${studioEnabled}`);
					if (studioEnabled) io.info("Start Studio with `cleo studio start`.");
				}
				if (conduitPath !== void 0) {
					if (!isAbsolutePath(conduitPath)) throw new Error(`E_SETUP_INVALID_VALUE: conduitPath '${conduitPath}' must be an absolute path`);
					await setConfigValue("conduit.dbPath", conduitPath, options.projectRoot, { global: false });
					fragments.push(`conduit.dbPath=${conduitPath}`);
				}
				return {
					changed: fragments.length > 0,
					summary: fragments.length > 0 ? fragments.join(" + ") : "no changes"
				};
			}
			const wantsSignalDock = await io.confirm("Enable SignalDock transport?", false);
			await setConfigValue("signaldock.enabled", wantsSignalDock, options.projectRoot, { global: true });
			fragments.push(`signaldock.enabled=${wantsSignalDock}`);
			if (wantsSignalDock) {
				let endpoint = "";
				while (true) {
					const raw = (await io.prompt(`SignalDock endpoint URL [${DEFAULT_SIGNALDOCK_ENDPOINT}]:`)).trim();
					endpoint = raw === "" ? DEFAULT_SIGNALDOCK_ENDPOINT : raw;
					if (isValidHttpUrl(endpoint)) break;
					io.warn(`'${endpoint}' is not a valid HTTP(S) URL — please try again.`);
				}
				await setConfigValue("signaldock.endpoint", endpoint, options.projectRoot, { global: true });
				fragments.push(`signaldock.endpoint=${endpoint}`);
			}
			const wantsStudio = await io.confirm("Enable Studio web UI?", false);
			await setConfigValue("studio.enabled", wantsStudio, options.projectRoot, { global: true });
			fragments.push(`studio.enabled=${wantsStudio}`);
			if (wantsStudio) io.info("Start Studio with `cleo studio start`.");
			let conduitWritten = false;
			while (true) {
				const raw = (await io.prompt("Custom Conduit DB path? [leave blank to use default]")).trim();
				if (raw === "") break;
				if (!isAbsolutePath(raw)) {
					io.warn(`'${raw}' is not an absolute path — please supply an absolute path or leave blank.`);
					continue;
				}
				await setConfigValue("conduit.dbPath", raw, options.projectRoot, { global: false });
				fragments.push(`conduit.dbPath=${raw}`);
				conduitWritten = true;
				break;
			}
			if (!conduitWritten) fragments.push("conduit.dbPath=default");
			return {
				changed: true,
				summary: fragments.join(" + ")
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/sections/llm.js
/**
* `llm` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Mirrors Hermes Agent's `setup.py` LLM block:
*   1. Display the current credential pool snapshot (provider/label/source).
*   2. Prompt for a provider id (or accept `--provider` non-interactively).
*   3. Accept an API key via `--api-key` *or* via interactive prompt.
*   4. Write the result to the credential pool — never to config.
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when pool has at least one entry (LLM-7).
*   - Bracketed paste sanitization on API key input (LLM-4).
*   - Pool-seeding consent prompt after key entry (LLM-5).
*   - Section description printed before prompts (GEN-6).
*
* Non-interactive contract:
*   - `options.nonInteractive === true` + `options.provider` + `options.apiKey`
*     → write key to pool; no prompts.
*   - Missing `--provider` or `--api-key` under `--non-interactive` →
*     section short-circuits silently (`changed: false`).
*
* @task T9420
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.3
*/
/**
* Provider ids the interactive wizard offers from the menu.
*
* Plugin providers are added later in T-E2-3; this list is the safe
* starter set drawn from `BuiltinProviderId`.
*/
var INTERACTIVE_PROVIDERS = [
	"anthropic",
	"openai",
	"gemini",
	"openrouter",
	"moonshot",
	"deepseek",
	"xai",
	"groq",
	"ollama"
];
/**
* Providers that support pool seeding consent prompt (LLM-5).
*/
var POOL_SEEDING_PROVIDERS = new Set(["anthropic", "openrouter"]);
/**
* Strip bracketed paste mode escape sequences from user input (LLM-4).
*
* Terminal emulators wrap pasted content with `\x1b[200~` (start) and
* `\x1b[201~` (end) when bracketed paste mode is enabled. These must be
* removed before persisting an API key, otherwise the key will include
* the raw escape bytes and authentication will fail.
*
* @param input - Raw string as received from the readline prompt.
* @returns Cleaned string with bracketed paste sequences stripped.
* @internal
*/
function stripBracketedPaste(input) {
	return input.replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
}
/**
* Build the `llm` section runner.
*
* Constructed via a factory so unit tests can swap `getCredentialPool`
* / `addCredential` references via module mocking without touching the
* production singleton.
*
* @returns A {@link WizardSectionRunner} for the LLM section.
* @task T9420
* @task T9610
*/
function createLlmSection(deps = {}) {
	const frontDoor = deps.runFrontDoorLogin ?? runFrontDoorLogin;
	return {
		section: "llm",
		title: "LLM provider + API key",
		optional: false,
		async isConfigured(_options) {
			try {
				return (await getCredentialPool().list()).length > 0;
			} catch {
				return false;
			}
		},
		async run(io, options) {
			io.info("Configures LLM provider credentials stored in the credential pool (brain.db).\nCredentials are never written to config files — they live in the encrypted pool.\nSupports: anthropic, openai, gemini, openrouter, moonshot, deepseek, xai, groq, ollama.");
			try {
				const entries = await getCredentialPool().list();
				if (entries.length === 0) io.info("No credentials configured yet.");
				else {
					io.info(`Current credentials (${entries.length}):`);
					for (const entry of entries) {
						const expiry = entry.expiresAt && entry.expiresAt > 0 ? ` exp=${new Date(entry.expiresAt).toISOString()}` : "";
						io.info(`  - ${entry.provider}:${entry.label} (${entry.authType}, source=${entry.source ?? "unknown"})${expiry}`);
					}
				}
			} catch (err) {
				io.warn(`Could not list current credentials: ${err instanceof Error ? err.message : String(err)}`);
			}
			if (options.nonInteractive === true) {
				if (!options.provider || !options.apiKey) return {
					changed: false,
					summary: "skipped (non-interactive: --provider and --api-key required)"
				};
				const cleanKey = stripBracketedPaste(options.apiKey);
				await persistApiKey(options.provider, cleanKey, options.label ?? "cli-input");
				if (options.poolSeedingConsent !== void 0) await setConfigValue("auth.poolSeedingConsent", options.poolSeedingConsent, void 0, { global: true });
				return {
					changed: true,
					summary: `added ${options.provider}:${options.label ?? "cli-input"} to pool`
				};
			}
			const provider = await io.select("Which LLM provider do you want to configure?", INTERACTIVE_PROVIDERS);
			if (await io.select("How will CLEO authenticate to this provider?", ["api_key", "oauth_login"]) === "oauth_login") {
				if (!deps.oauthAcquirer) {
					io.info(`OAuth login for '${provider}' needs an interactive terminal. Run \`cleo login ${provider}\` (or \`cleo llm login ${provider}\`) to complete it.`);
					return {
						changed: false,
						summary: `oauth login pending for ${provider} (run cleo login)`
					};
				}
				const result = await frontDoor(provider, { authMode: "oauth" }, deps.oauthAcquirer);
				if (result.validated) {
					io.info(`Logged in to ${result.provider} — bound ${result.profileName ?? "default"} → ${result.provider}/${result.modelId}.`);
					return {
						changed: true,
						summary: `oauth login complete for ${result.provider} (${result.modelId})`
					};
				}
				const failed = result.steps.find((s) => s.status === "failed");
				io.warn(`OAuth login did not complete: ${failed?.detail ?? "unknown error"}`);
				return {
					changed: false,
					summary: `oauth login failed for ${provider}`
				};
			}
			const label = await io.prompt("Label for this credential [default: cli-input]:") || "cli-input";
			const apiKey = stripBracketedPaste((await io.prompt("API key (input not echoed in production):")).trim());
			if (apiKey === "") {
				io.warn("No API key supplied — leaving section unchanged.");
				return {
					changed: false,
					summary: "skipped (empty api key)"
				};
			}
			await persistApiKey(provider, apiKey, label);
			if (POOL_SEEDING_PROVIDERS.has(provider)) {
				const consent = await io.confirm("Consent to including this key in CLEO's credential pool for multi-agent distribution?", false);
				await setConfigValue("auth.poolSeedingConsent", consent, void 0, { global: true });
				io.info(`Pool-seeding consent: ${consent ? "granted" : "denied"} (saved to auth.poolSeedingConsent)`);
			}
			return {
				changed: true,
				summary: `added ${provider}:${label} to pool`
			};
		}
	};
}
/**
* Upsert an API-key entry into the credential pool.
*
* Keeps the policy "secrets live in the pool, never in config" enforced
* by routing every write through {@link addCredential}.
*
* @internal
*/
async function persistApiKey(provider, apiKey, label) {
	await addCredential({
		provider,
		label,
		authType: "api_key",
		accessToken: apiKey,
		source: "cli-input"
	});
}
//#endregion
//#region ../core/dist/setup/sections/models-roles.js
/**
* `models-roles` setup wizard section (T11726 · M3).
*
* Sits immediately after the `llm` section in the canonical order. Where `llm`
* lands a credential in the pool, this section binds WHAT to use:
*
*   1. **default model** — pick the global default model (`cleo llm use`
*      equivalent → `llm.default.model`), sourcing choices from the live
*      models.dev catalog for the default provider.
*   2. **per-role profiles** — optionally pin each role in the canonical
*      {@link WHOAMI_ROLE_IDS} set to a provider/model (`cleo llm profile`
*      equivalent → `llm.roles.<role>`).
*
* ## Non-interactive contract (AC3)
*
* Driveable entirely from {@link WizardOptions}:
*   - `options.defaultModel` → write the global default model.
*   - `options.roleBindings[role]` → write `llm.roles.<role>` for each entry.
* Missing inputs short-circuit cleanly with `changed: false` (never a throw,
* never a half-written binding).
*
* ## Idempotency (AC4)
*
* `isConfigured()` returns `true` when a default model OR any role binding is
* already present in the global config, so the wizard skips the section unless
* `--reset` is passed.
*
* @module setup/sections/models-roles
* @task T11726
* @epic T11671 (E6-ONBOARDING-FRONT-DOOR)
*/
/**
* Sentinel option offered in interactive pickers meaning "leave this unset".
*
* @internal
*/
var SKIP_CHOICE = "(skip)";
/**
* Read the current global LLM config block (best-effort — returns `{}` on any
* read error so the section degrades gracefully on a fresh install).
*
* @internal
*/
async function readLlmConfig() {
	try {
		const llm = (await loadConfig()).llm;
		const def = llm?.default;
		return {
			defaultProvider: def?.provider,
			defaultModel: def?.model,
			roles: llm?.roles ?? {}
		};
	} catch {
		return {};
	}
}
/**
* Build the model choice list for a provider from the catalog, capped for a
* scannable menu. Always includes the {@link SKIP_CHOICE} sentinel last.
*
* @internal
*/
function modelChoicesForProvider(provider) {
	return [...listProviderModels(catalogKeyForProvider(provider)), SKIP_CHOICE];
}
/**
* Build the `models-roles` section runner (T11726).
*
* @returns A {@link WizardSectionRunner} for the models/roles section.
* @task T11726
*/
function createModelsRolesSection() {
	return {
		section: "models-roles",
		title: "Default model + per-role profiles",
		optional: true,
		async isConfigured(_options) {
			const cfg = await readLlmConfig();
			const hasDefaultModel = typeof cfg.defaultModel === "string" && cfg.defaultModel !== "";
			const hasRole = Boolean(cfg.roles && Object.keys(cfg.roles).length > 0);
			return hasDefaultModel || hasRole;
		},
		async run(io, options) {
			io.info("Selects the default model (cleo llm use) and optional per-role profiles (cleo llm profile).\nModel choices come from the cached models.dev catalog — run `cleo llm refresh-catalog` to update it.");
			const cfg = await readLlmConfig();
			const defaultProvider = options.provider ?? cfg.defaultProvider;
			const changes = [];
			if (options.nonInteractive === true) {
				if (options.defaultModel) {
					await setConfigValue("llm.default.model", options.defaultModel, void 0, { global: true });
					if (defaultProvider) await setConfigValue("llm.default.provider", defaultProvider, void 0, { global: true });
					changes.push(`default model → ${options.defaultModel}`);
				}
				for (const [role, binding] of Object.entries(options.roleBindings ?? {})) {
					if (!isWhoamiRole(role)) continue;
					let model = binding.model;
					if (!model) model = resolveProviderDefaultModel(catalogKeyForProvider(binding.provider)) ?? void 0;
					if (!model) {
						io.warn(`Skipping role binding ${role} → ${binding.provider}: no model supplied and no catalog default found for '${binding.provider}'. Re-run with {"${role}": {"provider": "${binding.provider}", "model": "<id>"}}.`);
						continue;
					}
					await writeRoleBinding(role, {
						...binding,
						model
					});
					changes.push(`${role} → ${binding.provider}/${model}`);
				}
				if (changes.length === 0) return {
					changed: false,
					summary: "skipped (non-interactive: no --default-model / role bindings supplied)"
				};
				return {
					changed: true,
					summary: changes.join("; ")
				};
			}
			if (!defaultProvider) {
				io.info("No default provider configured yet — run the LLM section (or `cleo login`) first, then re-run setup to pin models/roles.");
				return {
					changed: false,
					summary: "skipped (no provider configured)"
				};
			}
			if (defaultProvider === "ollama") {
				const modelPicked = await _pickOllamaModelInteractive(io);
				if (modelPicked) {
					await setConfigValue("llm.default.model", modelPicked, void 0, { global: true });
					await setConfigValue("llm.default.provider", "ollama", void 0, { global: true });
					changes.push(`default model → ${modelPicked}`);
				}
			} else {
				const modelChoices = modelChoicesForProvider(defaultProvider);
				if (modelChoices.length > 1) {
					const picked = await io.select(`Default model for ${defaultProvider}?`, modelChoices);
					if (picked !== SKIP_CHOICE) {
						await setConfigValue("llm.default.model", picked, void 0, { global: true });
						await setConfigValue("llm.default.provider", defaultProvider, void 0, { global: true });
						changes.push(`default model → ${picked}`);
					}
				} else {
					const latest = resolveProviderDefaultModel(catalogKeyForProvider(defaultProvider));
					const model = (await io.prompt(`Default model for ${defaultProvider}${latest ? ` [${latest}]` : ""} (blank to skip):`)).trim() || latest || "";
					if (model) {
						await setConfigValue("llm.default.model", model, void 0, { global: true });
						await setConfigValue("llm.default.provider", defaultProvider, void 0, { global: true });
						changes.push(`default model → ${model}`);
					}
				}
			}
			if (await io.confirm("Pin per-role profiles now? (advanced)", false)) for (const role of WHOAMI_ROLE_IDS) {
				const choices = modelChoicesForProvider(defaultProvider);
				const picked = await io.select(`Model for role '${role}' on ${defaultProvider}?`, choices);
				if (picked !== SKIP_CHOICE) {
					await writeRoleBinding(role, {
						provider: defaultProvider,
						model: picked
					});
					changes.push(`${role} → ${defaultProvider}/${picked}`);
				}
			}
			if (changes.length === 0) return {
				changed: false,
				summary: "no models/roles changes"
			};
			return {
				changed: true,
				summary: changes.join("; ")
			};
		}
	};
}
/**
* Narrow an arbitrary string to a {@link RoleName} in the whoami-enumerable set.
*
* @internal
*/
function isWhoamiRole(role) {
	return WHOAMI_ROLE_IDS.includes(role);
}
/**
* Write a per-role binding (`llm.roles.<role>.{provider,model,credentialLabel}`)
* to the global config.
*
* @internal
*/
async function writeRoleBinding(role, binding) {
	await setConfigValue(`llm.roles.${role}.provider`, binding.provider, void 0, { global: true });
	if (binding.model) await setConfigValue(`llm.roles.${role}.model`, binding.model, void 0, { global: true });
	if (binding.credentialLabel) await setConfigValue(`llm.roles.${role}.credentialLabel`, binding.credentialLabel, void 0, { global: true });
}
/**
* The default ranker used in production: calls the real `rankLocalModelFit`.
*
* Lazy import keeps this module free of heavy OS-level side effects at parse
* time (Gate-13 compliance: no transport construction at import).
*
* @internal
*/
async function defaultLocalModelFitRanker() {
	const { rankLocalModelFit } = await import('./local-model-fit-D20C-MZj.js');
	return rankLocalModelFit();
}
/**
* Interactive Ollama model picker driven by fit-gated recommendations (T11983).
*
* Behaviour:
* - Calls `rankLocalModelFit` to detect hardware + Ollama state.
* - Below the 4 GB floor → informs the user (cloud-only guidance) and returns
*   `null` (caller skips the binding).
* - Above the floor with recommendations → presents a ranked pick list of
*   2–3 candidates (never auto-selects, never auto-pulls — RECOMMEND-NEVER-KILL).
* - Already-pulled models are surfaced first with a `[pulled]` tag.
* - Free-text fallback offered as the last option so the user can type any tag.
* - Returns the chosen model tag, or `null` if the user skipped.
*
* The ranker is injectable so tests can supply a stub envelope without
* hitting the OS or network.
*
* @param io - Wizard I/O surface.
* @param ranker - Override the fit ranker for tests (defaults to the real one).
* @returns The chosen Ollama model tag, or `null` when the user skipped / below floor.
*
* @task T11983
*/
async function _pickOllamaModelInteractive(io, ranker = defaultLocalModelFitRanker) {
	io.info("Detecting local hardware for Ollama model fit ranking…");
	let fitEnvelope;
	try {
		fitEnvelope = await ranker();
	} catch (err) {
		io.warn(`Hardware detection failed: ${err instanceof Error ? err.message : String(err)}. Falling back to manual model entry.`);
		return (await io.prompt("Enter Ollama model tag (e.g. gemma4:e2b) or blank to skip:")).trim() || null;
	}
	const hw = fitEnvelope.hardware;
	io.info(`  Hardware: ${hw.totalRamGb.toFixed(1)} GB RAM, ` + (hw.vramTotalGb !== null ? `${hw.vramTotalGb.toFixed(1)} GB VRAM` : "no GPU detected") + (fitEnvelope.ollamaRunning ? ", Ollama running" : ", Ollama not running"));
	if (fitEnvelope.noRecommendationReason) {
		io.warn(`\nLocal model recommendation not available: ${fitEnvelope.noRecommendationReason}\nThis machine has ${hw.totalRamGb.toFixed(1)} GB RAM — local LLM inference requires at least 4 GB.\nPlease use a cloud provider (anthropic, openai, gemini, etc.) instead.`);
		return null;
	}
	const recs = fitEnvelope.recommendations;
	if (recs.length === 0) {
		io.warn("No local model candidates fit this machine. Consider a cloud provider.");
		return (await io.prompt("Enter Ollama model tag manually (blank to skip):")).trim() || null;
	}
	io.info("\nRecommended local models (ranked by hardware fit):");
	const choices = recs.map((r) => {
		return `${r.candidate.modelTag}${r.alreadyPulled ? " [pulled]" : ""} (${r.fitTier})`;
	});
	choices.push("(enter manually)");
	choices.push(SKIP_CHOICE);
	const picked = await io.select("Choose a local model for Ollama:", choices);
	if (picked === SKIP_CHOICE) return null;
	if (picked === "(enter manually)") return (await io.prompt("Enter Ollama model tag (e.g. gemma4:e2b):")).trim() || null;
	return picked.split(" ")[0] ?? picked;
}
//#endregion
//#region ../core/dist/setup/sections/project-conventions.js
/**
* `project-conventions` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Applies one of CLEO's three strictness presets (`strict` / `standard` /
* `minimal`) via the existing {@link applyStrictnessPreset} helper.
* Project-scoped: the preset is written to the *project* config so each
* repository can have its own enforcement tier.
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when project config has at least one
*     strictness-related key (PROJ-5).
*   - Current values display before prompting (GEN-7 / PROJ-1).
*   - AC enforcement mode override prompt (PROJ-3).
*   - Session auto-start override prompt (PROJ-3).
*   - Section description printed before prompts (GEN-6).
*
* Non-interactive contract:
*   - `options.nonInteractive === true` + `options.strictness` →
*     apply that preset; no prompts.
*   - `options.acEnforcementMode` and `options.sessionAutoStart` apply
*     fine-grained overrides when present (PROJ-4).
*   - Missing `--strictness` under `--non-interactive` →
*     section short-circuits silently (`changed: false`).
*
* @task T9420
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.7 (PROJ-1 through PROJ-5)
*/
var PRESET_CHOICES = [
	"strict",
	"standard",
	"minimal"
];
/** AC enforcement mode choices for the override prompt. */
var AC_ENFORCEMENT_CHOICES = [
	"block",
	"warn",
	"off",
	"keep-preset-default"
];
/** Session auto-start choices for the override prompt. */
var SESSION_AUTO_START_CHOICES = [
	"yes",
	"no",
	"keep-preset-default"
];
/**
* Build the `project-conventions` section runner.
*
* @returns A {@link WizardSectionRunner} for the conventions section.
* @task T9420
* @task T9610
*/
function createProjectConventionsSection() {
	return {
		section: "project-conventions",
		title: "Project conventions (strictness preset)",
		optional: true,
		async isConfigured(options) {
			const acMode = await getConfigValue("enforcement.acceptance.mode", options.projectRoot);
			const requireNotes = await getConfigValue("session.requireNotes", options.projectRoot);
			const acConfigured = acMode.source !== "default" && acMode.value !== void 0;
			const notesConfigured = requireNotes.source !== "default" && requireNotes.value !== void 0;
			return acConfigured || notesConfigured;
		},
		async run(io, options) {
			io.info("Configures project-level enforcement conventions written to `.cleo/config.json`.\nPresets (strict / standard / minimal) control AC enforcement, session policy, and lifecycle.\nFine-grained overrides can be applied after choosing a preset.");
			const currentAcMode = await getConfigValue("enforcement.acceptance.mode", options.projectRoot);
			const currentAutoStart = await getConfigValue("session.autoStart", options.projectRoot);
			io.info(`Current AC enforcement mode: ${currentAcMode.value ?? "(not set)"} (source: ${currentAcMode.source})`);
			io.info(`Current session auto-start: ${currentAutoStart.value ?? "(not set)"} (source: ${currentAutoStart.source})`);
			let preset;
			if (options.nonInteractive === true) {
				if (!options.strictness) return {
					changed: false,
					summary: "skipped (non-interactive: --strictness required)"
				};
				preset = options.strictness;
			} else preset = await io.select("Pick a strictness preset (strict|standard|minimal)", PRESET_CHOICES);
			const result = await applyStrictnessPreset(preset, options.projectRoot, { global: false });
			const keyCount = Object.keys(result.applied).length;
			const fragments = [`applied '${result.preset}' preset (${keyCount} keys to ${result.scope} config)`];
			let acOverride;
			if (options.nonInteractive === true) {
				if (options.acEnforcementMode !== void 0) acOverride = options.acEnforcementMode;
			} else {
				const acChoice = await io.select("Override AC enforcement mode? [block / warn / off / keep-preset-default]", AC_ENFORCEMENT_CHOICES);
				if (acChoice !== "keep-preset-default") acOverride = acChoice;
			}
			if (acOverride !== void 0) {
				await setConfigValue("enforcement.acceptance.mode", acOverride, options.projectRoot, { global: false });
				fragments.push(`set enforcement.acceptance.mode=${acOverride}`);
			}
			let sessionAutoStartOverride;
			if (options.nonInteractive === true) {
				if (options.sessionAutoStart !== void 0) sessionAutoStartOverride = options.sessionAutoStart;
			} else {
				const sessionChoice = await io.select("Override session auto-start? [yes / no / keep-preset-default]", SESSION_AUTO_START_CHOICES);
				if (sessionChoice === "yes") sessionAutoStartOverride = true;
				else if (sessionChoice === "no") sessionAutoStartOverride = false;
			}
			if (sessionAutoStartOverride !== void 0) {
				await setConfigValue("session.autoStart", sessionAutoStartOverride, options.projectRoot, { global: false });
				fragments.push(`set session.autoStart=${sessionAutoStartOverride}`);
			}
			return {
				changed: true,
				summary: fragments.join(" + ")
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/sections/sentient.js
/**
* `sentient` setup wizard section (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Toggles the two operator-visible sentient knobs:
*   1. The daemon kill-switch (`killSwitch: false` ⇒ daemon is allowed
*      to run; `true` ⇒ paused).
*   2. Tier-2 proposal generation (`tier2Enabled`).
*
* Both fields live in `<projectRoot>/.cleo/sentient-state.json` and are
* mutated via {@link patchSentientState} so the on-disk shape matches
* the format the daemon expects.
*
* V2 additions (T9610):
*   - `isConfigured()` — returns `true` when `sentient-state.json` exists (SENT-5).
*   - Current state display before prompting (GEN-7 / SENT-1).
*   - Section description printed before prompts (GEN-6).
*
* Non-interactive contract:
*   - `options.nonInteractive === true` → consume
*     `options.sentientEnabled` / `options.tier2Enabled` when present;
*     fields not supplied are left untouched.
*
* @task T9420
* @task T9610
* @epic T9402
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.4
*/
/**
* Build the `sentient` section runner.
*
* @returns A {@link WizardSectionRunner} for the sentient section.
* @task T9420
* @task T9610
*/
function createSentientSection() {
	return {
		section: "sentient",
		title: "Sentient daemon + Tier-2 proposals",
		optional: true,
		async isConfigured(options) {
			return existsSync(join(resolveOrCwd(options.projectRoot), SENTIENT_STATE_FILE));
		},
		async run(io, options) {
			const statePath = join(resolveOrCwd(options.projectRoot), SENTIENT_STATE_FILE);
			io.info("Configures the CLEO sentient daemon and Tier-2 autonomous proposal generation.\nState is stored in `.cleo/sentient-state.json` in the project root.\nTier-2 proposals are disabled by default for safety.");
			if (existsSync(statePath)) try {
				const state = await readSentientState(statePath);
				const daemonActive = !state.killSwitch;
				const t2Active = state.tier2Enabled ?? false;
				const lastTick = state.lastTickAt != null ? new Date(state.lastTickAt).toISOString() : "never";
				io.info(`Current sentient state: daemon=${daemonActive ? "enabled" : "disabled"}, tier2=${t2Active ? "enabled" : "disabled"}, last-tick=${lastTick}`);
			} catch {
				io.info("Current sentient state: (could not read state file)");
			}
			else io.info("Current sentient state: (not configured — state file does not exist)");
			let daemonEnabled;
			let tier2Enabled;
			if (options.nonInteractive === true) {
				daemonEnabled = options.sentientEnabled;
				tier2Enabled = options.tier2Enabled;
				if (daemonEnabled === void 0 && tier2Enabled === void 0) throw new Error("E_SETUP_MISSING_FLAG: --section sentient --non-interactive requires --sentient <on|off> or --tier2 <on|off>");
			} else {
				daemonEnabled = await io.confirm("Enable the sentient daemon (auto-tick reconciler)?", false);
				tier2Enabled = await io.confirm("Enable Tier-2 autonomous proposals (off by default)?", false);
			}
			const patch = {};
			if (daemonEnabled !== void 0) {
				patch.killSwitch = !daemonEnabled;
				patch.killSwitchReason = daemonEnabled ? void 0 : "cleo setup: operator disabled";
			}
			if (tier2Enabled !== void 0) patch.tier2Enabled = tier2Enabled;
			await patchSentientState(statePath, patch);
			const fragments = [];
			if (daemonEnabled !== void 0) fragments.push(`daemon ${daemonEnabled ? "enabled" : "disabled"}`);
			if (tier2Enabled !== void 0) fragments.push(`tier2 ${tier2Enabled ? "enabled" : "disabled"}`);
			return {
				changed: true,
				summary: fragments.join(" + ")
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/sections/telemetry.js
/**
* `telemetry` setup wizard section (E-SKILLS-OWNER-CI / T9673).
*
* Anonymous skills-usage telemetry is **default-on for new installs**.
* This section runs during `cleo setup` to:
*   1. Surface the default-on contract to the operator (transparency).
*   2. Allow the operator to opt-out interactively.
*   3. Persist `telemetry.enabled`, `telemetry.period`, `telemetry.installId`
*      to the global config.
*
* The opt-out path is the same single boolean used by `cleo telemetry disable`
* (T9666) — the wizard step is convenience, NOT the only surface.
*
* Payload contract (LOCKED in T9666, enforced by the transport in ADR-074):
*   { installId, period: 'monthly', skills: { canonicalSkillName, loadCount }[] }
*   NEVER includes user identity, session IDs, paths, or skill content.
*
* Non-interactive contract:
*   - `options.nonInteractive === true` → consume `options.telemetryEnabled`
*     when present; default to `true` (default-on contract) when absent so
*     unattended setup runs match interactive defaults.
*
* @task T9673
* @epic T9572
* @see packages/cleo/src/cli/commands/telemetry.ts
* @see .cleo/adrs/ADR-074-skills-telemetry-pr-diff-transport.md
*/
/**
* Telemetry reporting period — locked to `monthly` in T9666.
*
* Future periods (weekly, on-demand) MUST be added as a discriminated
* union in `packages/cleo/src/cli/commands/telemetry.ts` first; this
* section consumes the canonical type rather than re-defining it.
*/
var TELEMETRY_PERIOD = "monthly";
/**
* Build the `telemetry` section runner.
*
* Idempotent — `isConfigured()` returns `true` when `telemetry.enabled` has
* been explicitly written to the global config. On a fresh install the key
* is absent (not `false`); the wizard runs and writes `true` per the
* default-on contract.
*
* @returns A {@link WizardSectionRunner} for the telemetry section.
*/
function createTelemetrySection() {
	return {
		section: "telemetry",
		title: "Anonymous skills-usage telemetry",
		optional: true,
		async isConfigured() {
			return typeof (await getConfigValue("telemetry.enabled")).value === "boolean";
		},
		async run(io, options) {
			io.info("CLEO ships anonymous skills-usage telemetry that surfaces the most-loaded\ncanonical skills back to the owner-CI top-N council (T9572 SG-CLEO-SKILLS).\nPayload: { installId, period, skills: [{ canonicalSkillName, loadCount }] }.\nNEVER includes user identity, session IDs, paths, or skill content.\nDefault: enabled. Opt-out anytime via `cleo telemetry disable`.");
			let enabled;
			if (options.nonInteractive === true) enabled = options.telemetryEnabled ?? true;
			else enabled = await io.confirm("Enable anonymous skills-usage telemetry?", true);
			await setConfigValue("telemetry.enabled", enabled, void 0, { global: true });
			await setConfigValue("telemetry.period", TELEMETRY_PERIOD, void 0, { global: true });
			const existingId = await getConfigValue("telemetry.installId");
			if (typeof existingId.value !== "string" || existingId.value.length === 0) await setConfigValue("telemetry.installId", randomUUID(), void 0, { global: true });
			return {
				changed: true,
				summary: `telemetry ${enabled ? "enabled" : "disabled"} (period=${TELEMETRY_PERIOD})`
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/sections/verification.js
/**
* `verification` setup wizard section (E-CLEO-SETUP-V2 / T9594).
*
* Read-only health-check runner that validates the full CLEO configuration
* stack after the wizard has finished writing all other sections. No state
* is mutated — this section is a diagnostic surface only.
*
* The section runs 6 independent checks in order:
*
*   1. **credential-pool**     — at least one entry exists in the pool.
*   2. **credential-reach**    — first valid credential round-trips to its
*                                provider health endpoint (5 s timeout).
*   3. **config-integrity**    — global + project config files parse cleanly.
*   4. **harness-reach**       — detected harness responds:
*                                  Pi   → HTTP GET `<piUrl>/health` (3 s).
*                                  Code → `which claude` exits 0.
*   5. **signaldock-reach**    — if `signaldock.enabled`, HTTP GET to
*                                `<endpoint>/health` (3 s); SKIP otherwise.
*   6. **brain-db**            — `brain.db` exists on disk and opens
*                                without error.
*
* Each check yields a {@link VerificationCheck} carrying `PASS`, `FAIL`, or
* `SKIP`. The runner aggregates results and surfaces them via {@link WizardIO}:
*   - Interactive  → formatted text table (one line per check).
*   - Non-interactive → JSON array of {@link VerificationCheck} entries.
*
* The section always returns `{changed: false}` (read-only contract, VERIF-1).
* `isConfigured()` always returns `false` — verification always runs when
* included in a wizard pass (VERIF-6).
*
* @task T9594
* @epic T9591
* @see docs/plans/E-CLEO-SETUP-V2.md §4.9, §5.2 T9594
*/
/**
* Check 1 — Credential pool has at least one entry.
*
* @internal
*/
async function runCredentialPoolCheck() {
	const name = "credential-pool";
	try {
		const entries = await getCredentialPool().list();
		if (entries.length === 0) return {
			name,
			status: "FAIL",
			message: "pool is empty — run cleo setup --section llm"
		};
		return {
			name,
			status: "PASS",
			message: `${entries.length} credential(s) found`
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `pool.list() threw: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Race a promise against a timeout, resolving to `undefined` on expiry.
*
* @internal
*/
function withTimeout(promise, ms) {
	return new Promise((resolve) => {
		const timer = setTimeout(() => resolve(void 0), ms);
		promise.then((val) => {
			clearTimeout(timer);
			resolve(val);
		}, () => {
			clearTimeout(timer);
			resolve(void 0);
		});
	});
}
/**
* Check 2 — First valid credential can reach its provider.
*
* Uses a best-effort HTTP GET to a known provider health URL when available.
* Falls back to a simple fetch attempt and inspects the HTTP status code.
* Timeout: 5 s.
*
* @internal
*/
async function runCredentialReachabilityCheck() {
	const name = "credential-reach";
	try {
		const entries = await getCredentialPool().list();
		if (entries.length === 0) return {
			name,
			status: "SKIP",
			message: "no credentials configured"
		};
		const provider = entries[0].provider;
		const probeUrl = {
			anthropic: "https://api.anthropic.com/v1/models",
			openai: "https://api.openai.com/v1/models",
			gemini: "https://generativelanguage.googleapis.com/v1/models",
			openrouter: "https://openrouter.ai/api/v1/models"
		}[provider] ?? `https://api.${provider}.com/v1/models`;
		const response = await withTimeout(fetch(probeUrl, {
			method: "GET",
			headers: { "Content-Type": "application/json" },
			signal: AbortSignal.timeout(5e3)
		}), 5e3);
		if (response === void 0) return {
			name,
			status: "FAIL",
			message: `timeout after 5 s probing ${provider} (${probeUrl})`
		};
		if (response.ok || response.status === 401 || response.status === 403) return {
			name,
			status: "PASS",
			message: `${provider} endpoint reachable (HTTP ${response.status})`
		};
		return {
			name,
			status: "FAIL",
			message: `${provider} returned HTTP ${response.status}`
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `fetch failed: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Check 3 — Global + project config files parse cleanly.
*
* Delegates to {@link loadConfig} which already handles missing files
* gracefully; we surface any unexpected parsing errors as FAIL.
*
* @internal
*/
async function runConfigIntegrityCheck(cwd) {
	const name = "config-integrity";
	try {
		await loadConfig(cwd);
		return {
			name,
			status: "PASS",
			message: "global + project config parse cleanly"
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `config parse error: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Check 4 — Detected harness responds.
*
* - `pi` (or `piUrl` in config) → HTTP GET `<piUrl>/health` (3 s timeout).
* - `claude-code`               → resolves `which claude` in PATH.
* - `unknown`                   → SKIP.
*
* @internal
*/
async function runHarnessReachabilityCheck(cwd) {
	const name = "harness-reach";
	try {
		let active = process.env["CLEO_HARNESS"];
		if (!active) if (process.env["CLAUDECODE"] === "1") active = "claude-code";
		else if (process.env["CLEO_PI"] === "1") active = "pi";
		else active = (await getConfigValue("harness.active", cwd)).value;
		if (!active || active === "unknown") return {
			name,
			status: "SKIP",
			message: "no harness configured"
		};
		if (active === "claude-code") {
			const { exec } = await import('node:child_process');
			const { promisify } = await import('node:util');
			const execP = promisify(exec);
			try {
				await withTimeout(execP("which claude"), 3e3);
				return {
					name,
					status: "PASS",
					message: "`claude` binary found in PATH"
				};
			} catch {
				return {
					name,
					status: "FAIL",
					message: "`claude` binary not found — install Claude Code"
				};
			}
		}
		if (active === "pi") {
			const piUrlResolved = process.env["CLEO_PI_URL"] ?? (await getConfigValue("harness.piUrl", cwd)).value;
			const piUrl = typeof piUrlResolved === "string" && piUrlResolved ? piUrlResolved : null;
			if (!piUrl) return {
				name,
				status: "SKIP",
				message: "pi harness active but piUrl not configured"
			};
			const healthUrl = piUrl.replace(/\/$/, "") + "/health";
			const response = await withTimeout(fetch(healthUrl, { signal: AbortSignal.timeout(3e3) }), 3e3);
			if (response === void 0) return {
				name,
				status: "FAIL",
				message: `timeout after 3 s (${healthUrl})`
			};
			if (response.ok) return {
				name,
				status: "PASS",
				message: `Pi /health returned HTTP ${response.status}`
			};
			return {
				name,
				status: "FAIL",
				message: `Pi /health returned HTTP ${response.status}`
			};
		}
		return {
			name,
			status: "SKIP",
			message: `unrecognised harness '${active}'`
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `harness check failed: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Check 5 — SignalDock endpoint is reachable.
*
* Skipped when `signaldock.enabled` is `false` or not configured.
* Timeout: 3 s.
*
* @internal
*/
async function runAgentRegistryReachabilityCheck(cwd) {
	const name = "signaldock-reach";
	try {
		if (!(await getConfigValue("signaldock.enabled", cwd)).value) return {
			name,
			status: "SKIP",
			message: "SignalDock not enabled"
		};
		const endpoint = (await getConfigValue("signaldock.endpoint", cwd)).value;
		if (!endpoint || typeof endpoint !== "string") return {
			name,
			status: "SKIP",
			message: "SignalDock endpoint not configured"
		};
		const healthUrl = endpoint.replace(/\/$/, "") + "/health";
		const response = await withTimeout(fetch(healthUrl, { signal: AbortSignal.timeout(3e3) }), 3e3);
		if (response === void 0) return {
			name,
			status: "FAIL",
			message: `timeout after 3 s (${healthUrl})`
		};
		if (response.ok) return {
			name,
			status: "PASS",
			message: `SignalDock /health returned HTTP ${response.status}`
		};
		return {
			name,
			status: "FAIL",
			message: `SignalDock /health returned HTTP ${response.status}`
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `SignalDock check failed: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Check 6 — BRAIN database is present and opens cleanly.
*
* We perform a lightweight existence + read-access check rather than a full
* Drizzle open (which would trigger migrations). The section must remain
* read-only (VERIF-1).
*
* @internal
*/
async function runBrainDbCheck(cwd) {
	const name = "brain-db";
	try {
		const dbPath = join(resolveCleoDir(cwd), "brain.db");
		if (!existsSync(dbPath)) return {
			name,
			status: "FAIL",
			message: `brain.db not found at ${dbPath} — run cleo init`
		};
		await access(dbPath);
		return {
			name,
			status: "PASS",
			message: `brain.db accessible at ${dbPath}`
		};
	} catch (err) {
		return {
			name,
			status: "FAIL",
			message: `brain.db check failed: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}
/**
* Build the `verification` section runner.
*
* The runner is read-only — it never mutates config or DB state (VERIF-1).
* `isConfigured()` always returns `false` so the section is never skipped
* even when the rest of the wizard short-circuits (VERIF-6).
*
* @returns A {@link WizardSectionRunner} for the verification section.
* @task T9594
*/
function createVerificationSection() {
	return {
		section: "verification",
		title: "Verification (read-only health checks)",
		optional: true,
		async isConfigured() {
			return false;
		},
		async run(io, options) {
			const cwd = options.projectRoot;
			io.info("Running health checks — this may take up to 5 s per network check. No state is mutated.");
			const checks = [];
			checks.push(await runCredentialPoolCheck());
			checks.push(await runCredentialReachabilityCheck());
			checks.push(await runConfigIntegrityCheck(cwd));
			checks.push(await runHarnessReachabilityCheck(cwd));
			checks.push(await runAgentRegistryReachabilityCheck(cwd));
			checks.push(await runBrainDbCheck(cwd));
			const failCount = checks.filter((c) => c.status === "FAIL").length;
			const passCount = checks.filter((c) => c.status === "PASS").length;
			const skipCount = checks.filter((c) => c.status === "SKIP").length;
			if (options.nonInteractive === true) io.info(JSON.stringify(checks, null, 2));
			else {
				const padEnd = (s, n) => s.padEnd(n, " ");
				const header = `  ${"Check".padEnd(22)}  ${"Status".padEnd(6)}  Message`;
				io.info(header);
				io.info(`  ${"─".repeat(22)}  ${"─".repeat(6)}  ${"─".repeat(50)}`);
				for (const check of checks) {
					const statusLabel = check.status === "PASS" ? "PASS" : check.status === "FAIL" ? "FAIL" : "SKIP";
					io.info(`  ${padEnd(check.name, 22)}  ${padEnd(statusLabel, 6)}  ${check.message}`);
				}
				io.info("");
				io.info(`  Summary: ${passCount} PASS, ${failCount} FAIL, ${skipCount} SKIP`);
			}
			if (failCount > 0) return {
				changed: false,
				summary: `verification: ${failCount} check(s) failed — see output`
			};
			return {
				changed: false,
				summary: `verification passed: ${passCount} PASS, ${skipCount} SKIP`
			};
		}
	};
}
//#endregion
//#region ../core/dist/setup/wizard.js
/**
* Core setup wizard engine (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Hermes-style modular wizard: every section (LLM, identity, sentient,
* project conventions, …) is an independent {@link WizardSectionRunner}
* that can be run on its own, in a fixed pipeline, or via the Studio
* `/setup` route — the engine has zero awareness of which surface (CLI
* vs. web) is driving the I/O.
*
* The engine is intentionally I/O-agnostic: callers pass a
* {@link WizardIO} implementation (a readline-backed `ConsoleWizardIO`
* from the CLI, a stub recorder in tests, a SvelteKit form helper from
* the Studio). The runner never imports `node:readline` directly so
* unit tests can drive it without a TTY.
*
* ## Design pointers
*
* - `run(sections?)` walks every (or a named subset of) section
*   in declared order, gathering a per-section summary line. The
*   summary is returned to the caller so `cleo setup` can echo a
*   final overview before exiting.
* - `runSection(name)` resolves the section by `section` field — this
*   is the field consumers see in `--section <name>` flags.
* - The non-interactive mode flag is *plumbed into `WizardOptions`* so
*   each section can decide whether to prompt or apply CLI flags
*   silently. Sections that have no non-interactive contract simply
*   short-circuit with `{ changed: false, summary: 'skipped (non-interactive)' }`.
*
* ## Section contract
*
* Built-in sections live under `./sections/` and are exported via
* {@link createBuiltinSections}. Each section's `run()` returns a
* {@link WizardSectionResult} carrying:
*   - `changed`: did the section apply any change? Used to label the
*     summary line.
*   - `summary`: a single-line human-readable description of what was
*     done (or skipped).
*
* @task T9420
* @task T9607
* @task T9613
* @epic T9402
* @epic T9591
* @see docs/plans/E-CONFIG-AUTH-UNIFY.md §3.3.5, §5.3 T-E3-1
* @see docs/plans/E-CLEO-SETUP-V2.md §3.1, §3.2, §3.3, §3.4
*/
/**
* Base class for errors that are fatal to the entire wizard run.
*
* `WizardRunner.invokeSection` catches and swallows section-level errors so
* a single failing section does not abort the rest of the wizard. But some
* error conditions (e.g. stdin closed) make further prompting impossible and
* must propagate all the way out so the CLI can emit a proper LAFS error
* envelope instead of silently continuing with dead prompts.
*
* CLI-layer error classes (e.g. `StdinClosedError` in
* `packages/cleo/src/cli/lib/readline-wizard-io.ts`) extend this class so
* the runner recognises them without importing CLI types.
*
* @task T9599
*/
var WizardFatalError = class WizardFatalError extends Error {
	constructor(message) {
		super(message);
		this.name = "WizardFatalError";
	}
	/**
	* Type-guard so `invokeSection` can detect subclasses without `instanceof`
	* across package boundaries.
	*/
	static is(err) {
		return err instanceof WizardFatalError;
	}
};
/**
* Runs an ordered collection of {@link WizardSectionRunner} instances.
*
* Stateless: every method takes the registered sections from the
* constructor argument and the per-invocation `io`/`options`. The
* runner deliberately swallows section-level exceptions so a failing
* section never aborts the rest of the wizard — exceptions are logged
* via `io.error()` and the section's summary line records `failed`.
*
* Mirrors Hermes Agent's `setup.py` decomposition.
*
* @task T9420
*/
var WizardRunner = class {
	sectionList;
	sections;
	/**
	* Construct a runner over a fixed section list.
	*
	* The list order is preserved as the execution order for {@link run}.
	* Passing the same `section` id twice throws — every id MUST be unique.
	*
	* @param sections - The wizard's section runners in execution order.
	*/
	constructor(sectionList) {
		this.sectionList = sectionList;
		const map = /* @__PURE__ */ new Map();
		for (const section of sectionList) {
			if (map.has(section.section)) throw new Error(`WizardRunner: duplicate section id '${section.section}' — every section id must be unique.`);
			map.set(section.section, section);
		}
		this.sections = map;
	}
	/**
	* Return the registered section runners in declaration order.
	*
	* Read-only view — callers MUST NOT mutate.
	*
	* @returns Frozen list of section runners.
	*/
	list() {
		return this.sectionList;
	}
	/**
	* Execute every section in declaration order.
	*
	* Each section runs against the supplied `io`/`options`. Section
	* failures are caught and reported via `io.error()`; the next section
	* still runs.
	*
	* ### Skip logic (T9613 / E-CLEO-SETUP-V2 §3.4)
	*
	* Before invoking each section, `run()` calls `section.isConfigured?.(options)`.
	* When that returns `true` and `options.reset` is not set, the section is
	* bypassed with a `skipped (already configured)` summary line and an info
	* message directing the user to `--reset` for a full reconfigure.
	*
	* ### Progress prefix (T9613 / E-CLEO-SETUP-V2 §3.4)
	*
	* Section headers are emitted as `[N/total] title (section-id)` so
	* scripted consumers can parse progress reliably.
	*
	* ### First-run completion (T9613 / E-CLEO-SETUP-V2 §3.4)
	*
	* After all sections complete without any `failed:` summary line, this
	* method writes `auth.firstRunComplete = true` to the global config and
	* writes a `setup-completed.json` marker to the CLEO data directory.
	* The returned {@link WizardRunResult.firstRunComplete} flag reflects
	* whether the marker was written.
	*
	* @param io - Prompting surface.
	* @param options - Pre-parsed flags / programmatic bag.
	* @returns Aggregate run result with per-section summary lines.
	*/
	async run(io, options = {}) {
		const sectionsRun = [];
		const summary = [];
		const total = this.sectionList.length;
		let index = 0;
		for (const runner of this.sectionList) {
			index++;
			sectionsRun.push(runner.section);
			io.info(`\n[${index}/${total}] ${runner.title} (${runner.section})`);
			if (!options.reset && runner.isConfigured) {
				if (await runner.isConfigured(options)) {
					io.info(`[skip] ${runner.title} — already configured. Use --reset to reconfigure.`);
					summary.push(`${runner.section}: skipped (already configured)`);
					continue;
				}
			}
			const result = await this.invokeSection(runner, io, options);
			summary.push(`${runner.section}: ${result.summary}`);
		}
		if (!summary.some((line) => line.includes(": failed:"))) {
			try {
				await setConfigValue("auth.firstRunComplete", true, void 0, { global: true });
				const dataDir = getCleoPlatformPaths().data;
				await mkdir(dataDir, { recursive: true });
				await writeFile(join(dataDir, "setup-completed.json"), JSON.stringify({ completedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
			} catch {}
			return {
				sectionsRun,
				summary,
				firstRunComplete: true
			};
		}
		return {
			sectionsRun,
			summary,
			firstRunComplete: false
		};
	}
	/**
	* Execute a single named section.
	*
	* @param name - Section id matching a registered runner's `section` field.
	* @param io - Prompting surface.
	* @param options - Pre-parsed flags / programmatic bag.
	* @returns The section's own {@link WizardSectionResult}.
	* @throws When `name` is not registered.
	*/
	async runSection(name, io, options = {}) {
		const runner = this.sections.get(name);
		if (!runner) throw new Error(`WizardRunner.runSection: no section registered for id '${name}'. Known sections: ${Array.from(this.sections.keys()).join(", ") || "(none)"}.`);
		io.info(`\n── ${runner.title} (${runner.section}) ──`);
		return this.invokeSection(runner, io, options);
	}
	/**
	* Run one section while shielding the wizard from its exceptions.
	*
	* Section bugs surface as `failed: <message>` summary lines rather
	* than uncaught throws so an `cleo setup` invocation always completes
	* with a non-fatal report instead of a stack trace.
	*
	* **Exception**: {@link WizardFatalError} subclasses (e.g. `StdinClosedError`)
	* are re-thrown immediately — they signal conditions (stdin EOF, broken pipe)
	* where further prompting is impossible and the CLI must emit a LAFS error
	* envelope rather than continuing with a dead I/O surface.
	*
	* @internal
	*/
	async invokeSection(runner, io, options) {
		try {
			return await runner.run(io, options);
		} catch (err) {
			if (WizardFatalError.is(err)) throw err;
			const message = err instanceof Error ? err.message : String(err);
			io.error(`section '${runner.section}' failed: ${message}`);
			return {
				changed: false,
				summary: `failed: ${message}`
			};
		}
	}
};
//#endregion
//#region ../core/dist/setup/index.js
/**
* Setup wizard entry point (E-CONFIG-AUTH-UNIFY E3 / T9420).
*
* Re-exports the engine + every built-in section + a convenience
* factory that wires the canonical order. Both `cleo setup` (T9421)
* and the Studio `/setup` route (T-E3-8) consume this surface.
*
* V2 additions (T9607): re-exports {@link WizardInterruptError}.
* V2 additions (T9608): re-exports {@link createIntegrationsSection}.
*
* @task T9420
* @task T9607
* @task T9608
* @epic T9402
* @epic T9591
*/
/**
* Construct every built-in {@link WizardSectionRunner} in canonical order.
*
* Order matters: `cleo setup` walks the list verbatim. The current
* order is:
*   1. `llm`                 — credentials are the prerequisite for everything else
*   2. `models-roles`        — default model + per-role profiles (T11726)
*   3. `identity`            — agent name / SOUL.md before any agent dispatch
*   4. `sentient`            — daemon enablement after credentials exist
*   5. `project-conventions` — strictness preset before harness/brain layering
*   6. `harness`             — operator selects Pi vs Claude Code (T9425)
*   7. `brain`               — BRAIN memory bridge mode (T9425)
*   8. `integrations`        — SignalDock + Studio + Conduit (T9608)
*   9. `telemetry`           — anonymous skills-usage telemetry (T9673)
*  10. `verification`        — read-only health checks (T9594)
*
* @param llmDeps - Optional dependencies forwarded to the `llm` section — most
*   notably the interactive OAuth token acquirer (T11727). The CLI surface
*   passes the real acquirer so the wizard OAuth path runs the inline engine;
*   programmatic callers omit it (OAuth path points the user at `cleo login`).
* @returns Fresh array of section runner instances.
* @task T9420
* @task T9425
* @task T9594
* @task T9608
* @task T9673
* @task T11726
* @task T11727
*/
function createBuiltinSections(llmDeps = {}) {
	return [
		createLlmSection(llmDeps),
		createModelsRolesSection(),
		createIdentitySection(),
		createSentientSection(),
		createProjectConventionsSection(),
		createHarnessSection(),
		createBrainSection(),
		createIntegrationsSection(),
		createTelemetrySection(),
		createVerificationSection()
	];
}
//#endregion
//#region src/routes/api/setup/section/[name]/+server.ts
/**
* POST /api/setup/section/:name — run a single wizard section.
*
* Bridges the Studio `/setup` UI to the canonical {@link WizardSectionRunner}
* pipeline shipped from `@cleocode/core/setup`. The browser supplies a
* single JSON payload describing how the section should behave; the
* server constructs a {@link WizardOptions} bag plus a queue-backed
* `WizardIO` and invokes the matching section runner from
* {@link createBuiltinSections}.
*
* Request body:
* ```
* {
*   nonInteractive?: boolean,
*   provider?: string, apiKey?: string, label?: string,
*   agentName?: string, soulMdContent?: string,
*   sentientEnabled?: boolean, tier2Enabled?: boolean,
*   strictness?: 'strict' | 'standard' | 'minimal',
*   harness?: 'pi' | 'claude-code',
*   brainBridgeMode?: 'digest' | 'file' | 'disabled',
*   projectRoot?: string,
* }
* ```
*
* Response (LAFS):
* ```
* { success: true, data: { section, success, changes, summary } }
* ```
*
*   - `section`   — echoed section id so the client can correlate
*   - `success`   — `true` when the runner returned without surfacing
*                   any `io.error()` lines (i.e. no recoverable failure)
*   - `changes`   — `result.changed` from the section runner
*   - `summary`   — `result.summary` line from the section runner
*
* The endpoint is intentionally write-only against the section: it never
* echoes back secret material, never returns the request payload, and
* never reads credentials. The `/keys` route remains the single read
* surface for credential listings.
*
* SECURITY: the LLM section route is bypassed by the Studio UI in favour
* of POST /api/credentials directly (T9426) so that the only place the
* API key crosses the wire is the dedicated credential endpoint. This
* handler still supports `llm` for parity with `cleo setup --section llm`
* but the API key, when supplied, is forwarded straight to the wizard
* section which routes it through `addCredential` — same persistence
* path as POST /api/credentials.
*
* @task T9427
* @epic E-CONFIG-AUTH-UNIFY (E3 §5.3 T-E3-8)
*/
var ALLOWED_SECTIONS = new Set([
	"llm",
	"identity",
	"harness",
	"sentient",
	"project-conventions",
	"brain"
]);
/**
* Headless {@link WizardIO} implementation for the HTTP path.
*
* The Studio `/setup` flow operates in **non-interactive** mode: every
* answer the section needs is supplied up-front via `WizardOptions`.
* If a section nevertheless asks a question (an unforeseen path),
* `prompt`/`confirm`/`select` throw so the failure surfaces as a
* deterministic 500 instead of hanging.
*
* `info` / `warn` / `error` messages are captured so the response
* `success` field can flip to `false` when the section surfaced a
* recoverable error via `io.error()`.
*
* @internal
*/
var HttpWizardIO = class {
	/** Captured info/warn/error lines, in emission order. */
	errors = [];
	warns = [];
	async prompt(question) {
		throw new Error(`HttpWizardIO: section attempted an interactive prompt ('${question}') but the Studio /setup flow only supports non-interactive section runs. Pass the required field via the POST body.`);
	}
	async confirm(question) {
		throw new Error(`HttpWizardIO: section attempted an interactive confirm ('${question}') but the Studio /setup flow only supports non-interactive section runs.`);
	}
	async select(question) {
		throw new Error(`HttpWizardIO: section attempted an interactive select ('${question}') but the Studio /setup flow only supports non-interactive section runs.`);
	}
	info(_message) {}
	warn(message) {
		this.warns.push(message);
	}
	error(message) {
		this.errors.push(message);
	}
};
/**
* Narrow a parsed JSON body into a {@link WizardOptions} bag.
*
* Each field is pulled defensively — unknown types collapse to
* `undefined` rather than propagating to the section. The wizard
* sections themselves enforce that their required inputs are present
* (e.g. `llm` requires `provider` + `apiKey`).
*
* @internal
*/
function buildOptions(body) {
	const options = {};
	if (body["nonInteractive"] !== false) options.nonInteractive = true;
	else options.nonInteractive = false;
	if (typeof body["provider"] === "string") options.provider = body["provider"];
	if (typeof body["apiKey"] === "string") options.apiKey = body["apiKey"];
	if (typeof body["label"] === "string") options.label = body["label"];
	if (typeof body["agentName"] === "string") options.agentName = body["agentName"];
	if (typeof body["soulMdContent"] === "string") options.soulMdContent = body["soulMdContent"];
	if (typeof body["sentientEnabled"] === "boolean") options.sentientEnabled = body["sentientEnabled"];
	if (typeof body["tier2Enabled"] === "boolean") options.tier2Enabled = body["tier2Enabled"];
	const strictness = body["strictness"];
	if (strictness === "strict" || strictness === "standard" || strictness === "minimal") options.strictness = strictness;
	const harness = body["harness"];
	if (harness === "pi" || harness === "claude-code") options.harness = harness;
	const bridgeMode = body["brainBridgeMode"];
	if (bridgeMode === "digest" || bridgeMode === "file" || bridgeMode === "disabled") options.brainBridgeMode = bridgeMode;
	if (typeof body["projectRoot"] === "string") options.projectRoot = body["projectRoot"];
	return options;
}
/**
* POST /api/setup/section/:name — run the named wizard section.
*
* @task T9427
*/
var POST = async ({ request, params }) => {
	const name = params.name;
	if (!name || typeof name !== "string" || !ALLOWED_SECTIONS.has(name)) return json(err("E_VALIDATION", `Unknown wizard section '${String(name)}'. Allowed: ${Array.from(ALLOWED_SECTIONS).join(", ")}.`), { status: 400 });
	const section = name;
	const body = await parseJsonBody(request);
	if (isParseError(body)) return json(err("E_VALIDATION", body._parseError), { status: 400 });
	const options = buildOptions(body);
	const io = new HttpWizardIO();
	try {
		const result = await new WizardRunner(createBuiltinSections()).runSection(section, io, options);
		return json(ok({
			section,
			success: io.errors.length === 0,
			changes: result.changed,
			summary: result.summary
		}));
	} catch (e) {
		return json(err("E_SECTION_FAILED", `Section '${section}' failed: ${e instanceof Error ? e.message : String(e)}`), { status: 500 });
	}
};

export { POST };
//# sourceMappingURL=_server.ts-C8zoyT5E.js.map
