import { type AgentLocation } from './agent-location.js';
import { type HandoffLevel } from './handoff-level.js';
/**
 * The multi-project registry (#390): the list of projects the user has
 * installed The Framework into, kept as a single JSON file `.bashrc`-style —
 * `$HOME/.the-framework.json` — so it is the user's responsibility to re-create
 * per machine. The same file also holds the user's dashboard preferences (#410),
 * so the daemon owns one user file and the UI never needs localStorage.
 */
/** One registered project. */
export interface ProjectRecord {
    /** Stable, URL-safe id derived from the path. */
    id: string;
    /** Absolute repo path. */
    path: string;
    /** ISO timestamp the project was added. */
    addedAt: string;
}
/**
 * The dashboard's Global options (#410), persisted next to the project list so they
 * survive restarts without localStorage — the daemon reads/writes them, the SPA reads
 * them over `POST /_rpc/onPreferences`. Mostly flat booleans mirroring the Start form's
 * toggles; every field is optional and absent means off, except where a field documents its
 * own default below.
 */
/**
 * A user-defined preset (#626): a named prompt the user saved to re-run their own high-signal
 * prompts, sitting beside the built-in presets in the Start form. Just data — the label is the
 * button, the prompt is loaded verbatim into the editor and run as a `prompt` kind (unlike the
 * built-ins, whose text is a compiled render function). `id` is stable so edits/deletes address one.
 */
