/**
 * Side-effect layer for WIZ.3 — file-system writes the chat-setup wizard
 * performs in step (g) after the user confirms the dry-run preview.
 *
 * Split out of `chat_actions.ts` to keep the orchestrator's prompt-driving
 * flow under the LOC budget. This module is the ONLY place that creates
 * directories, writes files, or sets file modes; the orchestrator passes
 * a fully-formed `WritePlan` and never invokes `fs.*` directly.
 *
 * Discipline:
 *   - Atomic writes: tmp file (`<target>.tmp.<pid>.<ts>`) → `fs.rename`.
 *     POSIX `rename(2)` is atomic; if it fails after we've created a tmp,
 *     we unlink the tmp on cleanup so no `.tmp.<pid>` litter remains.
 *   - Backups: before any overwrite, copy the existing file into
 *     `~/.opensquid/backup/<isoTs>/<basename>`. The directory is created
 *     fresh per wizard run (timestamp guarantees uniqueness). If no files
 *     are being replaced the backup dir is still created — outro tells the
 *     user the path either way for symmetry.
 *   - Rollback: on first write failure, copy each backed-up file BACK to
 *     its original location and rethrow. Files that hadn't been written
 *     yet need no rollback (their target is unchanged from the start).
 *   - chmod 0600 on `~/.loop/.env` ALWAYS — even when we appended a single
 *     line, the goal is to enforce the secrets-backend invariant.
 *   - YAML serialization via `yaml.stringify` (yaml@2). The schema's Zod
 *     defaults are written explicitly so the file is self-documenting; we
 *     don't omit a key just because it equals its default. Comments are
 *     NOT preserved in models.yaml merges — the wizard owns its alias
 *     block but writes back a fresh document; per-pack chat_agent.yaml
 *     files are always freshly authored.
 *
 * Imports from: node:fs/promises, node:path, yaml.
 * Imported by: src/setup/cli/chat_actions.ts.
 */
import type { ChatAgentConfig } from '../../packs/schemas/chat_agent.js';
import { ModelsConfig, type ModelAlias } from '../../packs/schemas/models.js';
import type { ModelsState } from './chat_state.js';
export type WriteAction = {
    kind: 'create_or_replace';
    path: string;
    content: string;
    mode?: number;
} | {
    kind: 'append';
    path: string;
    appendBlock: string;
    mode?: number;
};
export interface WritePlan {
    /** Absolute path to ~/.opensquid/backup/<isoTs>/ — created during execute. */
    backupDir: string;
    actions: WriteAction[];
}
/**
 * Merge a new fast_chat alias into the existing models map (or create the
 * map fresh). Preserves all unrelated aliases verbatim.
 */
export declare function buildModelsYaml(existing: ModelsConfig, fastChatAlias: ModelAlias): string;
/**
 * Serialize a chat_agent.yaml document. Optional fields are omitted when
 * they equal the schema default to keep the file terse; required fields
 * (default_model) are always present.
 */
export declare function buildChatAgentYaml(cfg: ChatAgentConfig): string;
/**
 * Minimal `~/.opensquid/channels.json` seed (`ChannelsConfig` v1 — routing.ts
 * schema; `telegram` optional on UmbrellaRow, target arrives by manual edit).
 * Pure; schema-shaped.
 */
export declare function buildChannelsSeedJson(umbrellaId: string, memberPath: string): string;
/**
 * The user-scope `active.json` (`{ packs: string[] }` — discovery.ts schema).
 * The ONE owner of merge+dedupe+serialize; pure (deterministic in inputs).
 */
export declare function buildActiveJson(existing: string[], packId: string): string;
/**
 * The `.opensquid/project.json` card (paths.ts ProjectCard schema).
 * GENUINELY pure — deterministic in its inputs, byte-pinnable in tests; the
 * uuid is minted at the ORCHESTRATOR (which owns create-if-absent), not here.
 */
export declare function buildProjectCardJson(id: string, uuid: string): string;
/**
 * Build the `KEY=value` line(s) appended to ~/.loop/.env. We trim trailing
 * whitespace on the prior file (if any) and prepend a newline so the new
 * block is line-separated from prior content.
 */
export declare function buildEnvAppendBlock(entries: {
    key: string;
    value: string;
}[]): string;
export interface ExecuteResult {
    written: string[];
    backedUp: string[];
}
export declare function executePlan(plan: WritePlan): Promise<ExecuteResult>;
export interface PlanInput {
    homeDir: string;
    envPath: string;
    modelsState: ModelsState;
    fastChatAlias: ModelAlias;
    apiKey: string | null;
    storeKey: boolean;
    packId: string;
    packRoot: string;
    chatAgent: ChatAgentConfig;
    createPackManifest: boolean;
    customPromptPath?: string;
    customPromptBody?: string;
    /** T-FIX-FIRST-RUN-SETUP A: mint .opensquid/project.json when the FULL
     *  resolution (env, then cwd-walk) found no project identity. Orchestrator-
     *  gated: an env uuid or an existing card suppresses this input entirely —
     *  suppression IS the idempotency; buildPlan never decides. */
    projectCard?: {
        path: string;
        id: string;
        uuid: string;
    };
    /** FRS.B: write user-scope active.json on EXPLICIT consent (the activation
     *  prompt + the plan confirm both gate it — no silent install). Raw
     *  existing list from the orchestrator; buildActiveJson owns the merge. */
    activatePack?: {
        path: string;
        existing: string[];
        packId: string;
    };
    /** FRS.C: seed minimal channels.json ONLY when the file does not exist
     *  (existence-checked at the orchestrator — a present-but-malformed live
     *  config is the doctor's territory, never the wizard's). */
    channelsSeed?: {
        path: string;
        umbrellaId: string;
        memberPath: string;
    };
}
export declare function buildPlan(input: PlanInput): WritePlan;
/**
 * Render the dry-run preview. Masks every `=value` segment of an append
 * block to `=…<last4>` so secret-shaped writes never display in clear.
 */
export declare function renderPlanPreview(plan: WritePlan): string;
//# sourceMappingURL=chat_actions_writers.d.ts.map