export interface CustomPreset {
    id: string;
    label: string;
    prompt: string;
}
export interface Preferences {
    vanilla?: boolean;
    /** On-before-mergeable prompt (#326): on setReadyForMerge(), queue the quality follow-ups as TODO entries. */
    onBeforeMergeableQuality?: boolean;
    /** Give the agent a real browser via chrome-devtools-mcp during the agent (#452); maps to `--browser`. */
    browser?: boolean;
    /**
     * How far a finished session publishes itself (#1102/#1216/B5): keep it local, push the branch,
     * open a draft PR, or merge that PR. Absent = {@link DEFAULT_HANDOFF} (`pr`).
     *
     * Default-on, unlike most of this file, because it is what makes the handoff zero-config: the
     * old behaviour was a button nobody was obliged to press, and work that stayed on a local
     * branch nobody was told about (#860). A session can still opt out from its action bar.
     */
    handoff?: HandoffLevel;
    /**
     * Transparent mode (#625): run the wrapped agent raw — no framework system prompt, emit
     * protocols, consumption guard, dashboard, or TODO loop, so an agent is identical to `claude -p`.
     * The coarse master off-switch ("only pick what you need"); maps to `--transparent`. Absent = off.
     */
    transparent?: boolean;
    /** Fire a browser notification when a new item lands on the "needs you" queue (#627). Absent = on. */
    notifyBrowser?: boolean;
    /**
     * Also notify on plain agent activity — an agent started, an agent finished (#627). The default-off
     * counterpart to the always-on "needs you" notifications: it keeps you loosely informed of the
     * pipeline moving even when nothing needs you. A *category* toggle: it composes with the method
     * toggles ({@link notifyBrowser} / {@link notifyDiscord}), so activity reaches whichever are on.
     */
    notifyNewActivity?: boolean;
    /**
     * The "needs you" category (#627): notify when an agent is awaiting your answer or a PR is ready
     * to review. A *category* toggle, like {@link notifyNewActivity}, composing with the method
     * toggles ({@link notifyBrowser} / {@link notifyDiscord}). **Absent = on**: unlike the other
     * flat opt-in booleans, human-intervention pings are the baseline The Framework leans on, so an
     * unset preference keeps them firing; a user turns them off explicitly.
     */
    notifyHumanIntervention?: boolean;
    /** The model to run on (#628), e.g. `opus` / `sonnet`; maps to an agent's `--model`. Absent = the driver's default. */
    model?: string;
    /** Which coding agent drives the agent (#650): `claude` or `codex`; maps to `--agent`. Absent = the default (`claude`). */
    driver?: string;
    /** Preferred editor for "Open in editor" (#727): an editor CLI (e.g. `code`, `cursor`, `zed`).
     * Absent falls back to `$FRAMEWORK_EDITOR`, then `code`. */
    editor?: string;
    /** Dashboard color theme (#725): `system` (follow the OS, the default), `light`, or `dark`. Absent = system. */
    theme?: 'system' | 'light' | 'dark';
    /** Where a run executes (#1050/#610): `local` (this device, the default), `actions` (a fresh GitHub Actions runner) or `web` (a Claude Code cloud session); maps to `--run-on`. Absent = local. */
    target?: AgentLocation;
    /**
     * Post a Discord message when a new item lands on the "needs you" queue (#627). Absent = off:
     * unlike the in-browser toggle, Discord reaches you when no dashboard is open, so it is opt-in.
     * Gates the daemon watcher *on top of* a `DISCORD_WEBHOOK` being set (the webhook is where to
     * post; this is whether to).
     */
    notifyDiscord?: boolean;
    /**
     * Auto PM (#685): let the daemon start a PM agent by itself when the agent queue has run dry
     * and there is plenty of budget left, so leftover subscription quota goes on the roadmap
     * instead of expiring. **Absent = off**: it spends the user's allowance without being asked,
     * so it is opt-in like {@link notifyDiscord} rather than a baseline.
     */
    autoPm?: boolean;
    /**
     * The browser bridge (#1237): let an extension running in the user's own Claude session report
     * the question a Claude web agent is parked on, so it shows in the dashboard rather than only on
     * claude.ai. **Absent = off.** It opens the daemon's one route reachable from another origin,
     * so it is opt-in rather than a baseline, and turning it on is what mints the bridge token.
     */
    bridge?: boolean;
    /**
     * The routines {@link autoPm} must not fire, by {@link AutoPmJob.name} (#1209). Absent or empty
     * = every routine runs, which is what the sweep did before this existed.
     *
     * Opted *out* rather than opted in, so the list only ever names exceptions: a routine added in a
     * later version is on for everyone, instead of silently never running for whoever saved the
     * setting before it shipped. It names routines rather than indexing them for the same reason
     * {@link AutoPmJob.drains} is a flag — a reorder must not move which one is switched off.
     */
    autoPmOptOut?: string[];
    /**
     * How many agents the routine may keep going at once on one project (#1204). Absent defaults to
     * `DEFAULT_AUTO_PM_CONCURRENCY`, and the value is clamped to `MAX_AUTO_PM_CONCURRENCY`.
     *
     * Only the draining routine fans out: it takes work *off* the queue, one pinned entry per agent,
     * so several at once do disjoint work. The rotation invents work and each of its jobs rewrites
     * the queue file, so it stays one agent per tick whatever this says.
     */
    autoPmConcurrency?: number;
    /**
     * How far the automatic-consumption limit sits from the quota boundary, in percentage points
     * (#960). Absent defaults to {@link DEFAULT_SPEND_OFFSET} — a half-day cushion ahead of the
     * boundary — rather than sitting exactly on it (#960 Edit).
     *
     * Negative holds unattended work back further; positive lets it borrow into the days still to
     * come. It is an *offset* rather than an absolute percentage so the limit travels with the
     * boundary as the week goes on, instead of being overtaken by it on day two.
     */
    autoSpendOffset?: number;
    /** User-defined presets (#626): the user's own saved prompts, shown beside the built-in presets. */
    customPresets?: CustomPreset[];
    /**
     * Whether the Overview's Onboarding checklist has been dismissed (#958). Absent = show it,
     * so a fresh install is walked through setup; dismissing only hides it on the Overview, and
     * the same checklist stays available on the settings page.
     */
    onboardingDismissed?: boolean;
}
export { MAX_SPEND_OFFSET, DEFAULT_SPEND_OFFSET, DEFAULT_AUTO_PM_CONCURRENCY, MAX_AUTO_PM_CONCURRENCY, } from './preference-defaults.js';
/**
 * The credentials the daemon needs to reach a third party, set from the dashboard (#1095).
 *
 * Their tier is the {@link Registry.daemonToken} one, not {@link Preferences}: top-level, so
 * neither the browser bundle nor the per-project override map can ever carry them. Nothing
 * reads a value back out to a client — the dashboard is told only that one is *present*
 * ({@link DiscordCredentialStatus}) — so the registry file stays the one place they exist.
 *
 * The alternative was a second file. This one already holds `daemonToken`, which authenticates
 * every request to a network-reachable daemon, so the file is a secret store since #1051; a
 * second one would only spread the same exposure over two paths to keep 0600 on.
 */
export interface RegistrySecrets {
    /** Where Discord notifications are posted (#627). Overridden by `DISCORD_WEBHOOK` when that is set. */
    discordWebhook?: string;
}
/** The persisted registry file shape (#410): the project list plus the user preferences. */
export interface Registry {
    projects: ProjectRecord[];
    preferences: Preferences;
    /**
     * The shared daemon token (#1051): generated on the first non-loopback bind and reused after.
     * A top-level field, deliberately not a {@link Preferences} one, so it is never shipped to the
     * browser bundle. Absent on a loopback-only machine.
     */
    daemonToken?: string;
    /** Third-party credentials set from the dashboard (#1095). Absent until one is saved. */
    secrets?: RegistrySecrets;
}
/** A read/write handle for the user preferences, wired into the dashboard's context by the daemon. */
export interface PreferencesStore {
    read(): Promise<Preferences>;
    save(preferences: Preferences): Promise<void>;
    /**
     * Merge only the keys the caller changed (#1148) and hand back the stored result. Preferred over
     * {@link save}, which replaces the whole block from a snapshot that may already be stale.
     */
    patch(patch: Preferences): Promise<Preferences>;
}
/** The registry file name: a single file under `$XDG_CONFIG_HOME` (dotted under `$HOME`). */
export declare const REGISTRY_FILE = "the-framework.json";
/** Owner read/write only: the file holds the daemon token (#1051) and the Discord credentials (#1095). */
export declare const REGISTRY_FILE_MODE = 384;
/**
 * Deterministic, URL-safe id for a project path: the sanitized basename plus a
 * short hash of the full path, so two repos named alike still get distinct ids.
 * Pure; same path always yields the same id.
 */
export declare function projectId(path: string): string;
/**
 * The registry file path, resolved from `env` (injectable so tests never touch
 * the real home): `$XDG_CONFIG_HOME/the-framework.json` when set, else the
 * dotted `$HOME/.the-framework.json`. A single file, not a directory (#390).
 */
export declare function registryPath(env: NodeJS.ProcessEnv): string;
/** Minimal fs seam so the registry is unit-testable without touching disk. */
export interface RegistryFs {
    /** Rejects when the file is absent. */
    read(path: string): Promise<string>;
    write(path: string, contents: string): Promise<void>;
    /** Recursive; used on the registry file's parent dir. */
    mkdir(path: string): Promise<void>;
    /**
     * Replace `to` with `from`, atomically. Optional only so an existing implementation of this
     * seam keeps compiling; without it {@link writeRegistry} falls back to the truncate-then-write
     * this method exists to avoid (#991).
     */
    rename?(from: string, to: string): Promise<void>;
    /**
     * Narrow a file's permissions. Optional, and best-effort at the call site: this file holds the
     * daemon token (#1051) and the Discord credentials (#1095), so it is written owner-only — but a
     * filesystem that cannot express that (Windows, a FAT volume) must not fail the write.
     */
    chmod?(path: string, mode: number): Promise<void>;
}
/** A {@link RegistryFs} backed by `node:fs/promises`. See {@link nodeFs}. */
export declare function nodeRegistryFs(): RegistryFs;
/**
 * Keep only well-formed custom presets (#626): each needs a non-empty id, label, and prompt;
 * label/prompt are trimmed and length-capped, the list capped at {@link CUSTOM_PRESET_LIMITS.count},
 * and duplicate ids dropped. A malformed entry is skipped, not thrown — a bad registry never breaks the read.
 */
export declare function sanitizeCustomPresets(value: unknown): CustomPreset[];
/**
 * Read the whole registry. Forgiving: a missing / unreadable / malformed file — or one in a shape
 * this no longer writes — yields an empty registry, never throws. Projects are deduped by resolved
 * path and unknown preference fields are dropped.
 */
export declare function readRegistry(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<Registry>;
/**
 * Read the registry's project list. Forgiving: a missing / unreadable / malformed
 * file yields `[]`, never throws. Deduped by resolved path, first wins.
 */
export declare function listProjects(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<ProjectRecord[]>;
/**
 * Register a project. Idempotent by resolved path: when the path is already
 * registered, the existing record is returned untouched (addedAt survives);
 * otherwise the new record is appended and the file written back (preferences preserved).
 */
export declare function addProject(path: string, addedAt: string, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<ProjectRecord>;
/** The user's dashboard preferences (#410), or `{}` when none are stored. */
export declare function readPreferences(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<Preferences>;
/** Persist the dashboard preferences (#410), sanitized, preserving the project list. */
export declare function writePreferences(preferences: Preferences, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<void>;
/**
 * Merge `patch` over the stored preferences (#1148) and return the result.
 *
 * The counterpart to {@link writePreferences}, which replaces the whole block: a client that
 * sends its entire snapshot replays every value it happens to hold, so a dashboard tab opened
 * before someone else's change silently reverted it on the tab's next write, whatever key that
 * write was actually about. Sending only the changed keys makes a write touch only what it names.
 *
 * Clearing needs no sentinel: {@link sanitizePreferences} already drops blank strings and empty
 * lists, so `{ editor: '' }` merges in as blank and comes out absent, which is how the dashboard
 * clears the editor today.
 */
export declare function patchPreferences(patch: Preferences, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<Preferences>;
/**
 * The shared daemon token (#1051): read the persisted one, or generate + persist it now. Called
 * only on a non-loopback bind, so a loopback-only machine never grows one. Serialized with the
 * other mutators so two concurrent binds can't each write a different token. `base64url` of 32
 * random bytes: URL-safe, so it drops straight into a `?token=` without encoding.
 */
export declare function ensureDaemonToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string>;
/**
 * The stored third-party credentials (#1095), or `{}` when none are set. Daemon-side only —
 * every caller is a service that needs the value itself, never a client read: what the dashboard
 * gets told is presence, in {@link RegistrySecrets}'s doc sense.
 */
export declare function readSecrets(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<RegistrySecrets>;
/**
 * Merge a patch into the stored credentials (#1095), leaving everything else in the file alone.
 *
 * A patch, not a whole-object write, because the caller is a UI that edits one field: the bot
 * dialog must not clear the webhook by not knowing it. An explicit `null` (or a blank string)
 * clears a key — that is the Clear button — while `undefined` leaves it as it was, so "not
 * mentioned" and "removed" stay different things. Serialized with the other mutators.
 */
export declare function writeSecrets(patch: Partial<Record<keyof RegistrySecrets, string | null>>, fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<void>;
/** The persisted daemon token (#1051), or `undefined` when none exists. A pure read, so a process
 * that only prints the reachable URL never generates one. */
export declare function readDaemonToken(fs?: RegistryFs, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
/** A {@link PreferencesStore} bound to the real registry file, wired by the daemon so the
 * dashboard's preferences RPCs read/write the user's home file.
 *
 * `onChange` is handed **the keys the caller wrote**, not the merged result, so a listener can
 * tell "this write switched the setting on" from "it was already on and something else changed"
 * (#1161). It runs after the write has landed, and its failure is swallowed: the save succeeded,
 * and a listener must not be able to report otherwise. Same shape as the Discord store's
 * `onChange` (#1095), for the same reason — a setting saved in the browser has to reach the
 * daemon's own services without a restart.
 */
export declare function registryPreferencesStore(fs?: RegistryFs, env?: NodeJS.ProcessEnv, onChange?: (written: Preferences) => void): PreferencesStore;
//# sourceMappingURL=registry.d.ts.map