declare class ApiError extends Error {
    readonly code: number;
    constructor(code: number, message: string);
}
/** A step that ran but did not succeed. The input handle is still usable. */
declare class StepError extends ApiError {
    readonly step: string;
    readonly job: string;
    /** The handle passed IN, still alive — carry on from here if the step was optional. */
    readonly handle: string;
    constructor(message: string, step: string, job: string, 
    /** The handle passed IN, still alive — carry on from here if the step was optional. */
    handle: string);
}
/**
 * Query values, with `undefined` entries dropped so optional arguments do not
 * turn into the string "undefined" on the wire.
 */
type Query = Record<string, string | number | boolean | undefined>;
/**
 * The client every namespace talks through. Success and failure are the HTTP
 * status: every route refuses a request with a 4xx/5xx and `{error, code}`,
 * and `error` is the message the caller gets.
 *
 * A failed pipeline step is a 422 carrying the job and the input handle, so
 * `curl --fail` and a thrown exception both fall out without either caller
 * having to inspect a field.
 */
declare class HttpClient {
    private baseUrl;
    private token?;
    constructor(baseUrl: string, token?: string | undefined);
    private request;
    get<T>(path: string, query?: Query): Promise<T>;
    /** A body that is not JSON — a picture — as bytes; a refusal as an ApiError. */
    getBytes(path: string, query?: Query): Promise<Uint8Array>;
    post<T>(path: string, body?: unknown, query?: Query): Promise<T>;
    put<T>(path: string, body?: unknown, query?: Query): Promise<T>;
    patch<T>(path: string, body?: unknown): Promise<T>;
    delete<T>(path: string, query?: Query, body?: unknown): Promise<T>;
}

/** How two files of a group are the same image (#258). */
type Relation = 'identical' | 'same_image_data' | 'same_sensor_data' | 'same_capture' | 'looks_identical' | 'related';
/** The albums (by name) and folders (by path) on the photographer's devices (#864). */
interface OnDevices {
    albums: string[];
    folders: string[];
}
interface LibrarySource {
    id: number;
    label: string;
    volumeUuid: string;
    rootPath: string;
    connected: boolean;
    files: number;
    bytes: number;
    /** Files a scan did not find where they were (#661). */
    missing: number;
    /** Seconds since 1970. */
    addedAt: number;
    lastScanAt: number | null;
    /** Folders below the root that scans skip, relative to the root. */
    excludes: string[];
}
/**
 * One file's row in the library (#658): a file on a scanned disk, or one
 * photosuite listed, with its marks as the app last wrote or read them.
 */
interface LibraryFile {
    id: number;
    path: string;
    sourceId: number;
    size: number;
    /** Seconds since 1970. */
    mtime: number;
    /** `raw`, `video`, or the image format (`jpeg`, `heif`, …). */
    kind: string | null;
    rating: number | null;
    /** 1 picked, -1 rejected, 0 neither. */
    flag: number;
    /** The colour label's `xmp:Label` name. */
    label: string | null;
    hasEdits: boolean;
    make: string | null;
    model: string | null;
    lens: string | null;
    width: number | null;
    height: number | null;
    orientation: number | null;
    captureTimeMs: number | null;
    durationMs: number | null;
    caption: string | null;
    title: string | null;
    creator: string | null;
    city: string | null;
    state: string | null;
    country: string | null;
    sublocation: string | null;
    gpsLat: number | null;
    gpsLon: number | null;
    keywords: string[];
    people: string[];
    /** photosuite's virtual copies of it, by name. */
    copies: string[];
    /** Where it is published (`eventphotos:<event>`, `newspaper`). */
    published: string[];
    sharpness: number | null;
    /** A scan has read the file's own header (and can hash it). */
    scanned: boolean;
    /** On a disk that is connected now (#660). */
    connected: boolean;
    /** When a scan did not find it where it was (#661), seconds since 1970. */
    missingSince: number | null;
}
interface LibraryVolume {
    uuid: string;
    label: string;
    mountPoint: string;
}
interface LibrarySettings {
    /** Also compare files with no camera metadata by how they look. Slower. */
    perceptual: boolean;
    /** Long edge of the stored previews in pixels (#662); 0 makes none. A new size remakes them at the next scan. */
    previewSize: number;
    /** Where backups go (#673); unset, on another disk in the library than the database's when one is connected, else beside it. */
    backupFolder?: string | null;
    /** Days between backups; 0 makes none on a schedule. */
    backupEveryDays: number;
    /** Backups kept in the folder; the oldest go. */
    backupsKept: number;
    /** When one shoot ends and the next begins (#669). */
    shoots: ShootRules;
}
type ScanPhase = 'queued' | 'walking' | 'headers' | 'hashing' | 'sensor' | 'perceptual' | 'done';
interface ScanProgress {
    phase: ScanPhase;
    walked: number;
    walkedBytes: number;
    headersTotal: number;
    headersDone: number;
    hashTotal: number;
    hashDone: number;
    hashedBytes: number;
    sensorTotal: number;
    sensorDone: number;
    perceptualTotal: number;
    perceptualDone: number;
    errors: number;
    current: string;
    cancelled: boolean;
    failure: string | null;
    /** Files found moved since the last scan, followed to their new path (#661). */
    relinked: LibraryMoved[];
}
/** What is picked in the Library view's panel (#664). */
type LibraryPick = {
    kind: 'all';
} | {
    kind: 'source';
    id: number;
} | {
    kind: 'folder';
    path: string;
} | {
    kind: 'year';
    year: number;
} | {
    kind: 'month';
    year: number;
    month: number;
} | {
    kind: 'day';
    year: number;
    month: number;
    day: number;
} | {
    kind: 'undated';
} | {
    kind: 'collection';
    name: string;
} | {
    kind: 'smart_collection';
    name: string;
} | {
    kind: 'keyword';
    name: string;
} | {
    kind: 'person';
    name: string;
}
/** A place, `Norway|Rogaland|Bryne`, and the places under it (#668). */
 | {
    kind: 'place';
    name: string;
}
/** The pictures inside a part of the map (#668). */
 | {
    kind: 'area';
    south: number;
    west: number;
    north: number;
    east: number;
}
/** The shoots as a timeline (#669). */
 | {
    kind: 'shoots';
}
/** One shoot: the one holding this moment (epoch ms). */
 | {
    kind: 'shoot';
    at: number;
}
/** The pictures of a to-do list (#670). */
 | {
    kind: 'todo';
    list: 'not_culled' | 'picks_not_developed' | 'edited_not_delivered' | 'not_backed_up';
} | {
    kind: 'camera';
    name: string;
} | {
    kind: 'lens';
    name: string;
}
/** Disks & Duplicates. */
 | {
    kind: 'duplicates';
}
/** The pictures that look like this one, nearest first (#809). */
 | {
    kind: 'similar';
    path: string;
};
type LibrarySort = 'capture_time' | 'name' | 'rating' | 'added' | 'edited' | 'own' | 'relevance';
/** What the Library view's search field searches by (#809). */
type SearchBy = 'both' | 'words' | 'content';
/** Search by what is in the pictures: the model, and how far it has read the library (#809). */
interface ContentInfo {
    /** The content model is downloaded. */
    model: boolean;
    /** Pictures read, of how many can be. */
    read?: number | null;
    of?: number | null;
    /** Bytes downloaded, of how many, while the model downloads. */
    downloading?: number[] | null;
    /** Why the last download stopped. */
    error?: string | null;
}
/** The Library view: what is picked, searched and sorted, and what it opened. */
interface LibraryViewInfo {
    /** The window is showing it. */
    shown: boolean;
    pick: LibraryPick;
    search: string;
    sort: LibrarySort;
    /** What the search field searches by (#809). */
    search_by: SearchBy;
    /** What the window works on. */
    source?: Source | null;
    /** Pictures shown. */
    pictures: number;
}
/** What to pick, search and sort in the Library view; what is left out stays. */
interface LibraryViewBody {
    pick?: LibraryPick | null;
    search?: string | null;
    sort?: LibrarySort | null;
    /** The words written in the files, what is in the pictures, or both (#809). */
    search_by?: SearchBy | null;
}
/** A value in the Library panel and how many pictures have it. */
interface LibraryNamed {
    /** The value, as a pick or search names it. */
    name: string;
    count: number;
    /** How to show it when not as `name`: a camera whose model names its make already. */
    label?: string;
}
/** A folder in the Library panel and the pictures in it and below. */
interface LibraryFolderNode {
    name: string;
    path: string;
    count: number;
    folders: LibraryFolderNode[];
}
/** A disk or registered folder in the Library panel. */
interface LibrarySourceTree {
    id: number;
    label: string;
    root: string;
    connected: boolean;
    lastScanAt?: number | null;
    count: number;
    missing: number;
    folders: LibraryFolderNode[];
}
interface LibraryDay {
    day: number;
    count: number;
}
interface LibraryMonth {
    month: number;
    count: number;
    days: LibraryDay[];
}
interface LibraryYear {
    year: number;
    count: number;
    months: LibraryMonth[];
}
/** What the Library view's panel lists, with counts (#664). */
interface LibraryBrowse {
    sources: LibrarySourceTree[];
    /** Newest first. */
    years: LibraryYear[];
    undated: number;
    keywords: LibraryNamed[];
    people: LibraryNamed[];
    places: LibraryNamed[];
    cameras: LibraryNamed[];
    lenses: LibraryNamed[];
}
/** One value of a filter column and how many pictures have it. */
interface FacetValue {
    /** What a pick names. */
    value: string;
    /** How it is shown. */
    label: string;
    count: number;
    /** A year's months, a month's days. */
    values?: FacetValue[];
}
/** A filter column: its values with counts, given the other columns' picks. */
interface FacetColumn {
    facet: FilterColumn;
    values: FacetValue[];
}
/** The filter's columns: the search, the picks, every column's counts, and how many pictures are shown. */
interface LibraryFilterInfo {
    /** Words, `"a phrase"`, `-left out`. */
    text: string;
    columns: FilterColumnPicks;
    counts: FacetColumn[];
    shown: number;
}
/** The search and the picks to set; what is left out stays, `columns` replaces every pick. */
interface LibraryFilterBody {
    text?: string | null;
    columns?: FilterColumnPicks | null;
}
/** An album (hand-picked) or a smart collection (a saved search). */
type CollectionKind = 'album' | 'smart';
/** A set of collections, at the top or in another set. */
interface CollectionSet {
    id: number;
    name: string;
    parentId?: number | null;
}
/** One album or saved search, with how many pictures it holds. */
interface CollectionInfo {
    id: number;
    kind: CollectionKind;
    name: string;
    setId?: number | null;
    /** An album's pictures, or what a saved search finds now. */
    count: number;
    /** The album ⇧B fills. */
    target: boolean;
}
/** Every collection and set, and the target album. */
interface CollectionsInfo {
    sets: CollectionSet[];
    collections: CollectionInfo[];
    target?: string | null;
}
interface NewCollectionBody {
    kind: CollectionKind;
    name: string;
    /** A saved search's criteria. */
    filter?: CatalogFilter | null;
    set_id?: number | null;
}
/** What to change; `setId: null` moves it to the top. */
interface ChangeCollectionBody {
    name?: string | null;
    setId?: number | null;
    filter?: CatalogFilter | null;
}
/** Pictures into an album or out of it, by path; `selected` puts the selection in. */
interface AlbumItemsBody {
    add?: string[];
    remove?: string[];
    selected?: boolean;
    /**
     * A picture whose shot the album holds in another file (the raw of a DxO
     * DNG in it) takes that file's place, and goes out over it where the album
     * is published. Without it the picture is left out.
     */
    replace?: boolean;
}
interface AlbumItemsDone {
    added: number;
    removed: number;
    count: number;
    /** Pictures asked in whose shot the album holds in another file. */
    same_shot: SameShotDone[];
}
/** A picture whose shot the album holds in another file. */
interface SameShotDone {
    /** The picture asked in. */
    file: string;
    /** The album's file of the same shot. */
    there: string;
    /** Left out, put in that file's place, or not, as it was bought. */
    outcome: 'left' | 'replaced' | 'sold';
}
/** What a collection holds, in its order. */
interface CollectionPictures {
    paths: string[];
    total: number;
}
interface NewSetBody {
    name: string;
    parentId?: number | null;
}
/** What to change; `parentId: null` moves it to the top. */
interface ChangeSetBody {
    name?: string | null;
    parentId?: number | null;
}
/** A keyword as the list shows it (#667): from the pictures, the keyword list, or both. */
interface KeywordEntry {
    name: string;
    /** Its place in the hierarchy, `Places|Norway|Bryne`. */
    path: string;
    /** Pictures with it or one under it. */
    count: number;
    /** Some picture has it; only in the list, it is shown greyed. */
    used: boolean;
    synonyms: string[];
    /** Written into exported files. */
    export: boolean;
    keywords: KeywordEntry[];
}
/** How far a keyword change has been written. */
interface RewriteStatus {
    running: boolean;
    what: string;
    total: number;
    done: number;
    /** Pictures on a disk that is away: written when it is back. */
    waiting: number;
    errors: string[];
}
/** A correction made to what a workflow delivered (#911). */
interface Correction {
    id: number;
    /** Seconds since 1970. */
    at: number;
    /** The picture, as the workflow took it. */
    path: string;
    workflow: string;
    /** `photographer`, `api` or `claude`. */
    by: string;
    reason: string;
    /** `crop`, `exposure`, `numbers`, `caption`, or `held`. */
    kind: string;
    /** The develop shape an exposure correction moves: a preset's name, or `auto`. */
    shape: string;
    /** What the workflow did, and what it was corrected to. */
    was: unknown;
    now: unknown;
}
interface KeywordsInfo {
    keywords: KeywordEntry[];
    job: RewriteStatus;
    waiting: number;
    /** What Undo would put back. */
    undo?: string | null;
    /** The keyword ⇧K paints with. */
    paint?: string | null;
}
/** When one shoot ends and the next begins (#669). */
interface ShootRules {
    /** Hours between two pictures that start a new shoot. */
    gapHours: number;
    /** Kilometres between two pictures with a position that start a new shoot; 0 for none. */
    distanceKm: number;
}
/** One shoot (#669). */
interface Shoot {
    /** The first picture's capture time, epoch ms: what names the shoot to rename, merge or open it. */
    startMs: number;
    endMs: number;
    name: string;
    /** What named it. */
    namedBy: 'user' | 'album' | 'folder' | 'place' | 'date';
    /** The library's id of the cover picture. */
    coverId?: number | null;
    /** Where the cover picture is. */
    coverPath?: string | null;
    coverChosen: boolean;
    count: number;
    /** The camera bodies, most pictures first. */
    cameras: string[];
    /** The place most of its pictures are at. */
    place?: string | null;
    /** How far it has come (#670). */
    status: 'not_culled' | 'culled' | 'edited' | 'delivered';
    /** Marked done: off the to-do lists. */
    done: boolean;
    /** Pictures with a mark, picks, picks not developed, edited, sent. */
    marked: number;
    picks: number;
    picksUndeveloped: number;
    edited: number;
    sent: number;
    /** Pictures that exist on one physical drive only (#259). */
    unbacked: number;
}
/** A source and its pictures that exist on its drive only (#259). */
interface SourceCoverage {
    id: number;
    label: string;
    root: string;
    /** The physical drive it is on. */
    drive: string;
    connected: boolean;
    /** When it was last scanned, seconds since 1970. */
    lastScanAt?: number | null;
    pictures: number;
    onlyHere: number;
}
interface BackupCoverage {
    sources: SourceCoverage[];
    /** The backup being made. */
    backup: RewriteStatus;
}
/** Which of the pictures on one drive only a backup copies. */
type BackupScope = {
    kind: 'all';
} | {
    kind: 'source';
    id: number;
} | {
    kind: 'shoot';
    at: number;
};
/** A change made to a picture whose disk is not connected, waiting for it (#260). */
type PendingChange = {
    kind: 'marks';
    rating: number;
    flag: number;
    label?: string | null;
} | {
    kind: 'field';
    field: string;
    values: string[];
} | {
    kind: 'retag';
    from: string;
    to?: string | null;
};
interface PendingWrite {
    path: string;
    change: PendingChange;
}
/** A to-do list's size: shoots, and pictures (#670). */
interface TodoCount {
    shoots: number;
    pictures: number;
}
/** What is left across the shoots not marked done (#670). */
interface Todo {
    notCulled: TodoCount;
    picksNotDeveloped: TodoCount;
    editedNotDelivered: TodoCount;
    /** Pictures that exist on one physical drive only (#259). */
    notBackedUp: TodoCount;
}
interface ShootsInfo {
    /** Newest first. */
    shoots: Shoot[];
    rules: ShootRules;
}
/** A change the photographer makes to the shoots (#669). */
type ShootEdit = {
    kind: 'rename';
    at: number;
    name?: string | null;
} | {
    kind: 'merge_with_previous';
    at: number;
} | {
    kind: 'split';
    at: number;
} | {
    kind: 'cover';
    id: number;
} | {
    kind: 'done';
    at: number;
    done: boolean;
} | {
    kind: 'done_between';
    from: number;
    to: number;
    done: boolean;
};
/** A place and the pictures there or under it (#668). */
interface PlaceUse {
    name: string;
    /** `Norway|Rogaland|Bryne`. */
    path: string;
    count: number;
    /** Of `count`, the pictures whose place is from their position: the file names none. */
    derived: number;
    places: PlaceUse[];
}
interface PlacesInfo {
    places: PlaceUse[];
    /** The place names found from positions being written into files. */
    writing: RewriteStatus;
    /** GeoNames data is on disk, so places are found from positions. */
    finding: boolean;
}
/** The pictures in one cell of the map. */
interface Cluster {
    latitude: number;
    longitude: number;
    count: number;
    /** South, west, north, east: open it with `setView({ pick: { kind: 'area', … } })`. */
    cell: number[];
}
interface LibraryMap {
    clusters: Cluster[];
    /** Pictures in the library with no position. */
    withoutPosition: number;
}
/** One backup of the library (#673). */
interface LibraryBackup {
    /** The backup's folder. */
    path: string;
    /** When it was made, seconds since 1970. */
    at: number;
    bytes: number;
}
/** Something about the library's backups the photographer can act on. */
type BackupNotice = 
/** The database failed its integrity check: restore from a backup. */
{
    kind: 'damaged';
    problem: string;
}
/** No backup for more than twice the interval: back up now. */
 | {
    kind: 'overdue';
    since?: number | null;
};
/** The library's backups. */
interface LibraryBackupList {
    /** Newest first. */
    backups: LibraryBackup[];
    /** Where the next backup goes. */
    folder: string;
    notice?: BackupNotice | null;
}
/** One file the library followed to a new path (#661). */
interface LibraryMoved {
    id: number;
    from: string;
    to: string;
}
interface ScanStatus {
    running: boolean;
    progress: ScanProgress | null;
}
/** The keys that decide how two files relate. Hashes are hex BLAKE3. */
interface MatchKeys {
    fileHash: string | null;
    payloadHash: string | null;
    sensorHash: string | null;
    captureKey: string | null;
    captureStrong: boolean;
    serial: string | null;
    /** 64-bit dHash as 16 hex digits — a number would lose bits past 2^53. */
    phash: string | null;
}
interface GroupFile {
    id: number;
    sourceId: number;
    sourceLabel: string;
    connected: boolean;
    relPath: string;
    absPath: string;
    size: number;
    mtime: number;
    format: string | null;
    make: string | null;
    model: string | null;
    captureTimeMs: number | null;
    width: number | null;
    height: number | null;
    rating: number | null;
    /** From its XMP: 1 picked, -1 rejected, 0 neither. */
    flag: number;
    /** The colour label's `xmp:Label` name ("Red", "Yellow", …). */
    label: string | null;
    hasSidecar: boolean;
    hasEdits: boolean;
    /** Files with the same cluster number are interchangeable copies. */
    cluster: number;
    inMatchedFolder: boolean;
    keys: MatchKeys;
}
interface DuplicateGroup {
    id: number;
    files: GroupFile[];
    relations: Relation[];
    recoverableBytes: number;
}
interface FolderEntry {
    sourceId: number;
    sourceLabel: string;
    connected: boolean;
    relPath: string;
    absPath: string;
    fileCount: number;
    totalSize: number;
}
interface FolderGroup {
    id: string;
    entries: FolderEntry[];
    fileCount: number;
    totalSize: number;
    /** False when some copies carry different metadata. */
    allIdentical: boolean;
    recoverableBytes: number;
}
interface LibraryStats {
    files: number;
    groups: number;
    dirGroups: number;
    recoverableBytes: number;
    byRelation: {
        relation: Relation;
        groups: number;
    }[];
}
interface ResultsPage {
    ready: boolean;
    computing: boolean;
    error: string | null;
    stats: LibraryStats | null;
    dirGroups: FolderGroup[];
    totalGroups: number;
    groups: DuplicateGroup[];
}
interface TrashReport {
    trashed: string[];
    bytes: number;
    skipped: {
        path: string;
        reason: string;
    }[];
}
/** What excluding a folder took out of the library. */
interface ExcludeAdded {
    removedFiles: number;
}
/** A folder on a source, relative to its root. */
interface FolderRef {
    sourceId: number;
    relPath: string;
}
/**
 * Which page of results, and of which relation: query parameters, so a type
 * alias (an interface cannot stand where a query map is taken).
 */
type ResultsQuery = {
    relation?: Relation;
    offset?: number;
    limit?: number;
};
interface MergeReport {
    keeper: string;
    merged: string[];
    notMerged: string[];
}
/**
 * The library (#258): disks the app has scanned, and the images that exist in
 * more than one place on them — identical, with different metadata, converted
 * (same raw data), or as another version of the same capture.
 */
declare class LibraryNamespace {
    private readonly http;
    constructor(http: HttpClient);
    sources(): Promise<LibrarySource[]>;
    /** Register a folder or a disk. Refused when it overlaps a source. */
    addSource(path: string): Promise<LibrarySource>;
    /**
     * The albums and folders on the photographer's other devices (#864): their
     * pictures' previews go to the site, with FLOGVIT.plus. Nothing goes there
     * unless it is put on.
     */
    onDevices(): Promise<OnDevices>;
    /** Put an album or a folder on the devices (`on`), or take it off. */
    setOnDevices(set: {
        album: string;
    } | {
        folder: string;
    }, on: boolean): Promise<OnDevices>;
    /** Forget a source and what was scanned on it. Files are not touched. */
    removeSource(id: number): Promise<void>;
    /** Mounted volumes not registered as a source. */
    volumes(): Promise<LibraryVolume[]>;
    /** Skip a folder (relative to the source root) in scans. */
    exclude(sourceId: number, relPath: string): Promise<ExcludeAdded>;
    include(sourceId: number, relPath: string): Promise<void>;
    settings(): Promise<LibrarySettings>;
    setSettings(settings: LibrarySettings): Promise<LibrarySettings>;
    /** Start a scan of the given sources, or every connected one. */
    scan(sourceIds?: number[]): Promise<void>;
    scanStatus(): Promise<ScanStatus>;
    cancelScan(): Promise<void>;
    /** Scan and wait until the scan and the regrouping after it are done. */
    scanAndWait(sourceIds?: number[], pollMs?: number): Promise<ScanProgress | null>;
    /** Folder copies and duplicate groups, largest recoverable first. */
    results(options?: ResultsQuery): Promise<ResultsPage>;
    refresh(): Promise<void>;
    /**
     * The library's row for the file at `path`, with everything photosuite has
     * told it so far; `null` when the file is not in the library.
     */
    file(path: string): Promise<LibraryFile | null>;
    /**
     * The library's stored preview of file `id` (#662): a JPEG at the
     * library's preview size, upright, there whether its disk is connected or
     * not; `null` when it has none.
     */
    preview(id: number): Promise<Uint8Array | null>;
    /** The Library view: what is picked, searched and sorted, and what it opened (#664). */
    view(): Promise<LibraryViewInfo>;
    /** Show the Library view with this picked, searched and sorted; it opens, and the answer says what. */
    setView(body: LibraryViewBody): Promise<LibraryViewInfo>;
    /** Search by content: whether the model is here and how far it has read the library (#809). */
    content(): Promise<ContentInfo>;
    /** Download the content model, so pictures can be found by what is in them (#809). */
    downloadContentModel(): Promise<void>;
    /** Every shoot, newest first, and the rules they were found by (#669). */
    shoots(): Promise<ShootsInfo>;
    /** Per source, the pictures that exist on its drive only, and the backup being made (#259). */
    backupCoverage(): Promise<BackupCoverage>;
    /** Copy the pictures on one drive only into `dest`, each checked, with their sidecars; `dest` is scanned so they count (#259). */
    backUpPictures(scope: BackupScope, dest: string): Promise<RewriteStatus>;
    /** The changes made to pictures whose disk is not connected, written when it is back (#260). */
    pendingWrites(): Promise<PendingWrite[]>;
    /** What is left across the shoots not marked done: not culled, picks not developed, edited not delivered (#670). */
    todo(): Promise<Todo>;
    /** Rename a shoot, make it one with the shoot before, split it at a picture, give it a cover, or mark it done; the shoots are found again. */
    editShoot(edit: ShootEdit): Promise<string>;
    /** The hours between pictures and kilometres between positions that start a new shoot; the shoots are found again. */
    setShootRules(rules: ShootRules): Promise<string>;
    /** Every place as a tree with counts: what the files say, else where the positions are (#668). */
    places(): Promise<PlacesInfo>;
    /** Write the places found from positions into the files that name none — of this place, or of every place. */
    writePlaceNames(path?: string): Promise<RewriteStatus>;
    /** The library's pictures in a box, counted in cells of the map at `zoom` (0–19). */
    map(box: {
        south: number;
        west: number;
        north: number;
        east: number;
        zoom: number;
        cell?: number;
    }): Promise<LibraryMap>;
    /**
     * The corrections made to what workflows delivered, newest first (#911):
     * the picture, what the workflow did, what it was corrected to, why, and who.
     */
    corrections(): Promise<Correction[]>;
    /** Take a correction away, and what was learnt from it. */
    removeCorrection(id: number): Promise<Correction>;
    /** Take every correction away. */
    clearCorrections(): Promise<Done>;
    /** Every keyword the pictures have and the keyword list's, as a tree with counts (#667). */
    keywords(): Promise<KeywordsInfo>;
    /** Rename a keyword (by its path) in every picture that has it and in the list. */
    renameKeyword(path: string, name: string): Promise<RewriteStatus>;
    /** Make a keyword part of another: every picture with it has the other instead. */
    mergeKeyword(path: string, into: string): Promise<RewriteStatus>;
    /** Move a keyword under another, or to the top with `null`. */
    moveKeyword(path: string, parent: string | null): Promise<RewriteStatus>;
    /** Take a keyword, and what is under it, off every picture and out of the list. */
    deleteKeyword(path: string): Promise<RewriteStatus>;
    /** Every picture's keywords as they were before the last change. */
    undoKeywordChange(): Promise<string>;
    /** A keyword's synonyms and whether it is exported. */
    setKeywordVocabulary(path: string, change: {
        synonyms?: string[];
        export?: boolean;
    }): Promise<void>;
    /** The keyword list in Lightroom's text format. */
    exportKeywordList(): Promise<string>;
    /** A Lightroom keyword list added to the list. */
    importKeywordList(text: string): Promise<string>;
    /** The keyword ⇧K paints with; `null` for none. */
    setPaintKeyword(path: string | null): Promise<void>;
    /** ⇧K: the painting keyword on the selection, or off it when every picture has it. */
    paintKeyword(): Promise<string>;
    /** Every album and saved search with its count, the sets, and the target album (#666). */
    collections(): Promise<CollectionsInfo>;
    /** A new album, or a saved search with `filter`, in a set when given. */
    newCollection(body: NewCollectionBody): Promise<CollectionsInfo>;
    /** Rename a collection, move it to a set, or change a saved search's criteria. */
    changeCollection(id: number, body: ChangeCollectionBody): Promise<CollectionsInfo>;
    /** Delete a collection; its pictures stay where they are. */
    deleteCollection(id: number): Promise<void>;
    /** A copy of a collection beside it, named "… copy". */
    duplicateCollection(id: number): Promise<CollectionsInfo>;
    /** Pictures into album `id` or out of it. */
    albumItems(id: number, body: AlbumItemsBody): Promise<AlbumItemsDone>;
    /** What a collection holds: an album's pictures in its order, or what a saved search finds now. */
    collectionPictures(id: number): Promise<CollectionPictures>;
    /** The album ⇧B fills; `null` for none. */
    setTargetAlbum(name: string | null): Promise<void>;
    /** ⇧B: the selection into the target album, or out of it when every picture is in it. */
    toggleTargetAlbum(): Promise<string>;
    /** A new collection set, at the top or in another. */
    newCollectionSet(body: NewSetBody): Promise<CollectionSet>;
    /** Rename a set or move it into another. */
    changeCollectionSet(id: number, body: ChangeSetBody): Promise<void>;
    /** Delete a set; what was in it moves to its parent. */
    deleteCollectionSet(id: number): Promise<void>;
    /** The filter's columns over what the window works on (#665): counts for every column. */
    filter(): Promise<LibraryFilterInfo>;
    /** Search and pick in the filter's columns; the window shows what they leave. */
    setFilter(body: LibraryFilterBody): Promise<LibraryFilterInfo>;
    /** Keep what the window shows — its source, the search and the picks — as a smart collection. */
    saveFilter(name: string): Promise<void>;
    /** What the Library view's panel lists, with how many pictures each holds. */
    browse(): Promise<LibraryBrowse>;
    /** Every backup of the library (#673), where the next goes, and a damaged library or an overdue backup. */
    backups(): Promise<LibraryBackupList>;
    /** Back the library up now, with the albums and saved searches. A damaged library is refused. */
    backUp(): Promise<LibraryBackup>;
    /** Put a backup (its folder, from `backups()`) back as the library; the connected disks are scanned after. */
    restore(path: string): Promise<void>;
    /** Files a scan did not find where they were (#661), on one source or all. */
    missing(sourceId?: number): Promise<LibraryFile[]>;
    /**
     * A missing file is in `folder` now: it follows, keeping its id, and so do
     * the missing files from its old folder found there by name (Locate…).
     */
    relink(id: number, folder: string): Promise<LibraryMoved[]>;
    /** Forget missing files: they are gone. A file that is there is not forgotten. */
    forgetMissing(ids: number[]): Promise<void>;
    /**
     * The group a file belongs to: its copies, and other versions of the same
     * capture — such as the raw file a DxO or Lightroom DNG was made from.
     * `null` when it has none.
     */
    related(path: string): Promise<DuplicateGroup | null>;
    /**
     * Move files to the trash. The server never removes every file of a group,
     * nor the last copy of an image unless allowed: `true` allows it for every
     * file in `fileIds`, a list of ids for those files only.
     */
    trash(fileIds: number[], allowLastOfCluster?: boolean | number[]): Promise<TrashReport>;
    /** Trash folders of a folder copy; one always remains. */
    trashFolders(dirGroupId: string, folders: FolderRef[]): Promise<TrashReport>;
    /**
     * Bring rating, labels, keywords, people, IPTC text and the Copyrighted mark
     * into the kept file. Resolves once its sidecar is written.
     */
    mergeMetadata(keeperId: number, fromIds: number[]): Promise<MergeReport>;
}

interface CullingClientOptions {
    host?: string;
    port?: number;
    /** Full base URL override (e.g. "http://192.168.1.5:9100") */
    baseUrl?: string;
    /**
     * API token, sent as `Authorization: Bearer`. Defaults to the
     * `CULLING_API_TOKEN` environment variable, then to the token photosuite
     * wrote to `~/.culling/api.json` when its API server started (which also
     * gives the address when no host, port or baseUrl is given) — keep it out
     * of scripts. Every /api/* route answers 401 without it when one is
     * configured.
     */
    token?: string;
    /**
     * Open the event connection by itself, the first time something listens
     * (`on`, `waitFor`). Default: true. Not at creation: an open connection
     * keeps the process running, so a script that only calls routes would
     * never end (#850). With false, only `connect()` opens it.
     */
    autoConnect?: boolean;
    /**
     * The folder of an instance started with `--dir` (#940): the client
     * finds it through `<root>/.photosuite/culling/api.json`, which that
     * instance writes, instead of the machine's `~/.culling/api.json`. Paths
     * sent to and read from such an instance are in its root: `/` is the root.
     */
    root?: string;
    /** WebSocket reconnect delay in ms. Default: 1000 */
    reconnectDelay?: number;
    /** Max WebSocket reconnect delay in ms. Default: 30000 */
    maxReconnectDelay?: number;
}
/** POST /api/pick-for-delivery */
interface PickForDeliveryBody {
    /** At most this many pictures. */
    count: number;
    /** The color label the picks get; green when left out. */
    label?: string | null;
}
/** One picture picked for a delivery, and why. */
interface Picked {
    path: string;
    /** The participants it covers. */
    covers: string[];
    why: string;
    /** The next best for the same participant. */
    alternatives: string[];
}
/** What Pick for Delivery did. */
interface PickReport {
    picked: Picked[];
    /** Participants with a picture who got none. */
    uncovered: string[];
    /** Pictures with someone in them that were considered. */
    considered: number;
    /** What the score was: sharpness, or the editor's learned taste. */
    scored_by: string;
}
/** POST /api/albums/download */
interface AlbumDownloadBody {
    /** A public Flickr album's link, or a SmugMug gallery link or node id. */
    link: string;
    /** The folder the pictures go into (an event's in/). */
    folder: string;
    /** A SmugMug connection, for a SmugMug album. */
    connection?: string | null;
    /** For SmugMug: the folder under `folder` its pictures go into. */
    photographer?: string | null;
    /** For Flickr: one folder per photographer, from each title's first word. */
    splitByTitle?: boolean;
    /** The originals instead of Flickr's largest derived size or SmugMug's 4K. */
    originals?: boolean;
}
/** How far an album download has come. */
interface DownloadStatus {
    running: boolean;
    album: string;
    folder: string;
    total: number;
    downloaded: number;
    skipped: number;
    failed: number;
    paceSecs: number;
    errors: string[];
}
/** POST /api/event-site */
interface EventSiteBody {
    eventDir: string;
    delivered?: string[];
    siteDir: string;
    slug?: string | null;
    smugmugConnection?: string | null;
    smugmugAlbum?: string | null;
    localFlag?: boolean;
    photographerByFolder?: boolean;
    /** A runner read less than this share of the sharpest person in a picture of several loses their number there (#810). */
    softRunnerShare?: number | null;
    /** Each runner's pictures listed sharpest-on-them first (#810). */
    sharpestFirst?: boolean;
}
/** What a gallery build wrote. */
interface SiteSummary {
    manifest: string;
    images: number;
    participants: number;
    featured: number;
}
/** POST /api/best-of-year */
interface BestOptions {
    eventDir: string;
    count?: number;
    /** A local vision model for the shortlist; none: Vision alone. */
    model?: string | null;
    shortlist?: number | null;
    forgetRejections?: boolean;
}
/** How far the year's best has come. */
interface BestStatus {
    running: boolean;
    stage: string;
    pictures: number;
    picked: number;
    kept: number;
    rejected: number;
    folder: string;
    error?: string | null;
}
/** POST /api/workflows/run-on-folder */
interface FolderRunBody {
    /** The workflow, by name. */
    workflow: string;
    /** The folder of pictures (an event's in/), read with its subfolders. */
    folder?: string;
    /** An album or saved search, by name, instead of a folder (#666); `out` is then needed. */
    collection?: string | null;
    /** Where Export without a connection delivers; beside in/ when left out. */
    out?: string | null;
    /** "© <photographer> / <org>" for a picture credited from its folder. */
    org?: string | null;
    /** The photographer of pictures lying right in `folder`; the camera's name wins. */
    photographer?: string | null;
    /** Pictures at once (4 when left out). */
    workers?: number | null;
}
/** How far a folder run has come. */
interface FolderRunStatus {
    running: boolean;
    workflow: string;
    folder: string;
    total: number;
    done: number;
    failed: number;
    withNumbers: number;
    withoutNumbers: number;
    errors: string[];
}
/** PUT /api/numbers */
interface NumbersBody {
    /** Exactly these numbers; empty is nobody. */
    numbers?: string[] | null;
    /** Or numbers and names as typed ("12, Ida Hauge"), looked up in the start lists. */
    entry?: string | null;
    /** current, selected (default) or visible */
    scope?: string | null;
}
/** What setting numbers did. */
interface NumbersSet {
    pictures: number;
    /** Numbers of people a delivery does not name: not written. */
    unnamed: string[];
    /** Numbers no start list has: written as numbers, nobody named. */
    unknown: string[];
}
/** A read Identify held for a person. */
interface HoldInfo {
    /** "87", "3", or "@TIME21" for a timing point. */
    read: string;
    choices: string[];
    reason: string;
}
/** One picture in the open folder with a held read. */
interface HeldPicture {
    path: string;
    holds: HoldInfo[];
}
/** GET /api/review */
interface ReviewQueue {
    pictures: HeldPicture[];
}
/** One number on a picture, and who wears it. */
interface NumberOnPicture {
    number: string;
    who?: string | null;
}
/** GET /api/images/current/numbers */
interface CurrentNumbers {
    numbers: NumberOnPicture[];
}
/** GET/PUT /api/folder/start-lists */
interface FolderStartLists {
    /** Folders holding a teams.json; empty: every Identify step's. */
    dirs?: string[];
    numbersOnly?: boolean;
    /** The start lists that loaded, by event name (answers only). */
    loaded?: string[];
}
/** POST /api/delivery-report */
interface DeliveryReportBody {
    /** The start lists' folders; every Identify step's when left out. */
    rosters?: string[] | null;
}
/** What the hidden-digit guard costs one start list. */
interface GuardCost {
    start_list: string;
    numbers: number;
    /** Numbers a hidden first or last digit could be another number of. */
    exposed: number;
    pictures_held: number;
    pictures_named_exposed: number;
}
/** What a delivery contains. */
interface DeliveryReport {
    pictures: number;
    /** Different participants named. */
    participants: number;
    /** Participants in more than one picture. */
    in_several: number;
    /** Pictures with nobody named. */
    without_anyone: number;
    per_photographer: Record<string, number>;
    /** Participants per class, as the start list has it. */
    per_class: Record<string, number>;
    /** Frames Identify read, held a read in for a human, left someone unnamed in. */
    frames_read: number;
    frames_held: number;
    frames_unnamed: number;
    /** What the hidden-digit guard costs, per start list. */
    guard: GuardCost[];
    /** The report as text, for the e-mail. */
    summary: string;
    /** The captioned contact sheet. */
    pdf?: string | null;
}
/** One photographer at one event. */
interface PhotographerTally {
    photographer: string;
    /** The delivered folder, which is the event. */
    folder: string;
    delivered: number;
    published: number;
    /** The published pictures' file names. */
    files: string[];
}
/** GET /api/published/by-photographer */
interface PublishedByPhotographer {
    rows: PhotographerTally[];
    /** The same as CSV for the accounts. */
    csv: string;
}
/** POST /api/published/import */
interface ImportPublishedBody {
    /** published.json ([{file, caption}]) or one file name per line. */
    list: string;
}
/** What the editor publishes, learned as weights over the picker's features. */
interface Taste {
    weights: number[];
    bias: number;
    published: number;
    total: number;
}
/** What Import Published did. */
interface ImportReport {
    listed: number;
    matched: number;
    /** Published pictures recorded, across every folder imported. */
    published_known: number;
    /** The taste learned, once there is enough to learn from. */
    taste?: Taste | null;
}
/** A camera body and who shoots with it. */
interface Photographer {
    /** The body's serial number, as its EXIF gives it. */
    serial: string;
    name: string;
    /** The credit line a newspaper prints; empty: the name. */
    credit?: string;
}
/** GET/PUT /api/photographers */
interface PhotographerList {
    photographers: Photographer[];
}
/** POST /api/start-lists/fetch */
interface StartListFetchBody {
    /** The start list's page (race-result, elapse.no, racetracker.no, onreg) or a CSV file; empty fetches `dir` again. */
    source?: string;
    /** The event folder; omitted, ~/.culling/start-lists/<backend>-<event>. */
    dir?: string | null;
    /** A relay of teams whose numbers carry the heat, 07-422 (race-result only). */
    heat_numbers?: boolean;
    /** Also fetch the event's results: place and time per number (race-result). */
    results?: boolean;
    /** Contests not on the course, by part of their name (a spectator ticket); omitted keeps the folder's own. */
    exclude_contests?: string[] | null;
}
/** What a start-list fetch wrote. */
interface StartListSummary {
    dir: string;
    event: string;
    backend: string;
    participants: number;
    contests: string[];
    /** Entrants still without a number; fetch again once they have one. */
    pending_bib: number;
    /** Participants with a result, when results were fetched. */
    results: number;
}
/** What a route that does something says it did. */
interface Done {
    message: string;
}
/** POST /api/feedback — a bug report or feedback, sent on to photosuite.flogvit.com. */
interface Report {
    kind: 'BUG' | 'IDEA' | 'OTHER';
    message: string;
    /** Where to answer; empty for no answer. */
    reply_to?: string;
    /** Attach the app's log (default true). */
    include_log?: boolean;
    /** Attach the crash log (default true). */
    include_crash_log?: boolean;
}
/** A newer release the app's update check found. */
interface UpdateNotice {
    latest_version: string;
    url: string;
    covered: boolean;
    /** The app can install it itself (`installUpdate()`): covered, on macOS. */
    installable: boolean;
    /** What the versions after this one bring, newest first. */
    changes: ChangelogPost[];
}
/** One section of a changelog post: `New features`, `Polish`, `Bug fixes` or `Behind the scenes`. */
interface ChangelogSection {
    name: string;
    /** Each item as text, a link written `[label](https://…)`. */
    items: string[];
}
/** One version's post in the changelog. */
interface ChangelogPost {
    /** The first build that carries it; the release that brings it may be later. */
    version: string;
    /** `YYYY-MM-DD` */
    date: string;
    title: string;
    sections: ChangelogSection[];
}
/** GET /api/app/changelog */
interface AppChangelog {
    current_version: string;
    /** The posts this build carries, newest first. */
    posts: ChangelogPost[];
    /** What the newer version the update check found brings; empty without one. */
    available: ChangelogPost[];
    /** The version the app last showed what is new up to. */
    seen: string;
}
/** GET/POST /api/app/update */
interface AppUpdate {
    current_version: string;
    update?: UpdateNotice | null;
    /** The update is being installed; the app starts again when it is in place. */
    installing: boolean;
}
/** GET /api/about/licenses */
interface LicenseNotices {
    notices: string;
}
interface StatusInfo {
    folder: string | null;
    total_images: number;
    current_index: number | null;
    view_mode: string;
    sort_order: string;
    sort_direction: string;
    filter_active: boolean;
    /** A build for the documentation's screenshots, showing a demo's disks and pictures. */
    demo: boolean;
    /** The app runs with no window (`--headless`): routes that need one answer 409. */
    headless: boolean;
    /** The app runs in a root of its own (`--dir`): everything it keeps lives in that folder. */
    root: boolean;
    /** The graphics adapter the app draws and develops with; null when it develops on the CPU. */
    gpu: string | null;
    /** Develop renders shown straight from the GPU since the app started. */
    gpu_frames: number;
}
interface ImageInfo {
    index: number;
    filename: string;
    path: string;
    rating: number;
    flag: Flag;
    color_label: ColorLabel;
    tagged: boolean;
    masked: boolean;
    has_crop: boolean;
    file_type: 'raw' | 'jpeg' | 'other' | 'video';
    file_size: number | null;
    /**
     * How sharp the picture is where the camera focused (the subject when
     * focus missed it), higher is sharper: under 1000 very soft, 2000 soft,
     * 3000 normal, 5000 sharp. `null` until the background pass has run.
     */
    sharpness: number | null;
    /** The exposure verdict, `null` until the analysis pass has run. */
    exposure: 'underexposed' | 'good' | 'overexposed' | null;
    /** How many other images in the folder look near-identical. */
    near_duplicate_count: number;
    /** The faces (#131): `null` until read, and on other systems than macOS. */
    eyes: Eyes | null;
    /** Lightroom's settings in the file changed after the picture was developed here: `metadata.readFromFile()` takes them, `metadata.keepOwnDevelop()` keeps photosuite's. */
    lightroom_changed: boolean;
    /** In the target album, the one ⇧B fills (#927); false with no target, and while that album is the source. */
    in_target_album: boolean;
}
interface SelectionInfo {
    count: number;
    indices: number[];
}
/**
 * The view's state. Every on/off switch is here under the name
 * `ui_state_changed` reports it with, so read this on connect and follow the
 * events after.
 */
interface UiStateInfo {
    sidebar_visible: boolean;
    filmstrip_visible: boolean;
    folder_tree_visible: boolean;
    develop_panel_visible: boolean;
    scopes_visible: boolean;
    map_visible: boolean;
    fullscreen: boolean;
    presentation_mode: boolean;
    clipping: boolean;
    focus_peaking: boolean;
    shadow_detail: boolean;
    highlight_detail: boolean;
    noise_map: boolean;
    zebra: boolean;
    /** Every brightness zone in its own colour. */
    false_color: boolean;
    exif_overlay: boolean;
    /** The focus box: the eyes, the focus point or the sharpest spot, magnified. */
    focus_box: boolean;
    /** Sharpness read over the whole frame, the weakest part counting (#811). */
    landscape_sharpness: boolean;
    /** Each person outlined with how sharp they are beside the others (#810). */
    people_sharpness: boolean;
    /** Bursts split where the number, camera or lens changes (#806). */
    burst_split: boolean;
    /** The burst's pick shown with how far ahead it is (#807). */
    burst_pick_confidence: boolean;
    /** Bursts gone through one by one (#808). */
    burst_queue: boolean;
    auto_advance: boolean;
    /** The preview window: the develop view's picture in a window of its own. */
    preview_window: boolean;
    /** The Light Table: the grid in a window of its own. */
    light_table: boolean;
    /** The grid fullscreen here, the preview fullscreen on the second display. */
    dual_fullscreen: boolean;
    import_panel: boolean;
    keywords_panel: boolean;
    filter_columns: boolean;
    /** Every panel out of the way (Tab). */
    panels_hidden: boolean;
    /** The global exposure aid in EV — a way of looking, not an edit. */
    exposure_aid: number;
    channel: string;
    slideshow_active: boolean;
    slideshow_interval: number;
    compare_split: string;
    survey_count: number;
    /** The composition guide over the picture, `none` when off. */
    composition_guide: CropGrid;
    /** Which way a guide with a direction (spiral, triangles) faces: 0–3. */
    composition_turn: number;
    /** Screen pixels per UI point where the main window is: 2 on a Retina display. */
    pixels_per_point: number;
}
interface IptcInfo {
    headline: string;
    caption: string;
    keywords: string[];
    creator: string;
    creator_title: string;
    credit: string;
    source: string;
    instructions: string;
    copyright_notice: string;
    marked: boolean;
    usage_terms: string;
    copyright_url: string;
    location: string;
    city: string;
    state: string;
    country: string;
    country_code: string;
    /** The event shown (`Iptc4xmpExt:Event`). */
    event: string;
    /** People in the picture (`Iptc4xmpExt:PersonInImage`). */
    person_in_image: string[];
    /** `exif:UserComment` — where a transcribed voice note lands. */
    user_comment: string;
}
interface DenoiseQueueInfo {
    items: DenoiseQueueItemInfo[];
    batch_active: boolean;
    queued_count: number;
    processing_count: number;
    /** What the batch waits on that only the user can change: `other_open`, a
     * denoiser started elsewhere is open; `answer`, ours has stopped with its
     * window up and waits for the user. */
    waiting: 'nothing' | 'other_open' | 'answer';
}
interface DenoiseQueueItemInfo {
    index: number;
    filename: string;
    status: string;
}
/** A camera look learned from your pictures, or why one was not (#446). */
interface CameraLookInfo {
    make: string;
    model: string;
    /** The picture style, when the camera says (Sony's Creative Style). */
    style: string | null;
    /** `learned`, `stale` (in use; learned again after a develop-pipeline change) or `refused`. */
    status: string;
    pictures: number;
    /** Mean error against the camera JPEG with the look and without (0..1). */
    error: number | null;
    error_without: number | null;
    worst: string | null;
    reason: string | null;
    /** Seconds since the Unix epoch. */
    at: number;
}
interface CameraLookProgress {
    make: string;
    model: string;
    style: string | null;
    /** `candidate`, `scoring` or `noise`. */
    step: string;
    n: number;
    of: number;
}
interface CameraLooksInfo {
    enabled: boolean;
    looks: CameraLookInfo[];
    learning: CameraLookProgress | null;
}
interface CameraLookBody {
    make: string;
    model: string;
    style?: string | null;
}
interface DenoiseProfileInfo {
    name: string;
    program_type: string;
    is_active: boolean;
}
interface DenoiseSettingsInfo {
    auto_denoise_on_pick: boolean;
    /** Auto-denoise a picture rated at least this many stars; 0 is off. */
    auto_denoise_min_rating: number;
    active_profile: string | null;
}
interface ExportStatusInfo {
    active: boolean;
    current: number;
    total: number;
    export_type: string | null;
}
interface EditorInfo {
    name: string;
    path: string;
}
interface UploadQueueInfo {
    pending: number;
    total: number;
    /** Every file in the queue, as the upload panel lists it (#600). */
    items: UploadQueueEntry[];
    /** How many pictures are marked as sent somewhere. */
    transferred: number;
}
/** One file in the upload queue, for one destination. */
interface UploadQueueEntry {
    /** What `upload.removeItem()` takes. */
    index: number;
    path: string;
    /** The destination's place in `upload.destinations()`. */
    destination: number;
    status: 'queued' | 'uploading' | 'completed' | 'failed';
    error?: string | null;
}
interface UploadDestinationInfo {
    /** Position in the list — what the update and remove calls take. */
    index: number;
    /** The connection's name (same as `connection`). */
    name: string;
    /** The connection's type ("FTP", "Google Drive", …), or "missing". */
    destination_type: string;
    /** The connection files are sent to (Settings → Connections). */
    connection: string;
    /** Folder on the connection; `{date}` etc. are filled in when a file is sent. Empty: the connection's own. */
    remote_path: string;
}
/** What a find & replace changed. */
interface FindReplaceResult {
    replacements: number;
    images_modified: number;
}
/** POST /api/batch-rename */
interface BatchRenameOptions {
    /** A name template: `{date}`, `{seq}`, the file's own `{name}` … */
    template: string;
    start_number?: number;
}
/**
 * POST /api/import: a saved import preset (`preset`), or the dialog's
 * defaults. An empty `dest_path` is the preset's destination.
 */
interface ImportOptions {
    source_path: string;
    dest_path?: string;
    preset?: string;
    /** A saved IPTC template written into every picture; `""` for none. Left out: the one the import dialog opens with. */
    iptc_template?: string;
}
/**
 * The colour space an export is written in: `srgb` for the web and
 * newspapers, `display-p3`, `adobe-rgb`, `rec-2020` and `prophoto` keep what a
 * RAW holds beyond sRGB (`rec-2020` under the sRGB curve, as the develop draws it),
 * `original` keeps a finished picture's own profile.
 */
type ExportColorSpace = 'srgb' | 'display-p3' | 'adobe-rgb' | 'rec-2020' | 'prophoto' | 'original';
/**
 * What an export writes, chosen as the Export dialog chooses (#675): a saved
 * profile, and what is set in its place. `export()`, a script's export step
 * and a workflow's Export step take the same fields.
 */
interface ExportChoice {
    /** A saved export profile. */
    profile?: string | null;
    /** The file format, in place of the profile's; `original` copies the file. */
    format?: 'jpeg' | 'tiff' | 'png' | 'avif' | 'jxl' | 'webp' | 'jp2' | 'exr' | 'dng' | 'psd' | 'gif' | 'bmp' | 'qoi' | 'tga' | 'original';
    /** Bits per channel, in place of the profile's: 8 or 16 for a TIFF, PNG, PSD, JPEG XL or JPEG 2000, 32 for a float TIFF, 16 (half) or 32 for an EXR. */
    bit_depth?: 8 | 16 | 32;
    /** Quality (1–100) of a JPEG, AVIF, JPEG XL, WebP or JPEG 2000, in place of the profile's; a JPEG XL, WebP or JPEG 2000 at 100 is lossless. */
    quality?: number;
    /** HDR headroom in stops, in place of the profile's: 0 none, 1–3 a gain map that brightens the highlights of a JPEG, JPEG XL or AVIF on an HDR screen. */
    hdr_stops?: 0 | 1 | 2 | 3;
    /** How a TIFF is compressed, in place of the profile's: lossless LZW or ZIP (deflate). */
    tiff_compression?: 'none' | 'lzw' | 'deflate' | null;
    /** The colour space, in place of the profile's. AVIF takes srgb, display-p3 and rec-2020 only; JPEG XL every space but original. */
    color_space?: ExportColorSpace;
    /** How much metadata goes into each file, in place of the profile's. */
    metadata?: 'all' | 'without-gps' | 'without-people' | 'copyright-only' | 'nothing';
    /** An IPTC template laid over each picture's own; "" for none. */
    iptc_template?: string | null;
}
/** POST /api/export and /api/export/visible. No profile: the one the export dialog opens on. */
interface ExportOptions extends ExportChoice {
    /** Where the files go, in place of the profile's folder. */
    dest_folder?: string;
    /** Which pictures of a virtual-copy stack go: all, the originals or the copies. */
    vc_mode?: 'all' | 'originals_only' | 'copies_only';
}
/** POST /api/export/quick-jpeg */
interface QuickJpegOptions {
    /** Where the JPEG goes; the Quick Export setting when left out. */
    output_dir?: string;
}
/** PUT /api/denoise/settings */
interface DenoiseSettingsOptions {
    auto_denoise_on_pick?: boolean;
    /** Auto-denoise a picture rated at least this many stars (0–5); 0 is off. */
    auto_denoise_min_rating?: number;
}
/** A change to an upload destination: fields left out stay as they are. */
interface UploadDestinationChange {
    /** A connection that files can be written to. */
    connection?: string;
    /** Folder on the connection. */
    remotePath?: string;
}
/** A change to the filter: fields left out stay as they are. */
interface FilterOptions {
    min_rating?: number;
    rating_mode?: 'at_least' | 'exactly' | 'at_most';
    flag?: 'all' | 'pick' | 'reject' | 'unflagged';
    /** A label, or `'none'` to take the colour filter off. */
    color?: string;
    file_type?: 'all' | 'raw' | 'jpeg' | 'other' | 'video';
    /** `true`/`'tagged'`, `false`/`'untagged'`, or `'all'` for no tag filter. */
    tagged?: boolean | 'all' | 'tagged' | 'untagged';
    /** Uploaded (`'sent'`), not yet (`'not_sent'`), or no upload filter (`'all'`). */
    sent?: 'all' | 'sent' | 'not_sent';
    /** Camera model as the equipment list names it; `''` takes it off. */
    camera?: string;
    /** Lens as the equipment list names it; `''` takes it off. */
    lens?: string;
    /** Focal length as the equipment list names it (`'600 mm'`); `''` takes it off. */
    focal_length?: string;
    /** Aperture as the equipment list names it; `''` takes it off. */
    aperture?: string;
    /** ISO as the equipment list names it (`'100'`); `''` takes it off. */
    iso?: string;
    /**
     * Only pictures at least this sharp, measured where the camera focused
     * (under 1000 very soft, 2000 soft, 3000 normal, 5000 sharp, above that
     * very sharp); `0` takes it off.
     */
    min_sharpness?: number;
    /** Words to find in the pictures' metadata; `''` takes it off. */
    search?: string;
    /** Only pictures with a near-match in the folder. */
    near_match?: boolean;
    /** Only pictures with an exposure problem. */
    exposure_flagged?: boolean;
    /** Only pictures where someone's eyes are closed (faces are read on macOS). */
    eyes_closed?: boolean;
    /** Only pictures with a read Identify held for a person. */
    needs_review?: boolean;
    /** Only pictures inside this part of the map, `[south, west, north, east]`; `[]` takes it off (#668). */
    map_area?: number[];
    /** Leave out pictures on disks that are not connected (#260). */
    hide_offline?: boolean;
    /** Only the frames of bursts in this state (#808): `to_do`, `later`, `done`; `""` takes it off. */
    burst_state?: string;
    /** Only the pictures in the target album (`in`) or not in it (`out`), as ⇧B fills it (#927); `""` takes it off. */
    in_target_album?: 'in' | 'out' | '';
}
interface SortOptions {
    order: 'filename' | 'rating' | 'file_type' | 'capture_date' | 'sharpness';
    direction?: 'ascending' | 'descending';
}
interface BatchOptions {
    indices: number[];
    action: 'set_rating' | 'set_flag' | 'set_label' | 'toggle_tag' | 'move_to_trash' | 'queue_upload' | 'rotate' | 'clear_crop' | 'auto_crop' | 'set_mask';
    /**
     * Required by the setters and `rotate`: 0-5 for `set_rating`, a flag name
     * for `set_flag`, a label name (or `'none'`) for `set_label`, `true` or
     * `false` for `set_mask`, degrees (90, 180, 270, -90) for `rotate`. Missing
     * or of the wrong type is a 400.
     */
    value?: number | string | boolean;
}
/** Shapes the crop rectangle can be locked to. */
type CropAspect = 'free' | 'square' | '3x2' | '4x3' | '16x9' | '5x4';
/** Guides drawn over the crop rectangle. */
type CropGrid = 'none' | 'thirds' | 'golden' | 'diagonals' | 'triangles' | 'spiral' | 'fine';
/** POST /api/ui/composition-guide: the guide over the picture while viewing and editing. */
interface CompositionGuideBody {
    /** The guide; `none` turns it off. Left out, the guide stays. */
    guide?: CropGrid;
    /** Face a guide with a direction (spiral, triangles) the next way round. */
    turn?: boolean;
}
interface CropToolOptions {
    /** Open the tool, or close it. Closing crops unless `apply` says otherwise. */
    active?: boolean;
    /**
     * What closing does: `true` (the default) crops the image, `false` puts the
     * tool away and leaves the picture whole — the Cancel button.
     */
    apply?: boolean;
    aspect?: CropAspect;
    /** Swap width and height of the locked ratio. */
    portrait?: boolean;
    grid?: CropGrid;
    /** Show the whole frame with the crop drawn on it. */
    show_frame?: boolean;
    /** Arm the straighten gesture: a drag on the picture sets the angle. */
    straighten?: boolean;
    /** The toolbar's Reset: the rectangle back to the whole frame. */
    reset?: boolean;
    /** Put auto-crop suggestion n (from 0) in the rectangle, the toolbar's ‹ ›. */
    suggestion?: number;
}
interface CropToolState {
    active: boolean;
    aspect: CropAspect;
    portrait: boolean;
    grid: CropGrid;
    show_frame: boolean;
    straighten: boolean;
    /** How far the rectangle turns before a turn pulls it in, in degrees. */
    angle_limit: number;
    /** How many auto-crop suggestions the picture has (`images.autoCrop()` makes them). */
    suggestions: number;
    /** The one in the rectangle, when one was picked. */
    suggestion?: number | null;
    /** How the rules of composition chose the suggestion auto-crop applies. */
    composition?: Composed | null;
}
/** A rule of composition auto-crop judges a crop by. */
type CompositionRule = 'thirds' | 'phi_grid' | 'golden_spiral' | 'diagonals' | 'dynamic_symmetry' | 'centred' | 'eye_line' | 'horizon' | 'lead_room' | 'headroom' | 'negative_space' | 'fill' | 'leading_lines' | 'curves' | 'vanishing_point' | 'triangle' | 'structure' | 'symmetry' | 'repetition' | 'odds' | 'frame_in_frame' | 'foreground' | 'figure_ground' | 'balance' | 'bright_edges' | 'tangents' | 'joints' | 'distractions' | 'level' | 'sport_context' | 'taught';
/** What kind of picture the rules took it for, which sets their weights. */
type CompositionGenre = 'portrait' | 'group' | 'sport' | 'wildlife' | 'landscape' | 'architecture' | 'other';
/** One rule's say on a crop. */
interface RuleScore {
    rule: CompositionRule;
    /** 0..1; null where the picture gave the rule nothing to judge. */
    score?: number | null;
    /** How much it counts: fitted to this user's own crops of the genre. */
    weight: number;
}
/** A crop the rules of composition chose, and what it follows. */
interface Composed {
    crop: CropOptions;
    /** The weighted mean of the rules, 0..1. */
    score: number;
    scores: RuleScore[];
    /** The rules it follows best, as the crop tool lists them. */
    follows: CompositionRule[];
    genre?: CompositionGenre | null;
}
interface CropOptions {
    top: number;
    left: number;
    bottom: number;
    right: number;
    angle?: number;
}
interface IptcPatch {
    headline?: string;
    caption?: string;
    caption_mode?: 'replace' | 'prefix' | 'append';
    /** The whole list, or one text split on commas. */
    keywords?: string[] | string;
    keywords_add?: string[];
    keywords_remove?: string[];
    creator?: string;
    creator_title?: string;
    credit?: string;
    source?: string;
    instructions?: string;
    copyright_notice?: string;
    marked?: boolean;
    usage_terms?: string;
    copyright_url?: string;
    location?: string;
    city?: string;
    state?: string;
    country?: string;
    country_code?: string;
    event?: string;
    /** People: the whole list, or one text split on commas. */
    person_in_image?: string[] | string;
    user_comment?: string;
}
/** An IPTC field by its API name — find/replace scope, remap source and target. */
type IptcFieldName = 'creator' | 'creator_title' | 'credit' | 'source' | 'copyright_notice' | 'copyright' | 'usage_terms' | 'copyright_url' | 'caption' | 'headline' | 'keywords' | 'person_in_image' | 'people' | 'event' | 'user_comment' | 'instructions' | 'city' | 'state' | 'country' | 'country_code' | 'location';
interface FindReplaceOptions {
    find: string;
    replace: string;
    /**
     * `all_fields` (every IPTC field, each keyword and person on its own), or
     * one field. `copyright` is the same as `copyright_notice`, `people` as
     * `person_in_image`.
     */
    scope?: 'all_fields' | IptcFieldName;
    /**
     * Which images. Default `selected` — the current image when nothing is
     * selected — like the other metadata tools. `all_visible` is the same as
     * `visible`.
     */
    apply_to?: 'current' | 'selected' | 'visible' | 'all_visible';
    use_regex?: boolean;
}
interface SelectionCriteria {
    min_rating?: number;
    max_rating?: number;
    flag?: string;
    color?: string;
    file_type?: string;
    gps?: string;
    orientation?: string;
    date_from?: string;
    date_to?: string;
    camera?: string;
    lens?: string;
}
type NavigateTarget = 'next' | 'previous' | 'first' | 'last' | 'next_marked' | 'previous_marked' | 'next_burst_group' | 'previous_burst_group' | 'denoise_counterpart' | number;
type ColorLabel = 'red' | 'yellow' | 'green' | 'blue' | 'purple' | 'orange' | 'pink' | 'gray' | null;
type DecodeSource = 'embedded-jpeg' | 'native-raw';
type CompareSplit = 'horizontal' | 'vertical';
/** Which images a metadata tool runs over; the server's default is `selected` (the current image when nothing is selected). */
type ApplyScope = 'current' | 'selected' | 'visible';
type Flag = 'pick' | 'reject' | 'none';
declare const Flag: {
    Pick: "pick";
    Reject: "reject";
    None: "none";
};
declare const Label: {
    Red: "red";
    Yellow: "yellow";
    Green: "green";
    Blue: "blue";
    Purple: "purple";
    Orange: "orange";
    Pink: "pink";
    Gray: "gray";
};
type ViewMode = 'develop' | 'grid' | 'compare' | 'survey' | 'raw_steps';
declare const ViewMode: {
    Develop: "develop";
    Grid: "grid";
    Compare: "compare";
    Survey: "survey";
    RawSteps: "raw_steps";
};
declare const SortOrder: {
    Filename: "filename";
    Rating: "rating";
    FileType: "file_type";
    CaptureDate: "capture_date";
    Sharpness: "sharpness";
};
declare const FileType: {
    All: "all";
    Raw: "raw";
    Jpeg: "jpeg";
    Video: "video";
};
type Channel = 'rgb' | 'red' | 'green' | 'blue';
declare const Channel: {
    RGB: "rgb";
    Red: "red";
    Green: "green";
    Blue: "blue";
};
/**
 * Every event the app sends over the WebSocket, with what it carries. Listed
 * from the server's own `broadcast_event` calls: an event missing here is one
 * a typed `on()` cannot listen for.
 */
interface EventMap {
    /** A folder was opened: the tree, the menus, a drop, an import finishing, the API. */
    folder_opened: {
        path: string;
    };
    /** A folder, album, saved search or library search was opened (#663). */
    source_opened: {
        source: Source;
        pictures: number;
    };
    /** The open album's own order changed. */
    source_order_changed: Record<string, never>;
    /** Every move to another picture: keys, clicks, the slideshow and the API. */
    navigated: {
        index: number;
    };
    rating_set: {
        index: number;
        rating: number;
    };
    /** The flag the image has now: setting one it already had takes it off. */
    flag_set: {
        index: number;
        flag: Flag;
    };
    /** `label` is `null` when the picture has none (`ColorLabel` includes it). */
    color_label_set: {
        index: number;
        label?: ColorLabel;
    };
    tag_toggled: {
        index: number;
        tagged?: boolean;
    };
    image_state_changed: {
        index: number;
        masked?: boolean;
    };
    /** Capture times shifted by `offset` ("+05:30") on `images` pictures — the dialog or the API. */
    timezone_adjusted: {
        offset: string;
        images: number;
    };
    /** Every committed develop write (panel, undo/redo, presets, paste, auto, API); `changed` holds the fields it changed, with their new values. */
    develop_changed: {
        index: number;
        changed?: Record<string, unknown>;
    };
    /** The picture went back to as shot: Reset All, an undo to the start, the API. Follows its `develop_changed`. */
    develop_cleared: {
        index: number;
    };
    /** Lightroom's settings in a picture's file changed after it was developed here. */
    lightroom_changed: {
        index: number;
        path: string;
    };
    /** photosuite's develop was kept over the changed Lightroom settings. */
    lightroom_kept: {
        index: number;
    };
    /** Sent for every crop write — the crop tool, auto-level, auto-crop and the API. `angle` is the crop's rotation in degrees. */
    crop_set: {
        index: number;
        angle?: number;
    };
    crop_cleared: {
        index: number;
    };
    /** Auto-crop on picture `index`: its own crop (`copies: 0`), or one virtual copy per person plus one of them all. */
    auto_crop_applied: {
        index: number;
        copies: number;
    };
    /** One per picture turned, clockwise: the menu, the key, the context menu and the API. */
    rotated: {
        index: number;
        degrees: 90 | 180 | 270;
    };
    view_mode_changed: {
        mode: string;
    };
    decode_source_changed: {
        source: DecodeSource;
    };
    sort_changed: {
        order: string;
        direction: string;
    };
    filter_changed: {
        visible_count: number;
    };
    /** The wheel, a click on the picture, a key or the API; at most once a frame. */
    zoom_changed: {
        level: number;
        fit_mode: 'fit_screen' | 'one_to_one' | 'custom';
    };
    /**
     * Carries the one setting that changed, e.g. `{ sidebar_visible: false }`,
     * `{ dialog: 'export', open: true }` or `{ panel_focused: 'develop' }`.
     */
    ui_state_changed: Record<string, unknown>;
    selection_changed: {
        count: number;
        indices: number[];
    };
    /** `count` files to the trash, and `copies` virtual copies removed while their original stays. */
    images_trashed: {
        count: number;
        copies: number;
    };
    images_renamed: {
        count: number;
    };
    /** `count` rejected pictures moved into the quarantine folder. */
    images_quarantined: {
        count: number;
    };
    /** `files` moved back from quarantine. */
    quarantine_undone: {
        files: number;
    };
    /** `count` frames newly picked; `eyes_closed`: best frames where someone's eyes are closed (told, never chosen by, #590). */
    best_in_bursts_picked: {
        count: number;
        eyes_closed: number;
    };
    /** The content model read another batch of the library's pictures; `progress` is `[read, of]` (#809). */
    library_content_read: {
        progress: number[] | null;
    };
    /** The content model's download ended; `available` says whether it is here (#809). */
    library_content_model: {
        available: boolean;
    };
    /** A burst put to `to_do`, `later` or `done` by hand; `state` is where it stands now (#808). */
    burst_state_changed: {
        group: number;
        state: string;
    };
    /** A burst settled: `kept` the frame kept, `rejected` how many others (#808). */
    burst_settled: {
        group: number;
        kept: number;
        rejected: number;
    };
    delivery_picked: {
        count: number;
        uncovered: number;
    };
    numbers_set: {
        path: string;
        numbers: string[];
    };
    folder_run_started: {
        workflow: string;
        folder: string;
    };
    /** `count` rejected; `eyes_closed`: kept frames where someone's eyes are closed. */
    duplicates_rejected: {
        count: number;
        eyes_closed: number;
    };
    dng_conversion_started: {
        count: number;
    };
    gallery_export_started: {
        count: number;
    };
    contact_sheet_started: {
        count: number;
    };
    /** `file` is the rendered copy the editor opened, when it got one (#572). */
    opened_in_editor: {
        index: number;
        file?: string;
    };
    /** The video on screen was played, paused, sought or muted (#574). */
    video_changed: VideoState;
    /** A Merge to HDR or Panorama finished; its DNG is in the folder (#579). */
    merged: Merged;
    /** The Import dialog, auto-import on a card, or the API. `source` is the card's or folder's name. */
    /** `job` is what `files.importJobs()` and `import_finished` call it. */
    import_started: {
        job: number;
        source: string;
        files: number;
        dest: string;
    };
    /** An import ended: completed, cancelled or failed, with what it did. */
    import_finished: ImportJobInfo;
    /** The export dialog or `export` over the API (`type: 'export'`), or a quick JPEG. */
    export_started: {
        type: 'export' | 'quick_jpeg' | 'edit_copy';
        files: number;
        dest: string;
    };
    smart_collection_saved: {
        name: string;
    };
    /** Pictures put in an album or taken out (#666); one of the counts. */
    album_changed: {
        name: string;
        added?: number;
        removed?: number;
    };
    /** A keyword renamed, merged, moved or deleted across the library (#667). */
    keyword_changed: {
        what: string;
    };
    /** Places found from the library's positions by the offline geocoder (#668). */
    library_places_found: {
        count: number;
    };
    /** The library's shoots found again (#669). */
    library_shoots_found: {
        count: number;
    };
    virtual_copy_created: {
        parent_index: number;
        index: number;
    };
    virtual_copy_deleted: {
        index: number;
    };
    vc_stack_toggled: {
        index: number;
        collapsed: boolean;
    };
    vc_stacks_toggled_all: {
        collapsed: boolean;
        stacks: number;
    };
    group_unmarked_rejected: {
        group: number;
        changed: number;
    };
    burst_detection_changed: {
        enabled: boolean;
    };
    /** One group (`id`), or every group (`all: true`). */
    burst_groups_changed: {
        id?: number;
        all?: boolean;
        collapsed: boolean;
    };
    upload_queued: {
        count: number;
    };
    upload_flush: {
        count: number;
    };
    /** One of `added`, `updated` or `removed`, holding the destination's index. */
    upload_destinations_changed: {
        added?: number;
        updated?: number;
        removed?: number;
    };
    /** `files` queued for denoise: the menu, a quick action, auto-denoise or the API. */
    denoise_sent: {
        files: number;
        queue_count: number;
        profile: string;
    };
    denoise_flushed: {
        count: number;
        profile: string;
    };
    denoise_cancelled: Record<string, never>;
    denoise_queue_updated: {
        removed_index: number;
    };
    denoise_profile_changed: {
        name: string;
    };
    denoise_settings_changed: {
        auto_denoise_on_pick: boolean;
        auto_denoise_min_rating: number;
    };
    /** A look was learned for a body and is in use now. */
    camera_look_learned: CameraLookInfo;
    /** A body's look was not learned; `reason` says why. `null` when it was not listed. */
    camera_look_refused: CameraLookInfo | null;
    iptc_copied: {
        index: number;
    };
    iptc_pasted: {
        count: number;
    };
    iptc_updated: {
        index: number;
    };
    find_replace_done: {
        replacements: number;
        images_modified: number;
    };
    iptc_remapped: {
        fields: number;
        images: number;
    };
    encoding_fixed: {
        issues: number;
        images: number;
    };
    metadata_stripped: {
        processed: number;
        failed: number;
    };
    gps_tracklog_applied: {
        applied: number;
    };
    reverse_geocode_started: {
        images: number;
    };
    /** Every setting that changed, with its new value — Settings, a key, a menu item or PATCH /api/settings. */
    settings_changed: Record<string, unknown>;
    connection_upserted: {
        name: string;
        created: boolean;
    };
    connection_deleted: {
        name: string;
    };
    tether_config_changed: Record<string, never>;
    workflow_profile_created: {
        index: number;
        name: string;
    };
    workflow_profile_deleted: {
        index: number;
        name: string;
    };
    /** A workflow was changed in place over the API; its index stays. */
    workflow_profile_changed: {
        index: number;
        name: string;
    };
    workflow_started: {
        index: number;
        name: string;
    };
    /** Once, however it stopped: the panel, Settings, the API or by itself. */
    workflow_stopped: {
        name: string;
    };
    workflow_file_ingested: {
        file: string;
    };
    workflow_step_started: {
        file: string;
        step: string;
    };
    /** What the step did (`outcome`), or why it failed (`error`), #905. */
    workflow_step_completed: WorkflowStep & {
        file: string;
    };
    /** The picture's whole record, as `GET /api/workflow/files` has it. */
    workflow_file_completed: WorkflowFile;
    /** Paused or resumed (#907), and how many pictures wait. */
    workflow_paused: {
        name: string;
        waiting: number;
    };
    workflow_resumed: {
        name: string;
        waiting: number;
    };
    /** A picture was asked to run again; its record comes with `rerun_of`. */
    workflow_file_rerun: {
        id: number;
        file: string;
        overrides: WorkflowOverrides;
    };
    workflow_file_held: {
        id: number;
        file: string;
        reason: string;
        by: 'photographer' | 'api' | 'claude';
    };
    workflow_file_released: {
        id: number;
        file: string;
    };
    /** A note written into the latest run (#912). */
    workflow_note: WorkflowNote;
    /** The workflow watcher found something wrong (#844): once per finding. */
    watch_finding: WatchFinding;
    workflow_file_uploaded: {
        file: string;
        connection: string;
    };
    workflow_error: {
        file: string;
        message: string;
    };
    library_sources_changed: Record<string, never>;
    /** An album or folder was put on the photographer's devices or taken off (#864). */
    on_devices_changed: Record<string, never>;
    /** An album was bound, unbound or synced, or something at its destination taken down (#753); `job` is its sync, running or last. */
    published_changed: {
        album: string;
        job: JobStatus | null;
    };
    /** The library was backed up (#673). */
    library_backed_up: LibraryBackup;
    /** The library was put back from a backup; the connected disks are being scanned. */
    library_restored: {
        path: string;
    };
    library_scan_started: {
        sourceIds: number[] | null;
    };
    library_scan_progress: ScanProgress;
    library_scan_finished: ScanProgress;
    library_results_changed: LibraryStats;
    /** An audio note's transcription came back, or failed. */
    transcribed: {
        path: string;
        text: string | null;
        error: string | null;
    };
    /** A licence request came back: activated, refreshed, refused, deactivated or failed. */
    license_changed: LicenseInfo;
}
type EventName = keyof EventMap;
type EventHandler<E extends EventName> = (data: EventMap[E]) => void;
type WildcardHandler = (event: EventName, data: unknown) => void;
/** The name the panel API takes. */
type PanelName = 'folders' | 'filmstrip' | 'sidebar' | 'histogram' | 'viewer' | 'develop' | 'scopes' | 'map';
/** One dockable panel: in the layout, and in front of its slot or behind it. */
interface PanelInfo {
    name: PanelName;
    /** The label on its tab. */
    title: string;
    visible: boolean;
    /** True when this is the tab actually on screen in the slot it shares. */
    active: boolean;
}
/** The crop on an image, as fractions of the full frame. */
interface CropInfo {
    /** False when the image is whole; the edges are then 0, 0, 1, 1. */
    cropped: boolean;
    top: number;
    left: number;
    bottom: number;
    right: number;
    /** Straightening angle in degrees, clockwise-positive. */
    angle: number;
}
/** How auto-crop's learning stands (#706, #707). */
interface LearningStatus {
    /** The base crop model suggestions come from, by its hash; null when this installation has none and auto-crop uses the rule alone. */
    model?: string | null;
    /** The crops kept as examples. */
    examples: number;
    /** A new base model reading the examples, while the one in use answers. */
    relearning?: Relearning | null;
    /** Paused: a new base model reads no more examples until resumed, also after a restart. */
    paused: boolean;
    /** How the suggestions matched the latest crops, before each was learnt from. */
    score?: LearningScore | null;
    /** How the last new base model went, until dismissed. */
    outcome?: string | null;
}
/** A new base crop model reading the examples. */
interface Relearning {
    model: string;
    done: number;
    total: number;
}
/** Mean intersection over union with the user's own crops. */
interface LearningScore {
    /** The suggestion as given: the base model and the user's layer. */
    own: number;
    /** The base model alone. */
    base: number;
    /** Over this many crops, the latest. */
    crops: number;
}
/** The name the dialog API takes; the server's DialogName lists the same. */
type DialogName = 'settings' | 'export' | 'import' | 'batch-rename' | 'eventphotos' | 'publish' | 'gallery' | 'iptc' | 'smart_collection' | 'place_by_neighbours' | 'feedback' | 'lightroom_catalog' | 'whats_new' | 'downloads' | 'workflow_templates' | 'person_tag';
/** A section of Settings, in the order the dialog lists them. */
type SettingsSection = 'general' | 'performance' | 'appearance' | 'color' | 'transform' | 'launching' | 'connections' | 'tether' | 'denoise' | 'workflow' | 'geocode' | 'shortcuts' | 'ai_services' | 'license';
/** One dialog window: its name, its title, and whether it is open. */
interface DialogInfo {
    name: DialogName;
    /** The window's own title. */
    title: string;
    open: boolean;
}
/** The Library window's three tabs. */
type LibraryTab = 'duplicates' | 'folder-copies' | 'disks';
/** A column of the filter (#665). */
type FilterColumn = 'date' | 'camera' | 'lens' | 'focal' | 'iso' | 'keyword' | 'person' | 'place' | 'kind' | 'label' | 'rating' | 'flag' | 'edited' | 'published' | 'album' | 'disk';
/**
 * Values picked, by column. `""` is the pictures with none; a date of
 * `"2024"` or `"2024-05"` holds every day in it.
 */
type FilterColumnPicks = Partial<Record<FilterColumn, string[]>>;
/** GET/POST /api/video: the video on screen (#574). */
interface VideoState {
    path: string;
    playing: boolean;
    /** Seconds from the start. */
    position: number;
    /** Seconds; null while the file says nothing. */
    duration: number | null;
    muted: boolean;
}
/** POST /api/video: what to change; fields left out stay. */
interface VideoBody {
    /** Play (true) or pause (false). */
    playing?: boolean;
    /** Seek to this many seconds from the start. */
    position?: number;
    /** Sound off (true) or on (false). */
    muted?: boolean;
}
/** The faces in a picture (#131): those large enough to judge. */
interface Eyes {
    faces: number;
    /** Faces with both eyes closed, or the lids lowered over them. */
    closed: number;
    smiling: number;
}
/** POST /api/captions/suggest: which pictures (#577). */
interface CaptionSuggestBody {
    /** Pictures in the open folder; left out, the selected ones, else the one on screen. */
    paths?: string[] | null;
}
/** A caption and keywords a model suggests; keywords the picture has already are left out. */
interface Suggestion {
    caption: string;
    keywords: string[];
}
/** The suggestion for one picture, or why there is none. */
interface Suggested {
    path: string;
    suggestion?: Suggestion | null;
    error?: string | null;
}
/** A picture with a position (#578). */
interface MapPosition {
    index: number;
    path: string;
    latitude: number;
    longitude: number;
}
/** POST /api/map/place-by-neighbours (#668). */
interface PlaceByNeighboursBody {
    minutes?: number;
    apply?: boolean;
}
/** A picture placed by a neighbour in time. */
interface NeighbourPlace {
    index: number;
    path: string;
    latitude: number;
    longitude: number;
    /** The picture with the position, by the same camera. */
    from: string;
    /** Seconds between the two. */
    seconds: number;
}
interface PlacedByNeighbours {
    places: NeighbourPlace[];
    /** Files given a position. */
    placed: number;
}
/** GET /api/map/positions */
interface MapPositions {
    positions: MapPosition[];
}
/** POST /api/map/position: as dropping pictures on the map. */
interface MapPositionBody {
    /** Rows; left out, the selected pictures, else the one on screen. */
    indices?: number[] | null;
    latitude: number;
    longitude: number;
}
/** POST /api/merge: which pictures, into what (#579). */
interface MergePicturesBody {
    kind: 'hdr' | 'panorama';
    /** A panorama's projection; cylindrical when left out. */
    projection?: 'cylindrical' | 'spherical' | 'perspective' | null;
    /** RAWs in the open folder; left out, the selection, else the bracket or burst of the picture on screen. */
    paths?: string[] | null;
}
/** What a merge made: a linear DNG beside the first frame. */
interface Merged {
    path: string;
    width: number;
    height: number;
}
/** GET /api/images/current/bracket */
interface BracketInfo {
    indices: number[];
}
/** One signal of the learned taste (#582). */
interface TasteSignal {
    signal: string;
    /** Positive: pictures with more of it are kept; negative: passed over. */
    weight: number;
}
/** GET /api/taste */
interface TasteInfo {
    kept: number;
    passed_over: number;
    learn_from: number;
    /** Empty until it is used. */
    signals: TasteSignal[];
}
/**
 * A saved list of named presets — export profiles, import presets or IPTC
 * templates — as the config folder keeps it (#593).
 */
interface SavedPresets {
    /** Each preset as stored, with its `name`. */
    items: Record<string, unknown>[];
    /** The one the dialog opens on (and an export or import uses when none is named). */
    last_used?: string | null;
}
/** One key action and the combination bound to it. */
interface BindingInfo {
    /** The action's name, as `bindKey()` takes it. */
    action: string;
    /** What Settings → Shortcuts calls it. */
    label: string;
    category: string;
    /** As Settings writes it ("Cmd+Shift+N"); null when unbound. */
    combo?: string | null;
}
/** The pictures in a folder, by file name. */
interface PictureNames {
    folder: string;
    files: string[];
}
/** The licence on this machine, as Settings → License shows it (#594). */
interface LicenseInfo {
    status: 'licensed' | 'trial' | 'version_not_covered' | 'not_activated' | 'ended' | 'trial_expired';
    /** Whether the app can be used in this state. */
    usable: boolean;
    tier?: string | null;
    license_key?: string | null;
    /** Unix seconds: the last release the licence's updates cover. */
    updates_until?: number | null;
    lifetime_updates: boolean;
    /** Unix seconds: when a time-limited licence ends. */
    valid_until?: number | null;
    /** Unix seconds: when the trial ends. */
    trial_expires_at?: number | null;
    /** Why the server refused the licence, when it did. */
    refused?: string | null;
    /** A request to the licence server is under way; its outcome comes as `license_changed`. */
    busy: boolean;
    /** Sign in with FLOGVIT is waiting for the browser: the page it opened (#788). */
    sign_in_page?: string | null;
    message?: string | null;
    message_is_error: boolean;
}
/** GET /api/undo: what Edit → Undo and Redo would do now (#709). */
interface UndoInfo {
    /** The step Undo takes back ("Auto Crop (12 pictures)"); null with nothing to undo. */
    undo: string | null;
    /** The step Redo does again; null with nothing to redo. */
    redo: string | null;
}
/** POST /api/app/scripts-folder */
interface ScriptsFolderInfo {
    /** The folder, with the SDK that belongs to this app and a README in it. */
    path: string;
    /** Ready-made scripts and workflows, on the website. */
    collection: string;
}
/** GET /api/app/log */
interface LogInfo {
    /** The log file a bug report carries. */
    path: string;
    /** Its last lines, oldest first. */
    lines: string[];
}
/** Burst detection state and group list. */
interface BurstGroupsInfo {
    enabled: boolean;
    groups: BurstGroupApiInfo[];
    /** Bursts still to do (#808), of `total` (stacks are not counted). */
    left: number;
    total: number;
}
/** One detected burst group. */
interface BurstGroupApiInfo {
    collapsed: boolean;
    id: number;
    /** Flat image indices in chronological order. */
    indices: number[];
    time_span_ms: number;
    /** A stack the photographer made, not a burst. */
    stack: boolean;
    /** `to_do`, `later` or `done` (#808). */
    state: string;
    /** The burst's pick (#807): the frame kept, else what you keep, then the sharpest. */
    pick?: number | null;
    /** The photographer kept `pick` themselves. */
    yours: boolean;
    /** How far ahead of the runner-up, in words. */
    pick_said?: string | null;
    /** Within the close-call margin of the runner-up. */
    close_call: boolean;
}
/** POST /api/burst/settle (#808) */
interface BurstSettleBody {
    /** The frame to keep, by index; left out, `keep` says which. */
    index?: number | null;
    /** `on_screen` (the default) or `pick`. */
    keep?: string | null;
}
/** What the window works on (#663). */
type Source = {
    kind: 'folder';
    path: string;
}
/** An album (manual collection), in its own order. */
 | {
    kind: 'collection';
    name: string;
}
/** A saved search, as it finds now. */
 | {
    kind: 'smart_collection';
    name: string;
}
/** A library search, in the library's order when one is given. */
 | {
    kind: 'query';
    filter: CatalogFilter;
    sort?: 'capture_time' | 'name' | 'rating' | 'added' | 'edited';
    descending?: boolean;
};
/** What is open. */
interface OpenSourceInfo {
    source?: Source | null;
    /** What it is called where it is shown. */
    label?: string | null;
    pictures: number;
    /** The folders its pictures are in. */
    folders: string[];
}
/**
 * What a catalog search asks for — the Library search box, a smart
 * collection and `POST /api/catalog/search` share it (#660). Every set field
 * must hold, or any one with `match_any`; text fields are case-insensitive.
 */
interface CatalogFilter {
    camera?: string | null;
    captured_after_ms?: number | null;
    captured_before_ms?: number | null;
    /** The flag: pick, reject, none */
    flag?: Flag;
    lens?: string | null;
    min_rating?: number | null;
    min_sharpness?: number | null;
    /**
     * Free text, as in the Library search box: every word found at the start
     * of a word in the path, caption, title, keywords, people, place, camera
     * or lens.
     */
    text?: string | null;
    /** Part of one of its keywords. */
    keyword?: string | null;
    /** Part of the name of someone in it. */
    person?: string | null;
    /** Part of its place: sublocation, city, state or country. */
    place?: string | null;
    /** Part of its caption. */
    caption?: string | null;
    color_label?: ColorLabel;
    /** Any of these kinds: `raw`, `video`, `jpeg`, `heif`, `tiff`, `png`, … */
    kinds?: string[];
    has_edits?: boolean | null;
    /** With keywords, or without (Lightroom's Without Keywords, #671). */
    has_keywords?: boolean | null;
    /** With a caption, or without. */
    has_caption?: boolean | null;
    /** Taken in the last this many days, counted when it searches (Lightroom's Past Month). */
    captured_within_days?: number | null;
    /** The file or its sidecar changed in the last this many days (Lightroom's Recently Modified). */
    changed_within_days?: number | null;
    /** Published somewhere whose name holds this (`eventphotos:<event>`, `newspaper`); `""` for anywhere. */
    published?: string | null;
    /** In the manual collection of this name. */
    collection?: string | null;
    /** Somewhere below this folder. */
    folder?: string | null;
    /** On this library source. */
    source_id?: number | null;
    /** On a disk that is connected now, or not. */
    connected?: boolean | null;
    /** Inside this box: south, west, north, east, in degrees. */
    gps_box?: [number, number, number, number] | null;
    /** Any of these keywords, whole — values picked from a list (#664). */
    keywords_any?: string[];
    /** The keyword at this place in the hierarchy (`Sport|Running`), or one under it (#667). */
    keyword_under?: string | null;
    /** This place (`Norway|Rogaland|Bryne`) or one under it: the file's, else where its position is (#668). */
    place_under?: string | null;
    /** The shoot holding this moment, epoch ms (#669). */
    shoot_at?: number | null;
    /** The pictures of a to-do list (#670). */
    todo?: 'not_culled' | 'picks_not_developed' | 'edited_not_delivered' | 'not_backed_up' | null;
    /** Any of these people, whole. */
    people_any?: string[];
    /** Any of these places, "City, Country". */
    places_any?: string[];
    /** Any of these cameras, make and model. */
    cameras_any?: string[];
    lenses_any?: string[];
    /** Pictures with no capture time. */
    undated?: boolean;
    /** Values picked in the filter's columns (#665): any of a column's, all of the columns. */
    columns?: FilterColumnPicks;
    /** Groups of their own, each one condition with all or any of its own fields (#666). */
    groups?: CatalogFilter[];
    /** Any one of the fields instead of all of them. */
    match_any?: boolean;
    /**
     * Words for what is in the picture, in any of the content model's
     * languages (#809): the pictures nearest them, nearest first. With
     * `text`, what holds its words or lies near, the words first.
     */
    content?: string | null;
    /** The pictures that look like this one (a path), nearest first (#809). */
    similar_to?: string | null;
}
/**
 * A saved cross-folder query (#134 smart collection): a named filter,
 * persisted so it can be re-run later. The Library dialog and the API save
 * and run it the same way, through [`CatalogFilter::to_query`].
 */
interface SmartCollection extends CatalogFilter {
    name: string;
    /** The Lightroom smart collection it was made from (`<catalog>/<id>`), so importing again updates it (#671). */
    lightroom?: string | null;
}
/** Catalog search results: a page of them, and how many match in all. */
interface CatalogResults {
    results: CatalogResultInfo[];
    total: number;
}
/** One row of a catalog search result (#134). */
interface CatalogResultInfo {
    camera?: string | null;
    capture_time_ms?: number | null;
    file_name: string;
    /** The flag: pick, reject, none */
    flag: Flag;
    folder: string;
    lens?: string | null;
    rating: number;
    sharpness?: number | null;
    /** `raw`, `video`, or the image format. */
    kind?: string | null;
    /** A movie's length. */
    duration_ms?: number | null;
    caption?: string | null;
    title?: string | null;
    creator?: string | null;
    keywords: string[];
    people: string[];
    city?: string | null;
    state?: string | null;
    country?: string | null;
    /** `Iptc4xmpCore:Location`. */
    sublocation?: string | null;
    gps_lat?: number | null;
    gps_lon?: number | null;
    /** On a disk that is connected now (#660). */
    connected: boolean;
}
/**
 * POST /api/catalog/search — the library searched (#134, #660): every
 * picture it knows, also on disks that are not connected, a page at a time.
 */
interface CatalogSearchBody extends CatalogFilter {
    /** The sort: capture_time, name, rating, added, edited (default capture_time). */
    sort?: 'capture_time' | 'name' | 'rating' | 'added' | 'edited' | null;
    /** Newest, highest or last first. */
    descending?: boolean;
    /** Rows to skip: the page's start. */
    offset?: number | null;
    /** Cap on returned rows (default 500). */
    limit?: number | null;
}
/**
 * A hand-picked cross-folder set of images (#134 manual collection). Unlike a
 * [`SmartCollection`] (a saved query), the membership is explicit.
 */
interface ManualCollection {
    items?: CollectionItem[];
    name: string;
    publish?: null | PublishTarget;
    /** The Lightroom collection it was made from (`<catalog>/<id>`), so importing again updates it (#671). */
    lightroom?: string | null;
}
/** A reference to one image in some folder (#134 manual collection member). */
interface CollectionItem {
    file_name: string;
    folder: string;
    published?: null | PublishedItem;
}
/** One image's state on its album's destination (#265, #753). */
interface PublishedItem {
    /**
     * Our id for the picture, sent as the destination's `clientRef` where it
     * keeps one. Made BEFORE the first send and saved, so a send that dies
     * between "made" and "remembered" finds it again instead of a second.
     */
    client_ref: string;
    /** Why the last attempt failed, until one succeeds. */
    error?: string | null;
    /** Fingerprint of the fields sent beside it (eventphotos' tags, the gallery's texts and prices). */
    fields_fingerprint?: string;
    /** Fingerprint of what was sent; empty until a send completes. */
    fingerprint?: string;
    /** Hidden on the destination because it was taken out of the album. */
    hidden?: boolean;
    /** Unix seconds of the last successful send of this image. */
    published_at?: number;
    /** The destination's id for it, once it is there (a photo id, an artwork id, an image key, a file name). */
    remote_id?: string | null;
    /** Somebody bought it: it is never overwritten or deleted (Vegard 2026-09-22). */
    sold?: boolean;
    /**
     * The edit the destination's editor held right after photosuite last sent
     * it ("" for none), so an edit made there since is seen (#931).
     */
    edit_there?: string | null;
    /** What of an edit made there was taken into the picture, the last time one was (#931). */
    taken_edit?: TakenEdit | null;
}
/** What of an edit made in a destination's own editor was taken into the picture (#931). */
interface TakenEdit {
    /** The crop made there replaced the picture's (or made one). */
    crop: boolean;
    /** The picture had a crop of its own, made here since it was sent, that the one made there replaced. */
    replaced_crop: boolean;
    /** Exposure, highlights or shadows set there, added to the develop. */
    tone: boolean;
}
/** What kind of place an album is published to (#753). */
type DestinationKind = 'eventphotos' | 'files' | 'smugmug' | 'gallery' | 'flickr';
/**
 * What each picture is called where it is published (a SmugMug image's
 * title, caption and keywords), as templates over its own metadata:
 * `{headline}`, `{caption}`, `{keywords}`, `{city}` … (#755).
 */
interface PublishTexts {
    caption?: string;
    keywords?: string;
    title?: string;
}
/** An album bound to a destination (#265, #753). */
interface PublishTarget {
    /** Kept in sync by itself: what is put in goes out, what is taken out comes down, what is edited is sent again. */
    auto_sync?: boolean;
    /** The connection profile that reaches it, by name. */
    connection: string;
    /** The eventphotos event, for an eventphotos binding. */
    event_id?: number;
    /** For an eventphotos binding: a runner read less than this share of the sharpest person in a picture of several is not tagged with their number there (#810). */
    soft_runner_share?: number | null;
    event_slug?: string;
    event_title?: string;
    /** For a folder: the export profile each picture is sent as; empty sends the original. */
    export_profile?: string;
    kind?: DestinationKind;
    /** SHA-256 of the logo's bytes: the URL stays the same when the logo is replaced. */
    logo_sha256?: string | null;
    logo_url?: string | null;
    /** What of the destination decides how a picture is drawn, as read at the last sync. */
    look?: Record<string, unknown>;
    /** Where on the destination, in its own terms: a folder under the connection's. */
    place?: string;
    /** The place as the destination calls it, for the binding's name. */
    place_title?: string;
    /** Images taken out after they were sent, until they are taken down — and after, where the destination hides. */
    removed?: CollectionItem[];
    /** What the event asked renditions to carry, as of the last sync. */
    sale_long_edge?: number;
    /** For SmugMug and Flickr: each picture's title, caption and keywords; left out, the picture's headline, caption and keywords. */
    texts?: PublishTexts;
    watermark?: string;
}
/** One connection profile with its secrets left out. */
interface ConnectionInfo {
    /** The connection's fields, every secret emptied. */
    connection: Record<string, unknown>;
    name: string;
    /** The write-only secrets currently stored for it. */
    secrets_set: string[];
    /** The protocol, as Settings labels it ("FTP", "Google Drive", …). */
    type: string;
}
/** A step's picture reaching the screen (#745). */
interface StepShown {
    /** The step's number since the app started. */
    n: number;
    file: string;
    /** From the step to the picture on screen, in milliseconds. */
    ms: number;
    /** `in memory`, `cached preview` or `loaded` (the camera's JPEG), `raw rendered ahead` or `raw`. */
    how: string;
}
/** How long the steps took to put their pictures on screen, and the RAW spool pace (#745). */
interface ViewSteps {
    steps: StepShown[];
    raw_sources_per_second?: number | null;
    raw_show_ms?: number | null;
    raw_pace_per_second?: number | null;
}
interface AlbumStatus {
    failed: number;
    items: ItemStatus[];
    job?: null | JobStatus;
    missing: number;
    modified: number;
    name: string;
    new: number;
    /** What is on the destination and in no picture of the album, as read at the last sync. */
    outside: RemoteItem[];
    published: number;
    sold: number;
    target?: null | PublishTarget;
    /** Taken out of the album and not yet taken down. */
    toTakeDown: number;
}
interface ItemStatus {
    error?: string | null;
    fileName: string;
    folder: string;
    /** The destination's id for it, once it is there. */
    remoteId?: string | null;
    state: ItemState;
    /** What of an edit made in the destination's own editor was taken into the picture, the last time one was (#931). */
    takenEdit?: TakenEdit | null;
}
/** Where an image in a bound album stands. */
type ItemState = 'new' | 'published' | 'modified' | 'sold' | 'missing' | 'failed';
interface JobStatus {
    /** The file being worked on, for the progress line. */
    current?: string | null;
    done: number;
    /** Why the whole sync stopped, when it did (no token, event gone, …). */
    error?: string | null;
    failed: number;
    /** Pictures whose fields alone were sent again. */
    fields: number;
    /** Unix seconds the last sync of this album finished. */
    finishedAt?: number;
    replaced: number;
    running: boolean;
    skippedSold: number;
    takenDown: number;
    total: number;
    uploaded: number;
}
/** One thing on a destination, as it reports it. */
interface RemoteItem {
    capturedAt?: string | null;
    /** The id photosuite gave it, when photosuite put it there. */
    clientRef?: string | null;
    fileName?: string | null;
    height?: number | null;
    hidden: boolean;
    /** The destination's own id: a number, an image key, a file name. */
    id: string;
    /** Bought: never overwritten, never deleted. */
    sold: boolean;
    thumbUrl?: string | null;
    width?: number | null;
}
interface BindBody {
    /** Kept in sync by itself (the default), or only when asked. */
    autoSync?: boolean | null;
    /** The connection profile that reaches the destination, by name. */
    connection: string;
    /** The eventphotos event, for `eventphotos`. */
    eventId?: number | null;
    /** For `files`: the export profile each picture is sent as; left out, the original. */
    exportProfile?: string;
    kind: DestinationKind;
    /** The folder under the connection's, for `files`; the gallery (link, node id or folder path) for `smugmug`; the set (id or title) for `flickr`. */
    place?: string;
    /** For `smugmug` and `flickr`: each picture's title, caption and keywords; left out, the picture's headline, caption and keywords. */
    texts?: PublishTexts | null;
    /** For `eventphotos`: a runner read less than this share of the sharpest person in a picture of several is not tagged with their number there (#810). */
    softRunnerShare?: number | null;
}
interface ItemsChanged {
    added: number;
    removed: number;
}
interface Linked {
    linked: boolean;
}
/** The gallery a token belongs to (#750). */
interface GalleryInfo {
    defaultLocale?: string;
    defaultPresetId?: number | null;
    handle: string;
    /** The languages its texts are written in. */
    languages?: string[];
    name: string;
    url?: string | null;
}
/** A print size and its price, in øre. */
interface GalleryVariant {
    /** The gallery's id, when the variant is already on the artwork. */
    id?: number | null;
    label?: string;
    longEdgeCm?: number | null;
    material?: string;
    priceOre: number;
}
interface GalleryPreset {
    editionSize?: number | null;
    id: number;
    name: string;
    variants?: GalleryVariant[];
}
/** A title and a description in one language; `machine` when a machine wrote it. */
interface GalleryText {
    description?: string;
    machine?: boolean;
    title?: string;
}
interface GalleryCollection {
    count?: number;
    id: number;
    /** The menu group it hangs under in the gallery's menu. */
    menuGroup?: string | null;
    slug?: string;
    texts?: Record<string, GalleryText>;
}
/** An artwork as the gallery shows it. */
interface GalleryArtwork {
    awards?: string[];
    capturedAt?: string | null;
    clientRef?: string | null;
    collections?: number[];
    editionSize?: number | null;
    featured?: boolean;
    height?: number;
    id: number;
    keywords?: string[];
    presetId?: number | null;
    slug?: string;
    soldBefore?: number;
    /** `draft`, `published` or `hidden`. */
    status: string;
    /** Copies gone: sold before, sold here, and held by a checkout. */
    taken?: number;
    texts?: Record<string, GalleryText>;
    thumbUrl?: string | null;
    url?: string | null;
    variants?: GalleryVariant[];
    width?: number;
}
interface GalleryView {
    /** The album that is the gallery, when one is. */
    album?: string | null;
    collections: GalleryCollection[];
    connection: string;
    gallery: GalleryInfo;
    presets: GalleryPreset[];
}
interface GalleryArtworkOf {
    /** The picture's artwork; null when the picture is not in a gallery. */
    artwork?: GalleryArtwork | null;
}
interface GalleryBulkDone {
    /** How many of the pictures were in a gallery and changed. */
    changed: number;
}
interface GalleryAdded {
    added: number;
    /** The album that is the gallery (made and bound when there was none). */
    album: string;
}
/** What became of an item taken down that photosuite did not put there. */
interface OutsideTakenDown {
    takenDown: 'deleted' | 'hidden';
}
interface SyncStarted {
    started: boolean;
}
interface Unbound {
    unbound: boolean;
}
/**
 * An event the token's photographer can publish into, with what a rendition
 * drawn outside the browser has to know.
 */
interface RemoteEvent {
    endsOn?: string | null;
    id: number;
    /** The event's logo, else the photographer's; drawn instead of the text. */
    logoUrl?: string | null;
    /** "owner" or "member". */
    role: string;
    /** Long edge of the sale file; 0 = the crop at its own resolution. */
    saleLongEdge: number;
    slug: string;
    startsOn: string;
    title: string;
    /** The text mark, drawn when there is no logo. */
    watermark: string;
}
/** POST /api/geocode/reverse */
interface ScopeBody {
    /** The scope: current, selected, visible. Default selected: the current image when nothing is selected. */
    scope?: ApplyScope;
}
/** POST /api/lightroom/catalog-import */
interface CatalogImportBody {
    /** The Lightroom Classic catalog (`*.lrcat`); read from a copy, so Lightroom may stay open. */
    catalog: string;
    /** Only the pictures under this folder; omitted, every picture in the catalog. */
    under?: string | null;
}
/** How a Lightroom catalog import goes. */
interface CatalogImportStatus {
    running: boolean;
    catalog: string;
    /** The folder it is limited to; empty for the whole catalog. */
    under: string;
    /** What it did, once it is done. */
    report?: CatalogReport | null;
    errors: string[];
}
/** What a Lightroom catalog import did. */
interface CatalogReport {
    /** Pictures under the folder asked for (a virtual copy goes with its original). */
    pictures: number;
    /** Sidecars written. */
    written: number;
    /** Virtual copies added to their originals' sidecars. */
    virtual_copies: number;
    /** Pictures already developed in photosuite: their develop stays, and they are marked when Lightroom's differ. */
    developed_here: number;
    /** Files the catalog names that are not on disk, by folder. */
    not_found: NotFound[];
    /** Files that could not be written, with why. */
    failed: Failed[];
    /** AI masks copied out of the catalog's own store, for pictures with no `.acr` holding them. */
    ai_masks_kept: number;
    /** AI masks the settings name that neither the `.acr` nor the catalog has. */
    ai_masks_missing: number;
    /** What the catalog's organisation became in the library (#671). */
    organised: Organised;
}
/** A Lightroom catalog's folders, collections, sets, smart collections, keywords and stacks in the library (#671). */
interface Organised {
    /** Folders added to the library, connected now; they are scanned. */
    sources: string[];
    /** Folders on disks not connected now, added with the catalog's pictures. */
    offline_sources: string[];
    offline_pictures: number;
    /** Lightroom's previews kept for those pictures. */
    previews: number;
    /** Folders that could not be added, and why. */
    not_added: Failed[];
    sets: number;
    albums: number;
    smart_collections: number;
    smart_as_albums: SmartAsAlbum[];
    keywords: number;
    stacks: number;
    /** What the publish services published to; not brought in. */
    published: Published[];
}
/** A smart collection whose rules did not all translate: an album of the pictures it holds now. */
interface SmartAsAlbum {
    name: string;
    /** The rules with no counterpart, as Lightroom has them. */
    rules: string[];
    pictures: number;
    /** Whether the pictures could be worked out; when not, the album is empty. */
    worked_out: boolean;
}
/** A collection a Lightroom publish service published to. */
interface Published {
    service: string;
    collection: string;
    url?: string | null;
    pictures: number;
}
/** A folder of pictures the catalog has that are not on disk. */
interface NotFound {
    folder: string;
    pictures: number;
}
/** A picture whose sidecar could not be written. */
interface Failed {
    file: string;
    error: string;
}
/** POST /api/jpeg-tool */
interface JpegToolBody {
    /** Target DPI for op = "dpi" (default 300). */
    dpi?: number | null;
    /** Image indices; empty/omitted = the current image. */
    indices?: number[];
    /** The jpeg op: mirror_h, mirror_v, trim, extract, dpi */
    op: 'mirror_h' | 'mirror_v' | 'trim' | 'extract' | 'dpi';
}
/** POST /api/metadata/fix-encoding */
interface FixEncodingBody {
    /** The direction: latin1_to_utf8, utf8_to_latin1 */
    direction?: 'latin1_to_utf8' | 'utf8_to_latin1';
    /**
     * When true, return the detected issues without writing anything —
     * the API equivalent of the dialog's reviewable list.
     */
    dry_run?: boolean;
    /** The scope: current, selected, visible. Default selected: the current image when nothing is selected. */
    scope?: ApplyScope;
}
/** What an encoding scan found, and — unless it was a dry run — fixed. */
interface EncodingFixResult {
    /** Values written; 0 on a dry run. */
    fixed: number;
    /** Images written; 0 on a dry run. */
    images: number;
    /** Every mojibake value found in scope, with what it becomes. */
    issues: EncodingIssueInfo[];
}
/** One detected mojibake fix (fix-encoding dry run). */
interface EncodingIssueInfo {
    /** IPTC field label (e.g. "Caption", "Creator") */
    field: string;
    filename: string;
    /** Value that would be written */
    fixed: string;
    /** Flat image index */
    index: number;
    /** Current (broken) value */
    original: string;
}
/** POST /api/metadata/gps-tracklog */
interface GpsTracklogBody {
    /** Also reverse-geocode the matched coordinates */
    auto_geocode?: boolean;
    /**
     * Overrides the photos' UTC offset, as "+02:00", "-05:30" or "+0200".
     * Omitted: each photo's own — the camera's OffsetTimeOriginal, else
     * the app machine's time zone on the capture date.
     */
    offset?: string | null;
    /** The scope: current, selected, visible. Default selected: the current image when nothing is selected. */
    scope?: ApplyScope;
    /** Path to a GPX file */
    tracklog_path: string;
}
/** POST /api/metadata/remap */
interface RemapBody {
    rules: RemapRuleBody[];
    /** The scope: current, selected, visible. Default selected: the current image when nothing is selected. */
    scope?: ApplyScope;
}
/**
 * One field-remap rule for POST /api/metadata/remap.
 *
 * Field names (snake_case): creator, creator_title, credit, source,
 * copyright_notice, usage_terms, copyright_url, caption, headline,
 * keywords, person_in_image, event, user_comment, instructions, city, state,
 * country, country_code, location. A list (keywords, person_in_image) is
 * joined with ", " when copied into a text field and split on commas when
 * a text is copied into it.
 */
interface RemapRuleBody {
    /** The remap action: copy, move, swap */
    action?: 'copy' | 'move' | 'swap';
    /** The conflict mode: overwrite, skip, append */
    conflict?: 'overwrite' | 'skip' | 'append';
    /** The IPTC field: creator, creator_title, credit, source, copyright_notice, usage_terms, copyright_url, caption, headline, keywords, person_in_image, event, user_comment, instructions, city, state, country, country_code, location */
    source: IptcFieldName;
    /** The IPTC field: creator, creator_title, credit, source, copyright_notice, usage_terms, copyright_url, caption, headline, keywords, person_in_image, event, user_comment, instructions, city, state, country, country_code, location */
    target: IptcFieldName;
}
/** POST /api/metadata/strip */
interface StripBody {
    exif?: boolean;
    gps?: boolean;
    iptc?: boolean;
    /** Write stripped copies here instead of modifying originals */
    output_dir?: string | null;
    /** The scope: current, selected, visible. Default selected: the current image when nothing is selected. */
    scope?: ApplyScope;
    thumbnails?: boolean;
    xmp?: boolean;
}
interface HandleInfo {
    /** Named files this handle knows about: `original`, `denoised`, `dng`, … */
    files: Record<string, string>;
    handle: string;
    path: string;
}
interface WorkflowProfileInfo {
    index: number;
    is_running: boolean;
    name: string;
    steps: string[];
}
/**
 * A workflow as a `.photosuite-workflow` file (#842): its steps, and the
 * connections, denoise profiles, IPTC templates and export profiles they
 * name. No password, token or key is in it, and the work folder is empty.
 */
interface SharedWorkflow {
    /** The file's format (1). */
    photosuite_workflow: number;
    workflow: Record<string, unknown>;
    connections?: Record<string, unknown>[];
    denoise_profiles?: Record<string, unknown>[];
    iptc_templates?: Record<string, unknown>[];
    export_profiles?: Record<string, unknown>[];
}
/** POST /api/workflow/profiles/import */
interface WorkflowImported {
    profile: WorkflowProfileInfo;
    /** What to fill in before it runs, one sentence each: a password, the work folder, a webhook's address. */
    needs: string[];
}
/**
 * How the running workflow is doing; also what `GET /api/workflows/status`
 * answers (no name and not running when none has run).
 */
interface WorkflowStatus {
    current_file?: string | null;
    current_step?: string | null;
    errors: string[];
    files_ingested: number;
    files_processed: number;
    /** Files an Export step delivered to its connection. */
    files_uploaded: number;
    profile_name?: string | null;
    running: boolean;
    /** Paused (#907): pictures keep arriving and wait; nothing is processed. */
    paused: boolean;
    /** Pictures arrived and waiting to be processed. */
    waiting: number;
}
/** What a run of one picture again sets for that picture alone (#907). */
interface WorkflowOverrides {
    /** `[left, top, right, bottom]` in 0..1 of the picture the crop step cuts. */
    crop?: [number, number, number, number] | null;
    /** The develop's exposure and the Exposure step's gain, in EV. */
    ev?: number | null;
    /** The race numbers, settled as a person would; Identify names these. */
    bibs?: string[] | null;
    caption?: string | null;
    /** A person approved it: an Approve step lets it through. */
    approved?: boolean | null;
}
/**
 * POST /api/workflow/files/{id}/rerun: what is set for the picture alone,
 * and who set it and why — kept as a correction the workflow learns from (#911).
 */
interface WorkflowRerunBody extends WorkflowOverrides {
    /** `api` when left out. */
    by?: 'photographer' | 'api' | 'claude' | null;
    /** Why it was wrong. */
    reason?: string | null;
}
/** A note written into a run (#912). */
interface WorkflowNote {
    /** Epoch milliseconds. */
    at_ms: number;
    text: string;
    /** The picture's record it is about. */
    picture?: number | null;
}
/** POST /api/workflow/notes */
interface WorkflowNoteBody {
    text: string;
    picture?: number | null;
}
/** A picture a run's report names, and why. */
interface WorkflowRunPicture {
    id: number;
    file: string;
    /** The reason it was held, the step it failed at, the record it ran again, its seconds. */
    why: string;
    by?: 'photographer' | 'api' | 'claude' | null;
}
/** What a workflow run did, for after the event (#912). */
interface WorkflowRun {
    id: number;
    workflow: string;
    /** Epoch milliseconds; `stopped_ms` absent while it runs. */
    started_ms: number;
    stopped_ms?: number | null;
    arrived: number;
    finished: number;
    /** Pictures delivered, per destination (a connection, or `delivered/`). */
    delivered: Record<string, number>;
    held: WorkflowRunPicture[];
    reruns: WorkflowRunPicture[];
    failed: WorkflowRunPicture[];
    /** Seconds from arrival to done: the median, and the slowest five. */
    median_seconds?: number | null;
    slowest: WorkflowRunPicture[];
    findings: WatchFinding[];
    notes: WorkflowNote[];
    /** Where the report was saved as HTML when the run stopped. */
    saved?: string | null;
}
/** POST /api/workflow/files/{id}/rerun */
interface WorkflowRerun {
    /** The record redone; the new run's record has `rerun_of` set to it. */
    id: number;
    /** Per destination: whether the new picture replaces the first, goes beside it, or is not sent. */
    destinations: string[];
}
/** POST /api/workflow/files/{id}/hold */
interface WorkflowHoldBody {
    reason: string;
    /** Who holds it; `api` when left out. */
    by?: 'photographer' | 'api' | 'claude' | null;
}
/** A picture kept back from delivery (#907). */
interface WorkflowHeld {
    reason: string;
    by: 'photographer' | 'api' | 'claude';
    at_ms: number;
    /** What holding it did, which releasing it undoes. */
    taken: WorkflowTaken[];
}
/** One thing holding a picture did. */
type WorkflowTaken = 
/** A file moved out of a delivery folder or the retry queue. */
{
    kind: 'moved';
    from: string;
    to: string;
}
/** Out of an album; hidden where published, never deleted. */
 | {
    kind: 'out_of_album';
    album: string;
    file: string;
}
/** Already at a destination photosuite cannot take it back from. */
 | {
    kind: 'already_sent';
    connection: string;
    file: string;
};
/**
 * What one step of a workflow did to a picture (#905); `kind` says which.
 * A step that did nothing says why (`skipped`).
 */
type StepOutcome = {
    kind: 'skipped';
    why: string;
}
/** The picture is now read from `file` (Use RAW, Use JPEG, Reset), at this size. */
 | {
    kind: 'loaded';
    file: string;
    width: number;
    height: number;
}
/** The picture is now `file` (Switch File); the next step that needs pixels reads them. */
 | {
    kind: 'switched';
    file: string;
}
/** The denoiser's output; `reused` when it was on disk from an earlier attempt. */
 | {
    kind: 'denoised';
    file: string;
    reused: boolean;
}
/** The denoiser gave up: the camera's own JPEG goes out, named `_org`. */
 | {
    kind: 'denoise_fell_back';
    file: string;
    reason: string;
} | {
    kind: 'found_dng';
    file: string;
} | {
    kind: 'developed';
    /** The picture's own develop, the preset (shape) the step names, or neutral. */
    from: 'own' | 'preset' | 'neutral';
    preset?: string | null;
    /** Read on the picture itself (`expose`, or the Auto step). */
    exposed: boolean;
    exposure_ev: number;
    lift: number;
    blacks: number;
    temperature: number;
    tint: number;
    masks: number;
    spots: number;
    /** The picture's stored crop was cut too. */
    cropped: boolean;
} | {
    kind: 'exposed';
    ev: number;
} | {
    kind: 'cropped';
    from_width: number;
    from_height: number;
    /** The rectangle in the picture it cut, before levelling turned it. */
    x: number;
    y: number;
    width: number;
    height: number;
    /** The shape asked for (`3:2/2:3`, `16:9`; empty for free). */
    aspect: string;
    /** `subject`, `centred` (nobody found), `given` (a Crop step) or `level` (only levelled). */
    placed_by: 'subject' | 'centred' | 'given' | 'level';
    subjects: CropSubject[];
    /** Where the camera focused, upright, in 0..1. */
    focus?: [number, number] | null;
    /** Degrees it was levelled by. */
    level_angle?: number | null;
    out_width: number;
    out_height: number;
} | {
    kind: 'resized';
    from_width: number;
    from_height: number;
    width: number;
    height: number;
} | {
    kind: 'identified';
    /** Numbers named, in the order read, and who they belong to. */
    numbers: string[];
    names: string[];
    /** Reads held for a person to settle, each with what it could be instead. */
    held: Record<string, string[]>;
    /** Numbers of people the step does not name. */
    unnamed: string[];
    people: number;
    with_digits: number;
    /** A person settled this picture's numbers before; nothing was read. */
    settled: boolean;
} | {
    kind: 'captioned';
    headline: string;
    caption: string;
    keywords: string[];
    creator: string;
} | {
    kind: 'transcribed';
    text: string;
}
/** `file` is in the album; `published` when the album publishes somewhere. */
 | {
    kind: 'in_album';
    album: string;
    file: string;
    published: boolean;
} | {
    kind: 'exported';
    output: WorkflowOutput;
} | {
    kind: 'webhook';
    status: number;
}
/** Claude's judgement before delivery (#910); `unjudged` when it did not answer, and the picture went out. */
 | {
    kind: 'judged';
    verdict: 'pass' | 'fail' | 'unjudged';
    reason: string;
    /** A better crop, `[left, top, right, bottom]` in 0..1 of the frame the crop step cut. */
    crop?: [number, number, number, number] | null;
    done: 'delivered' | 'held' | 'marked' | 'recropped';
    model: string;
};
/** A subject a crop framed around. */
interface CropSubject {
    /** The detector's class (`person`, `dog`, …). */
    class: string;
    /** `[x1, y1, x2, y2]` in 0..1 of the picture that was cut. */
    bbox: [number, number, number, number];
    /** Outlined where the camera focused, or the detection holding that point. */
    at_focus: boolean;
}
/** A file an Export step wrote, where it went, and what is in it. */
interface WorkflowOutput {
    file: string;
    /** Where it lies on this machine: the delivered file, the sent copy, or the one in the retry queue. */
    path: string;
    /** Empty for the work folder's `delivered/`. */
    connection: string;
    remote_path: string;
    /** Reached the connection; `false` while it waits in the retry queue for the reason in `error`. */
    delivered: boolean;
    error?: string | null;
    width: number;
    height: number;
    bytes: number;
    /** The start of the file's SHA-256: the same picture sent twice has the same digest. */
    digest: string;
    headline: string;
    caption: string;
}
/** One step's part in a picture's record. */
interface WorkflowStep {
    /** Its place in the workflow, from 0. */
    index: number;
    step: string;
    ms: number;
    /** What it did; absent when it failed. */
    outcome?: StepOutcome | null;
    error?: string | null;
}
/** A picture's whole way through a workflow (#905). */
interface WorkflowFile {
    /** Counts up while the app runs: pass the last one seen as `since`. */
    id: number;
    workflow: string;
    /** The picture's name as it arrived, and where it lay. */
    file: string;
    source: string;
    /** Epoch milliseconds it arrived and the workflow was done with it. */
    arrived_ms: number;
    finished_ms: number;
    steps: WorkflowStep[];
    /** Every Export step's file. */
    outputs: WorkflowOutput[];
    /** The first step that failed, and why. */
    failure?: string | null;
    /** A picture run again (#907): the record it redoes, and what was set for it. */
    rerun_of?: number | null;
    overrides?: WorkflowOverrides | null;
    /** Kept back from delivery. */
    held?: WorkflowHeld | null;
}
/** GET /api/workflow/files */
interface WorkflowFiles {
    /** Oldest first. */
    files: WorkflowFile[];
    /** Pass as `since` next time. */
    next: number;
}
interface WorkflowTemplateInfo {
    category: string;
    description: string;
    id: string;
    name: string;
    steps: string[];
}
/** Current zoom state. */
interface ZoomInfo {
    /** The fit mode: fit_screen, one_to_one, custom */
    fit_mode: 'fit_screen' | 'one_to_one' | 'custom';
    /**
     * The zoom the picture is drawn at, in screen pixels per picture pixel
     * (1.0 = 100 %: one picture pixel per screen pixel, also on a Retina
     * screen), fit included.
     */
    level: number;
    /** Pan offset x */
    pan_x: number;
    /** Pan offset y */
    pan_y: number;
    /**
     * The point of the picture in the middle of the view, as [x, y] in 0..1
     * of the upright picture; null at fit, where the whole picture shows.
     */
    center: [number, number] | null;
}
/** POST /api/zoom */
interface ZoomBody {
    /** The fit mode: fit_screen, one_to_one, custom */
    fit_mode?: 'fit_screen' | 'one_to_one' | 'custom';
    /**
     * Zoom level in screen pixels per picture pixel (0.05 to 20.0; 1.0 =
     * 100 %). If omitted, fit_mode is used alone. Like a level chosen in
     * the info bar, it is also what a click on the picture and Z zoom to
     * from then on (`zoom_click_level` in the settings); `fit_mode` alone
     * does not change that.
     */
    level?: number | null;
    /**
     * The point to put in the middle of the view, as [x, y] in 0..1 of the
     * upright picture (the frame masks and the focus box use), as dragging
     * the picture does. Only with a zoom level: at fit the whole picture
     * shows. Without it, the point in the middle stays where it was.
     */
    center?: [number, number] | null;
}
/** One person (or animal) in the picture on screen and how sharp they are beside the others (#810). */
interface PersonSharpnessInfo {
    /** x1, y1, x2, y2, normalised to the upright picture. */
    bbox: number[];
    animal: boolean;
    /** Sharpness inside the box over the whole frame's; null where not read. */
    sharpness?: number | null;
    /** Beside the sharpest in the picture: 1 for the sharpest. */
    share?: number | null;
    /** The number Identify read on them. */
    bib?: string | null;
}
/** GET /api/images/current/people-sharpness (#810) */
interface PeopleSharpnessInfo {
    /** null until read: the people are found when the picture has rested, and the call asks for them. */
    people?: PersonSharpnessInfo[] | null;
}
/** The workspaces: built-in (`default` is Reset Workspace) and saved (#595). */
interface WorkspaceList {
    builtin: string[];
    custom: string[];
}
/** One display. */
interface DisplayEntry {
    /** What `arrangeWindow` takes as `display`. */
    id: number;
    x: number;
    y: number;
    width: number;
    height: number;
    is_main: boolean;
}
/** Where the Window menu puts the main window. */
type WindowArrangement = 'tile_left' | 'tile_right' | 'fill' | 'center';
/** A finished recording of the main window (`ui.stopRecording`). */
interface RecordingSummary {
    /** The folder the frames are in. */
    dir: string;
    /**
     * Frames written, `frame-000000.jpg` on. `frames.txt` beside them has one
     * line per frame: its number and its time in milliseconds from the start.
     */
    frames: number;
    /** Frames the writers could not keep up with, left out. */
    dropped: number;
    seconds: number;
}
/** The FTP tether server (#600). */
interface TetherStatus {
    running: boolean;
    /** The address the server is bound to. */
    address?: string | null;
    /** Where a camera can connect (`ip:port`). */
    connect_addresses: string[];
    camera_connected: boolean;
    /** The folder frames arrive in. */
    folder: string;
    /** How many pictures it holds (a RAW+JPEG pair once). */
    pictures: number;
}
/** POST /api/import — the job started. */
interface ImportStarted {
    files: number;
    /** What GET /api/import/jobs and the `import_finished` event call it. */
    job: number;
    message: string;
}
/** One import, as the import panel shows it. */
interface ImportJobInfo {
    backed_up_files: number;
    completed_files: number;
    copied_bytes: number;
    current_file: string;
    dest: string;
    errors: string[];
    eta_seconds: number;
    /** Why it failed, when it did. */
    failure?: string | null;
    /** Whether it has finished (completed, cancelled or failed). */
    finished: boolean;
    job: number;
    skipped_files: number;
    /** The card or folder it copies from. */
    source: string;
    /** scanning | copying | backing_up | verifying | completed | cancelled | failed */
    state: 'scanning' | 'copying' | 'backing_up' | 'verifying' | 'completed' | 'cancelled' | 'failed';
    total_bytes: number;
    total_files: number;
    verified_files: number;
    verify_failures: number;
}
/** What the info sidebar shows about a picture. */
interface PictureMetadata {
    /**
     * The camera's facts: camera, lens, exposure, size, capture time
     * (`date_taken` is the sidecar's when one sets it).
     */
    exif: ImageMetadata;
    /** An audio note beside the picture (`DSC1.WAV`). */
    has_audio: boolean;
    index: number;
    /** Where it was taken: the sidecar's position first, then the file's. */
    latitude?: number | null;
    longitude?: number | null;
    path: string;
    /** A transcription is being made. */
    transcribing: boolean;
    /** The note's transcription, once made. */
    transcription?: string | null;
}
/**
 * EXIF metadata for display in the UI sidebar and overlay.
 *
 * Text fields (make, camera, lens, serial, software, artist, copyright) hold
 * the text the file holds. The exposure fields are display strings
 * ("f/2.8", "1/125 s").
 */
interface ImageMetadata {
    aperture?: string | null;
    artist?: string | null;
    camera?: string | null;
    /** Epoch milliseconds from DateTimeOriginal + SubSecTimeOriginal. */
    capture_time_ms?: number | null;
    color_space?: string | null;
    copyright?: string | null;
    /**
     * DateTimeOriginal in EXIF spelling, `YYYY:MM:DD HH:MM:SS`. Read it with
     * [`parse_datetime`], show it with [`display_datetime`].
     */
    date_taken?: string | null;
    exposure_compensation?: string | null;
    exposure_program?: string | null;
    file_name?: string | null;
    file_size?: number | null;
    flash?: string | null;
    focal_length?: string | null;
    gps_latitude?: number | null;
    gps_longitude?: number | null;
    height?: number | null;
    iso?: string | null;
    lens?: string | null;
    make?: string | null;
    metering_mode?: string | null;
    /**
     * OffsetTimeOriginal as the file holds it (`+02:00`): the camera
     * clock's offset from UTC when the picture was taken. Read it with
     * [`capture_timestamp`] / [`capture_offset`].
     */
    offset_time_original?: string | null;
    /**
     * The orientation the file gives the picture (1–8), by
     * `loader::file_orientation`'s rule; a rotation set in photosuite is the
     * row's own (`ImageState::orientation`) and goes on top.
     */
    orientation?: number | null;
    serial_number?: string | null;
    shutter_speed?: string | null;
    /** ExposureTime as seconds (e.g. 0.008 for 1/125). */
    shutter_speed_secs?: number | null;
    software?: string | null;
    white_balance?: string | null;
    /** The stored size, before the orientation turns it. */
    width?: number | null;
}
/** GET/POST /api/images/current/audio */
interface AudioState {
    has_audio: boolean;
    playing: boolean;
}
/** The day's capture notes for the current picture, as the overlay lists them. */
interface CaptureNotes {
    notes: CaptureNoteInfo[];
    /** The note written just after the picture, which the overlay picks. */
    suggested?: number | null;
}
/** One capture note: the people someone wrote down at the time. */
interface CaptureNoteInfo {
    /** They agreed to be named. */
    consent: boolean;
    event?: string | null;
    /** Its place in `notes`, what `apply` takes. */
    index: number;
    /** Everyone written in it, together. */
    names: string[];
    /** Epoch milliseconds when it was written. */
    ts: number;
}
/** A camera card the Import dialog would offer: a mounted volume with a DCIM folder (#599). */
interface CardInfo {
    name: string;
    /** What `importFrom` takes as `source_path`. */
    mount_point: string;
    total_bytes: number;
    free_bytes: number;
    image_count: number;
    image_bytes: number;
}
/** A batch job the progress panel shows (#606). */
interface BatchJobInfo {
    kind: 'auto_crop' | 'denoise' | 'folder_move';
    label: string;
    done: number;
    total: number;
    finished: boolean;
}
/** Whether the transcription service has its key (#607). */
interface TranscriptionKeyInfo {
    provider: string;
    /** A key is stored or in the environment; it is never read back. */
    set: boolean;
}
/** A program the external-editor list can pick. */
interface DetectedProgram {
    name: string;
    path: string;
    role: string;
}
/** One detection model. */
interface ModelInfo {
    model: string;
    label: string;
    downloaded: boolean;
    downloading: boolean;
}
interface ModelList {
    models: ModelInfo[];
    /** Why the last download failed, when it did. */
    error?: string | null;
}
/** The geocoding dataset place names are looked up in. */
interface GeocodeDatasetInfo {
    dataset: string;
    downloaded: boolean;
    loaded: boolean;
    loading: boolean;
    downloaded_bytes?: number | null;
    total_bytes?: number | null;
    error?: string | null;
}
/** An optional download the app fetches the first time a feature is used (#848). */
type DownloadName = 'subject' | 'sky' | 'objects' | 'people' | 'landscape' | 'search' | 'detector' | 'places';
/** One optional download, and where it is. */
interface DownloadInfo {
    name: DownloadName;
    /** The features it is for, as Help → Downloads… says. */
    label: string;
    gives: string;
    state: 'here' | 'downloading' | 'missing' | 'failed';
    /** When missing: its size, as said to the user. */
    size?: string | null;
    downloaded?: number | null;
    total?: number | null;
    error?: string | null;
}
/** GET /api/downloads */
interface DownloadList {
    downloads: DownloadInfo[];
}
/**
 * One command: a request to the API, as a key sends it (#610). Every route
 * that does something is one, once per value where the values are known.
 */
interface ActionInfo {
    /** The route's operation id and the values it is sent with: `navigate?target=next`. */
    id: string;
    label: string;
    /** The route's tag, and the route when it has more than one value: "Navigation › Navigate". */
    group: string;
    method: string;
    /** A `{name}` left in it is one of `inputs`. */
    path: string;
    /** The JSON body, when the route takes one. */
    body?: Record<string, unknown>;
    /** What has to be filled in before it can be sent. */
    inputs?: InputInfo[];
    /** Its icon: a Phosphor icon's name (`arrow-right`). */
    icon: string;
    /** The colour to draw the icon in, when the colour is the point (a colour label). */
    icon_color?: string;
}
/** A value a command needs that the API has no fixed set for. */
interface InputInfo {
    /** The field, or the `{parameter}` in the path. */
    name: string;
    place: 'path' | 'body';
    /** The JSON type: string, integer, number, boolean, array, object. */
    kind: string;
    description?: string;
}
/** One dial: a develop slider, or one of the view's. */
interface AdjustmentInfo {
    /**
     * What `actions.adjust()` takes: a `DevelopBody` field, or one of the
     * view's — picture, zoom (percent), exposure_aid, rating, survey_count,
     * slideshow_interval.
     */
    field: string;
    label: string;
    /** View, or the develop panel's group. */
    group: string;
    /** How far it goes: a develop slider's reach, which can be past the panel's rail (Shadows −200..300). */
    min: number;
    max: number;
    /** What one step moves it. */
    step: number;
    decimals: number;
    /** Where a reset goes (zoom's 0 is fit). */
    neutral: number;
    /** Its icon: a Phosphor icon's name (`sun` for Exposure). */
    icon: string;
}
interface ActionCatalog {
    commands: ActionInfo[];
    adjustments: AdjustmentInfo[];
}
/** A dial's value. */
interface AdjustedValue {
    field: string;
    value: number;
    /** What to show beside the dial: "0.35", "12 / 240", "Fit", "★★★☆☆". */
    text: string;
}
/** What a console shows now. */
interface ActionState {
    /** Every dial's value. */
    values: AdjustedValue[];
    /**
     * The ids of the commands that are on: a menu item with its check mark,
     * and the picture's own stars, flag and colour label.
     */
    on: string[];
    /**
     * Where the photographer is, when it has commands of its own; a console
     * shows what belongs there. Changes with `ui_state_changed { context }`.
     */
    context?: 'crop' | 'iptc' | 'masks' | 'survey' | 'compare' | 'grid' | 'raw_steps';
}
/** A key the app answers itself (`actions.pressKey`). */
type FixedKey = 'next' | 'previous' | 'down' | 'up' | 'first' | 'last' | 'enter' | 'escape' | 'swap_compare' | 'next_survey_page' | 'previous_survey_page' | 'larger' | 'smaller' | 'split_horizontal' | 'split_vertical' | 'reset_zoom' | 'before_after';
/** What the workflow watcher found (#844). */
interface WatchFinding {
    kind: 'not_delivering' | 'step_failing' | 'queue_waiting' | 'disk_filling' | 'stopped' | 'unjudged' | 'judged';
    /** What is wrong, in a photographer's words. */
    what: string;
    /** What to do about it. */
    todo: string;
    /** Epoch seconds it was first seen. */
    since: number;
    /** The workflow records of the pictures it is about (#913). */
    pictures?: number[];
}
/** What the workflow watcher does (#844). */
interface WatchSettings {
    enabled: boolean;
    idle_minutes: number;
    queue_minutes: number;
    min_free_gb: number;
    /** 0 asks only the rules. */
    claude_every_minutes: number;
    model: string;
    notify: boolean;
    /** Show Claude the pictures delivered since the last check, not only the facts (#913). */
    pictures: boolean;
}
/** PUT /api/watch: the fields given change, the rest stay. */
type WatchSettingsBody = Partial<WatchSettings>;
interface WatchCost {
    model: string;
    calls_per_hour: number;
    /** Dollars for a ten-hour event day; null for a model without a known price. */
    usd_per_event_day: number | null;
}
/** GET /api/watch */
interface WatchInfo {
    settings: WatchSettings;
    workflow: string | null;
    running: boolean;
    findings: WatchFinding[];
    last_judged: number | null;
    calls_last_hour: number;
    cost: WatchCost;
    last_error: string | null;
}

/**
 * Engine client: pipeline handles and the ingest work queue (#211).
 *
 * The rule this file lives under: it may rename, default, and await. It may
 * not sequence, retry, or branch. Anything that needed those would be a gap in
 * the API, and fixing it here would only fix it for TypeScript — the curl and
 * Python callers would still be missing it.
 *
 * So `denoise()` is one POST, chaining is syntax over the handles the server
 * already returns, and `await` is the `?wait=` query parameter.
 */

interface StepResult {
    status: 'done';
    job: string;
    step: string;
    handle: string;
    path?: string;
    /** What the step did (#905): its crop, the numbers read, Claude's verdict. */
    outcome?: StepOutcome | null;
    /**
     * `false` when an optional step failed and the chain carried on from the
     * input handle. Always check this before claiming the step ran — the whole
     * point of marking a step optional is that the delivery can go out without
     * it, and a caller that never looks cannot tell the difference.
     */
    applied?: boolean;
    /** Why an optional step did not apply. */
    error?: string;
    /**
     * Names of the steps in this chain that did NOT apply.
     *
     * A chained call resolves to the LAST step's result, so without this an
     * optional step that fell through leaves no trace in what the caller sees —
     * and a whole match goes out un-denoised behind a column of success lines.
     */
    skipped?: string[];
}
/**
 * Somewhere pictures are sent.
 *
 * Made once with `culling.to(...)` and named on every delivery, so a script
 * says where each picture went rather than hiding it in a setting.
 */
interface Place {
    /** A Google Drive share link, or the name of a connection. */
    connection: string | null;
    /** Folder inside it. Created on first use and reused after that. */
    path: string;
}
/** A rectangle, 0.0–1.0 relative to the image. */
interface CropRect {
    x: number;
    y: number;
    width: number;
    height: number;
}
/** Where the photographer stood, as the event's results know it. */
interface TimingPoint {
    /** The results column: a split (`TIME21`) or `finish`. */
    column: string;
    /** The column is time since the participant's start, not a time of day. */
    elapsed?: boolean;
    /** The start as a time of day for participants whose results carry none. */
    start?: string;
    /** Seconds to add to the camera's clock to get the timing system's. */
    offset_secs?: number;
    /** How far from the frame's time a passing still counts, in seconds. */
    window_secs?: number;
}
interface PipelineIdentifyBody {
    rosters: string[];
    yolox?: YoloxModel;
    /** Classes never named, matched inside the class or age group. */
    unnamedClasses?: string[];
    /** Numbers or names of participants who asked not to be named. */
    optedOut?: string[];
    /** For a frame with a person and no number read: who passed this point then, held, never named. */
    timingPoint?: TimingPoint | null;
}
/** POST /api/detect */
interface DetectBody {
    path: string;
    rosters?: string[];
    /** Classes never named, matched inside the class or age group. */
    unnamedClasses?: string[];
    /** Numbers or names of participants who asked not to be named. */
    optedOut?: string[];
    /** For a frame with a person and no number read: who passed this point then, held, never named. */
    timingPoint?: TimingPoint | null;
    yolox?: YoloxModel;
}
interface StepOptions {
    /** Seconds to wait per request. The call still resolves when the step ends. */
    wait?: number;
    /**
     * How many times the ENGINE runs the step before reporting it failed.
     *
     * For steps that fail in ways that pass on their own — an external denoiser
     * busy with the batch before this one, or waiting behind a dialog nobody is
     * there to click. The attempts happen inside culling, so a caller in any
     * language gets them by asking; they are not something this SDK does on the
     * side (#235).
     */
    retry?: number;
    /** Milliseconds between attempts. Default 5000. */
    retryPauseMs?: number;
    /**
     * Carry on from the INPUT handle instead of throwing when the step fails.
     * The result then has `applied: false` and the reason in `error`.
     */
    optional?: boolean;
}
/**
 * A handle, and the steps that can be run on it.
 *
 * Awaitable and chainable: `await h.resize(1920)` gives the next handle, and
 * `await h.resize(1920).export(...)` runs both. Each step consumes the handle
 * it was given, so the file a step works on is the one the caller named — the
 * data flow is written in the script, not inferred from a cursor.
 */
declare class PipelineHandle implements PromiseLike<StepResult> {
    private readonly http;
    private resolved;
    /**
     * The frame's own name, carried down the chain.
     *
     * `{name}` must not be read off the current file: after a delivery that is
     * the JPEG just written, so the line reporting it said `X_crop → …X_crop`
     * and the frame's real name was nowhere.
     */
    private readonly frameBase?;
    constructor(http: HttpClient, resolved: Promise<StepResult>, 
    /**
     * The frame's own name, carried down the chain.
     *
     * `{name}` must not be read off the current file: after a delivery that is
     * the JPEG just written, so the line reporting it said `X_crop → …X_crop`
     * and the frame's real name was nowhere.
     */
    frameBase?: string | undefined);
    then<A = StepResult, B = never>(onfulfilled?: ((value: StepResult) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): PromiseLike<A | B>;
    /** The handle id, once whatever produced it has finished. */
    id(): Promise<string>;
    private step;
    /**
     * Put the frame back the way it was before you started shaping it.
     *
     * Same picture, same source, no crop and no resize — so the next delivery of
     * this frame is cut from the same material as the last, not from the JPEG
     * that one wrote.
     *
     * It does not rewind past a denoise, nor past `develop` or `auto`: those
     * choose the picture, and a reset renders it again with the same settings.
     * A frame delivered without noise reduction or without the photographer's
     * edits looks perfectly fine on a desk, which is what makes it dangerous.
     */
    reset(options?: StepOptions): PipelineHandle;
    /**
     * Send the picture as it now stands.
     *
     * `to` is a place made with `culling.to(...)`. It is named on every delivery
     * on purpose: where a picture went is the first thing anyone asks when one
     * is missing, and a destination set once further up is a destination nobody
     * can see.
     *
     * `name` may use `{base}` — the camera's own filename, which survives a
     * denoise and a reset — plus `{stem}`, `{ext}` and the other metadata keys.
     */
    deliver(to: Place, name: string, options?: StepOptions & ExportChoice): PipelineHandle;
    /**
     * Narrate, without breaking the chain.
     *
     * Printing the delivered path used to mean stopping to hold the result in a
     * variable, which is how a two-line delivery grew a scaffold of names for
     * things nobody wanted to name.
     *
     * `{name}`, `{base}`, `{path}` and `{step}` are filled in from the step this
     * follows. Client-side: it is the script's own narration, not work for
     * culling to do, so a caller in another language just prints between calls.
     */
    log(message: string): PipelineHandle;
    /**
     * Continue from this point more than once.
     *
     * One frame is very often delivered more than once — cut to the player at HD
     * and whole at 4K, or one crop per runner in the picture. This handle is
     * spent; the branches are independent and can be run in any order.
     *
     * What each branch carries: the same current file, the identified people, and
     * the detection boxes — so a second delivery never re-runs the detector. What
     * it does not carry is the loaded pixels, because a frame is ~200 MB and any
     * branch that crops would rewrite them anyway; each branch loads its own.
     */
    fork(count?: number): Promise<PipelineHandle[]>;
    /**
     * A second handle on the same picture, leaving this one usable.
     *
     * `const a = shot.copy()` reads as what it does. Prefer `reset()` when the
     * deliveries are simply one after another; reach for `copy()` when they are
     * genuinely independent.
     */
    copy(): Promise<PipelineHandle>;
    /**
     * Work from the JPEG inside the current file rather than its raw data.
     *
     * After a denoise the current file is the DNG the denoiser wrote, and the
     * JPEG inside it IS that result: full size, already demosaiced. Say this
     * where it takes effect, instead of declaring a decode policy on the session
     * before anything has happened (#236).
     */
    useJpeg(options?: StepOptions): PipelineHandle;
    /** Work from the current file's own pixels — decode the raw, render the DNG. */
    useRaw(options?: StepOptions): PipelineHandle;
    /**
     * Crop to a rectangle you chose, in normalised coordinates.
     *
     * `autoCrop` picks a subject for you. Use this when you have the detections
     * and your own rules about them — one crop per runner, one of a pair running
     * together, one of the whole group.
     *
     * `aspect` is the shape it is forced into. The rectangle is grown around its
     * centre and shifted inside the frame, never clipped, so what is delivered
     * really is the shape asked for. Any `w:h` (or `wxh`) works, portrait
     * included; an unknown shape is an ApiError 400.
     */
    crop(rect: CropRect, aspect?: string, options?: StepOptions): PipelineHandle;
    /**
     * Run the external noise-reduction application and continue from what it
     * produced.
     *
     * With the default `files: 'rawOnly'`, handing this a JPEG succeeds without
     * the denoiser ever running.
     */
    denoise(profile: string, options?: StepOptions & {
        files?: 'rawOnly' | 'all';
        /**
         * Deliver the CAMERA's own JPEG rather than nothing when the denoiser
         * will not play — a finished picture, just not a denoised one.
         *
         * The delivery is renamed: `{base}` becomes `<name>_org`. A frame that
         * skipped noise reduction and is named like one that did not is the
         * failure that looks like a success, and this delivery has met enough of
         * those.
         */
        fallback?: boolean;
    }): PipelineHandle;
    /**
     * Read the race numbers in the frame and attach who they belong to.
     *
     * Each roster is an event directory holding `bibs.json` and `teams.json`.
     * Pass several when races share a photo day: the frame is read once with a
     * digit range covering every start list, and each event keeps the riders its
     * own whitelist matches.
     *
     * Put this BEFORE any crop or resize: it reads the numbers off
     * full-resolution pixels, and a bib legible at 6000px is usually gone at
     * 1920.
     *
     * Finding nobody is a success, not a failure — most frames of a race are
     * spectators, marshals and empty road.
     */
    identify(rosters: string | string[], options?: StepOptions & Omit<PipelineIdentifyBody, 'rosters'>): PipelineHandle;
    /**
     * Crop to the subject in a given shape: `16:9` or `16x9`, `9:16`,
     * `square`, `free`, or a range like `3:2..16:9` picked per frame. The crop
     * tool's names (`CropAspect`) work too. An unknown shape is an
     * ApiError 400, never a quiet free crop.
     */
    autoCrop(aspect: string, options?: StepOptions & {
        padding?: number;
        level?: boolean;
        aroundFocus?: boolean;
    }): PipelineHandle;
    /**
     * Put the frame's file in an album, with the develop and the crop the
     * steps before chose written to its sidecar. An album published to an
     * eventphotos event (or a gallery, SmugMug, a folder) sends it by itself,
     * the crop as an edit the destination can widen.
     */
    addToAlbum(album: string, options?: StepOptions): PipelineHandle;
    /**
     * Claude looks at the frame (beside the whole frame it was cut from) and
     * passes or fails it against `criteria`, on the user's own Claude key. The
     * verdict, the reason and a better crop are in the result's `outcome`.
     */
    approve(criteria: string, options?: StepOptions & {
        model?: string;
        onFail?: 'hold' | 'mark' | 'recrop';
        timeoutSecs?: number;
    }): PipelineHandle;
    /**
     * Expose the frame properly without being told how — the Auto button.
     *
     * Reads the picture and sets exposure, highlight recovery and shadow lift, so
     * a frame shot too dark or too bright arrives looking like the ones around
     * it. It works on the RAW file, which is why it can help: a finished JPEG has
     * thrown away the headroom a recovery needs.
     *
     * It reloads the file, so it replaces whatever `useRaw` or `useJpeg` put
     * there and belongs before the crop.
     *
     * `strength` below 1 keeps only part of the correction, leaving some of the
     * photographer's own exposure in the picture.
     */
    auto(options?: StepOptions & {
        strength?: number;
    }): PipelineHandle;
    /**
     * Render the file through the develop settings stored beside it.
     *
     * Work done in the app lives in the XMP sidecar. Without this step a
     * delivery pipeline reloads the RAW and renders it as shot, so every
     * exposure correction, mask and spot fix the photographer made is thrown
     * away on the way to the customer — silently, because a picture still comes
     * out at the other end.
     *
     * It makes the picture the way every export does — a RAW from its sensor
     * data, whether or not anything is stored — so it needs no `useRaw` before
     * it and can sit in a pipeline unconditionally. Like `auto`, it reloads the
     * file, so it belongs before the crop. `crop: false` leaves the stored crop alone, for a
     * pipeline that places its own. `preset` names a develop preset for a
     * file with nothing stored, a DNG straight from the denoiser: the tone of
     * the event, the same on every picture. `expose` develops each such file
     * with Auto, so files shot under and over go out looking like one set.
     */
    develop(options?: StepOptions & {
        crop?: boolean;
        preset?: string;
        expose?: boolean;
    }): PipelineHandle;
    /**
     * Scale so the long side is `maxSide`. Pass `upscale` for a fixed delivery
     * size — without it this only shrinks, and a tight crop goes out smaller
     * than asked for.
     */
    resize(maxSide: number, options?: StepOptions & {
        upscale?: boolean;
    }): PipelineHandle;
    /**
     * Write the pixels out.
     *
     * `connection` names a configured destination, or is a Google Drive folder
     * share link — a delivery can then name the folder it was invited to
     * without anything being configured first, using the account the app is
     * signed in as. Pass `null` for a plain local write to `path`: an absolute
     * folder, or one under the work directory's `delivered/` (never the work
     * directory itself, which is the ingest queue).
     *
     * For a cloud destination `path` is a folder path *inside* it, created on
     * first use: `'nordsjorittet-2026'` puts the event in its own subfolder,
     * and every later frame of the same event finds that same folder.
     */
    export(connection: string | null, path: string, options?: StepOptions & ExportChoice & {
        filename?: string;
    }): PipelineHandle;
}
interface OpenOptions {
    /**
     * `native-raw` (default) decodes through the full-resolution path.
     * `embedded-jpeg` reads the full-size JPEG already inside the file, which is
     * much faster and is what a delivery wants after a denoise step. Both honour
     * EXIF rotation. The same decode source as `view.decodeSource`.
     */
    source?: DecodeSource;
    workDir?: string;
    /**
     * The watch this file came from, with its `lease`, so closing the session
     * settles the file instead of leaving it to be handed out again. `each`
     * passes these for you; a caller driving the queue itself must pass them.
     */
    watch?: string;
    lease?: string;
}
interface Session {
    session: string;
    handle: PipelineHandle;
    path: string;
}
/** What `detect` found in a frame. */
/** The YOLOX detector sizes, smallest first. An unknown name is a 400. */
type YoloxModel = 'nano' | 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' | 'x';
interface Detected {
    /** The frame's own size in pixels. The boxes are normalised against it. */
    width: number;
    height: number;
    /**
     * Each person, with the number they wear when a start list was given and
     * the read may name somebody. A held read leaves `bib` out.
     */
    persons: {
        bbox: [number, number, number, number];
        bib?: string;
    }[];
    /** Numbers the start lists name in the frame, in the order first read. */
    bibs: string[];
    /**
     * Reads held for a human rather than named, each with what it could be
     * instead: `87` when the start list also has 187, a single digit (empty
     * list), or a number the text on its own plate argues against.
     */
    held: Record<string, string[]>;
    /** Numbers on the start list of someone not named (a class or an opt-out). */
    unnamed: string[];
    personCount: number;
    bibVisible: number;
}
declare class PipelineNamespace {
    private readonly http;
    constructor(http: HttpClient);
    /**
     * Where the people are in a frame, as `[x, y, width, height]` normalised to
     * it, with the race number read off each of them when start lists are given
     * — checked against them exactly as `identify` checks it. Without start
     * lists no number is read.
     *
     * This is what `crop` is for: `autoCrop` picks a subject for you, and a
     * caller with its own rules about the people it finds needs the boxes.
     */
    /** What a handle points at: its path, and the named files it knows (`original`, `denoised`, `dng`, …). */
    handle(handle: string): Promise<HandleInfo>;
    detect(path: string, options?: Omit<DetectBody, 'path'>): Promise<Detected>;
    /** Open a session on a file and get its first handle. */
    open(path: string, options?: OpenOptions): Promise<Session>;
    /**
     * End a session. `delivered` acknowledges the file, `abandoned` puts it back
     * in the queue, `failed` moves it to `failed/` with the reason, and
     * `discarded` moves it to `not-sent/` — a frame that was looked at and lost
     * to a better one of the same moment is neither a delivery nor an error.
     */
    close(session: string, outcome?: 'delivered' | 'abandoned' | 'failed' | 'discarded', reason?: string): Promise<void>;
}
interface WatchOptions {
    remotePath?: string;
    workDir: string;
    pollIntervalSecs?: number;
    /**
     * Files leased out at once — handed out and not yet settled. Default 1.
     *
     * A lease is not a worker: raising this does not make steps run in
     * parallel. It is for a client that has to HOLD several frames before it
     * can judge any of them, the way a burst picker does with `next()`.
     */
    concurrency?: number;
    ordered?: boolean;
    /** Delete each file from the source once downloaded. */
    deleteFromSource?: boolean;
    /**
     * Take the work directory over from a watch already on it, instead of the
     * call failing with a 409.
     *
     * For a delivery that owns its work directory and is being started again:
     * the watch its last run left behind keeps polling, and if that run died
     * mid-frame it holds the frame and is never reaped. Only the watch on THIS
     * directory is stopped — every file it downloaded and never delivered is
     * handed out again by the new one.
     */
    replace?: boolean;
}
/**
 * A frame off the queue, ready to work on.
 *
 * It IS the handle — `frame.denoise(...)` rather than `frame.handle.denoise(...)`.
 * The handle was the engine's word for it, and it had no business in a script
 * that only wants to say what happens to a picture.
 */
type IngestedFile = PipelineHandle & {
    /** Local path of the downloaded copy. */
    path: string;
    /** Original name on the source, e.g. `DSC0942.ARW`. */
    name: string;
    /** That name without its extension — what deliveries are named after. */
    base: string;
    /** When the shutter fired, epoch ms, and how long it was open. */
    capturedMs?: number;
    shutterSecs?: number;
    /**
     * This frame has been DELIVERED before, and is being offered again so it can
     * be done properly — it went out on the camera's own JPEG because the
     * denoiser would not play.
     *
     * Use it to stop that happening twice: `fallback: !frame.retry` falls back
     * the first time, and on the second pass either denoises or fails, rather
     * than putting a second `_org` pair beside the first.
     *
     * A frame whose delivery FAILED is not a retry — nothing reached the desk,
     * so it comes back as an ordinary new file with every option intact.
     */
    retry: boolean;
    /** The session opened for this file; already carries the queue lease. */
    session: string;
    /** The frame itself. Kept so older scripts still read. */
    handle: PipelineHandle;
};
interface EachOptions {
    /** Stop the loop. */
    signal?: AbortSignal;
    /**
     * Called when a frame could not be delivered. Defaults to printing to
     * stderr — a failure nobody is told about looks exactly like a slow frame
     * from the outside, and the loop carries on either way.
     */
    onError?: (file: IngestedFile, error: unknown) => void;
    /** Seconds each poll holds open while the queue is empty. */
    wait?: number;
    source?: DecodeSource;
    /**
     * Consecutive failed polls before `each` gives up (default 10, with a
     * backoff between them). Transient errors must not end an unattended
     * delivery; a watch that is genuinely gone must not be polled forever
     * either, so past this it throws and a supervisor starts a fresh one.
     */
    maxPollFailures?: number;
}
/** A file waiting in a watch's queue, as `next()` hands it over. */
interface QueuedFile {
    path: string;
    name: string;
    /** Settle this when the session for the file closes. */
    lease: string;
    /** Delivered before and offered again — see `IngestedFile.retry`. */
    retry: boolean;
    /** When the shutter fired, epoch ms, and how long it was open. */
    capturedMs?: number;
    shutterSecs?: number;
}
/** A watch's queue, as `Watch.status()` reads it. */
interface WatchStatus {
    id: string;
    workDir: string;
    /**
     * Seconds since a client last asked for work. Growing while `ready` is
     * non-zero and `inflight` is zero means nobody is draining the queue.
     */
    idleSecs: number;
    concurrency: number;
    ordered: boolean;
    /** Waiting to be handed out. */
    ready: number;
    /** Handed out and not yet settled. */
    inflight: number;
    /**
     * Delivered on the camera's own JPEG and waiting for another go when the
     * queue is quiet. A queue nobody can see fills up in silence.
     */
    deferred: number;
    delivered: number;
}
declare class Watch {
    private readonly http;
    private readonly pipeline;
    readonly id: string;
    readonly workDir: string;
    constructor(http: HttpClient, pipeline: PipelineNamespace, id: string, workDir: string);
    /**
     * Process each file as it arrives.
     *
     * The lease, the session lifecycle and the outcome are handled here so a
     * caller writes only what happens to a file: the handler returning means
     * delivered, the handler throwing means `failed/`. Both are outcomes the
     * API exposes directly, so a curl caller reaches them too.
     */
    each(handler: (file: IngestedFile) => PromiseLike<unknown>, options?: EachOptions): Promise<void>;
    /**
     * Take the next file off the queue, or `undefined` when nothing is waiting.
     *
     * `each` is the loop most callers want. This is for the ones that cannot use
     * it: a picker that has to hold several frames before it can judge any of
     * them needs to keep taking files while earlier ones are still undecided,
     * which is exactly what `each` will not do. Raise the watch's `concurrency`
     * to at least the number of frames you intend to hold, or the queue will wait
     * for you to settle the first before releasing the second.
     */
    next(options?: {
        wait?: number;
    }): Promise<QueuedFile | undefined>;
    /** Queue depth and delivery counts — the numbers that show a stuck queue. */
    status(): Promise<WatchStatus>;
    stop(): Promise<void>;
}
declare class IngestNamespace {
    private readonly http;
    private readonly pipeline;
    constructor(http: HttpClient, pipeline: PipelineNamespace);
    /** Start watching a named connection. Credentials stay in the keychain. */
    watch(connection: string, options: WatchOptions): Promise<Watch>;
    list(): Promise<string[]>;
    /**
     * Stop a watch by id — including one this process never started.
     *
     * `Watch.stop()` only reaches a watch you are holding. The one that has to be
     * stopped is usually the opposite: left behind by a run that died, still
     * polling, still downloading, and hiding every file it takes from the watch
     * that replaced it.
     */
    stop(id: string): Promise<void>;
}

declare class NavigateNamespace {
    private http;
    constructor(http: HttpClient);
    to(target: NavigateTarget): Promise<void>;
    next(): Promise<void>;
    previous(): Promise<void>;
    first(): Promise<void>;
    last(): Promise<void>;
}

declare class ImagesNamespace {
    private http;
    constructor(http: HttpClient);
    /**
     * Merge a bracket into one HDR picture, or frames into a panorama (#579):
     * a linear DNG beside the first frame, shown in the folder. Takes as long
     * as the merge does.
     */
    /**
     * What the app has learned of what you keep (#582), from your picks and
     * stars in earlier folders. Pick Best in Bursts and Reject Duplicates rank
     * by it once `kept` and `passed_over` both reach `learn_from`.
     */
    taste(): Promise<TasteInfo>;
    /** Forget what was learned of what you keep; it learns again from the next folders. */
    resetTaste(): Promise<void>;
    merge(body: MergePicturesBody): Promise<Merged>;
    /** The exposure bracket the picture on screen is in. */
    bracket(): Promise<BracketInfo>;
    /** The pictures shown that have a position, as the Map panel pins them (#578). */
    positions(): Promise<MapPositions>;
    /** Give pictures a position, as dropping them on the map does; written to their sidecars. */
    setPosition(body: MapPositionBody): Promise<MapPositions>;
    /**
     * Pictures in the window without a position, placed by the nearest picture
     * in time from the same camera that has one, no more than `minutes` apart
     * (10 without); given those positions with `apply` (#668).
     */
    placeByNeighbours(body?: PlaceByNeighboursBody): Promise<PlacedByNeighbours>;
    /** The video on screen: playing, position, duration, sound (#574). */
    video(): Promise<VideoState>;
    /** Play or pause the video on screen, seek it, or turn its sound off or on. */
    setVideo(change: VideoBody): Promise<VideoState>;
    /**
     * What the info sidebar shows about a picture (the current one when no
     * index is given): its EXIF facts, position, audio note and transcription.
     */
    metadata(index?: number): Promise<PictureMetadata>;
    /** Transcribe the current picture's audio note; the text comes as `transcribed`. */
    transcribe(): Promise<void>;
    /** Whether the current picture has an audio note, and whether one plays. */
    audio(): Promise<AudioState>;
    /** Play the current picture's audio note, or stop what plays. */
    setAudio(playing: boolean): Promise<AudioState>;
    /** The day's capture notes for the current picture, the one written just after it suggested. */
    captureNotes(): Promise<CaptureNotes>;
    /** Write a note's people on the current picture (the suggested note when no index is given). */
    applyCaptureNote(index?: number): Promise<void>;
    /** Fetch newer capture notes from the server. */
    refreshCaptureNotes(): Promise<void>;
    /** Read the race numbers on the current picture, in the background; they land as keywords. */
    detectBibs(): Promise<void>;
    /** A virtual copy of the current picture: its own develop and marks, no file of its own. */
    createVirtualCopy(): Promise<void>;
    /** Delete the current picture, which must be a virtual copy. */
    deleteVirtualCopy(): Promise<void>;
    /** Collapse or expand the virtual-copy stack of a row (the current one when left out). */
    toggleVirtualCopyStack(index?: number): Promise<void>;
    /** Collapse every stack when any is expanded, else expand them all. */
    toggleAllVirtualCopyStacks(): Promise<void>;
    list(): Promise<ImageInfo[]>;
    current(): Promise<ImageInfo>;
    get(index: number): Promise<ImageInfo>;
    setRating(rating: number): Promise<void>;
    setFlag(flag: Flag): Promise<void>;
    pick(): Promise<void>;
    reject(): Promise<void>;
    unflag(): Promise<void>;
    setLabel(color: ColorLabel): Promise<void>;
    toggleTag(): Promise<void>;
    rotate(degrees: number): Promise<void>;
    /**
     * Set the crop angle from the picture's own edges — the straighten you do
     * not have to draw a line for.
     *
     * Reads the direction of every edge in the frame and takes the one the
     * picture prefers; horizons and buildings both count. A frame with nothing
     * straight in it is left alone rather than tilted at random, and the angle
     * is held to what the crop rectangle can be turned to without dragging
     * empty corners in.
     */
    autoLevel(): Promise<void>;
    /**
     * Pull the crop in until none of it falls in the empty corners a
     * perspective correction leaves behind. Keeps the rectangle's shape and its
     * angle, so a 16:9 crop stays 16:9.
     */
    constrainCrop(): Promise<void>;
    /**
     * Crop the current image around the people in it, the way the auto-crop key
     * does. A frame with nobody in it is left as it is.
     */
    autoCrop(): Promise<void>;
    /**
     * How auto-crop's learning stands: the base model, the crops kept as
     * examples (every crop made in the crop tool or with `setCrop`), how the
     * suggestions matched the latest of them, and a new base model reading them.
     */
    autoCropLearning(): Promise<LearningStatus>;
    /** Pause a new base model reading the examples; it stays paused over restarts. */
    pauseAutoCropLearning(): Promise<LearningStatus>;
    resumeAutoCropLearning(): Promise<LearningStatus>;
    /** Take the line about how the last new base model went off the info bar. */
    dismissAutoCropLearningOutcome(): Promise<LearningStatus>;
    /**
     * The crop tool's own state — the mode, not the rectangle.
     *
     * Setting `active: false` CLOSES the tool by applying what is in it, the
     * same as pressing Apply; the other fields are taken first, so closing does
     * not throw away the shape you just set.
     */
    cropTool(state?: CropToolOptions): Promise<CropToolState>;
    setCrop(options: CropOptions): Promise<void>;
    /** The crop on the current image, edges as fractions of the full frame. */
    crop(): Promise<CropInfo>;
    clearCrop(): Promise<void>;
    setMask(masked: boolean): Promise<void>;
    /**
     * Shift the capture times by `offset` — `'+05:30'`, `'-03:00'`, `'+0200'` —
     * the way Metadata → Adjust Timezone… does: the shift is added to what the
     * camera recorded, over the selection (the current image when nothing is
     * selected) unless `scope` says otherwise.
     */
    adjustTimezone(offset: string, scope?: ApplyScope): Promise<void>;
    /**
     * Edit → Copy (#812): the pictures as files on the clipboard, each with
     * its sidecars, to paste in Finder or Explorer — `indices`, or the
     * selection (the current picture when nothing is selected).
     */
    copyFiles(indices?: number[]): Promise<void>;
    /**
     * Each person (or animal) in the picture on screen and how sharp they are
     * beside the others, with the number Identify read on them (#810). `null`
     * until read: the call asks for the detection, so ask again.
     */
    peopleSharpness(): Promise<PeopleSharpnessInfo>;
    delete(indices?: number[]): Promise<void>;
}

declare class SelectionNamespace {
    private http;
    constructor(http: HttpClient);
    get(): Promise<SelectionInfo>;
    all(): Promise<void>;
    none(): Promise<void>;
    invert(): Promise<void>;
    tagged(): Promise<void>;
    untagged(): Promise<void>;
    set(indices: number[]): Promise<void>;
    add(indices: number[]): Promise<void>;
    remove(indices: number[]): Promise<void>;
    byCriteria(criteria: SelectionCriteria): Promise<void>;
    save(path: string): Promise<void>;
    load(path: string): Promise<void>;
}

/**
 * Thenable filter builder — chain methods and await the result.
 *
 * await app.filter.picks()
 * await app.filter.rated(3).picks()
 * await app.filter.raw().rated(4).picks()
 *
 * A chain changes only what it names: `filter.raw()` followed later by
 * `filter.picks()` shows raw picks. Start from `filter.clear()` for a filter
 * that is exactly the chain.
 */
declare class FilterBuilder implements PromiseLike<void> {
    private http;
    private options;
    constructor(http: HttpClient, initial?: FilterOptions);
    picks(): FilterBuilder;
    rejects(): FilterBuilder;
    unflagged(): FilterBuilder;
    rated(min: number): FilterBuilder;
    /** Tagged only (`true`), untagged only (`false`), or no tag filter (`'all'`). */
    tagged(value?: boolean | 'all'): FilterBuilder;
    raw(): FilterBuilder;
    jpeg(): FilterBuilder;
    /** Still pictures that are neither RAW nor JPEG: PNG, TIFF, HEIC … */
    other(): FilterBuilder;
    video(): FilterBuilder;
    /** Execute the filter. Called automatically when awaited. */
    apply(): Promise<void>;
    then<T = void, R = never>(onfulfilled?: ((value: void) => T | PromiseLike<T>) | null, onrejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<T | R>;
}
declare class FilterNamespace {
    private http;
    constructor(http: HttpClient);
    /** Set filter with raw options */
    set(options: FilterOptions): Promise<void>;
    /** Start building a filter chain */
    picks(): FilterBuilder;
    rejects(): FilterBuilder;
    unflagged(): FilterBuilder;
    rated(min: number): FilterBuilder;
    tagged(value?: boolean | 'all'): FilterBuilder;
    raw(): FilterBuilder;
    jpeg(): FilterBuilder;
    video(): FilterBuilder;
    /** Take every filter off, as the filter bar's clear button does. */
    clear(): Promise<void>;
}

declare class SortNamespace {
    private http;
    constructor(http: HttpClient);
    set(options: SortOptions): Promise<void>;
}

declare class BatchNamespace {
    private http;
    constructor(http: HttpClient);
    apply(options: BatchOptions): Promise<void>;
}

declare class ViewNamespace {
    private http;
    constructor(http: HttpClient);
    setMode(mode: ViewMode): Promise<void>;
    /**
     * Which picture a raw file is shown from.
     *
     * `embedded-jpeg` is the camera's own preview, on screen in milliseconds,
     * and the default because that is what culling is for. `native-raw` runs
     * our own conversion, which is what the develop view and every colour claim
     * are about — and what a screenshot of the product should show.
     */
    decodeSource(source: DecodeSource): Promise<void>;
    /** Which picture a raw file is shown from now. */
    currentDecodeSource(): Promise<DecodeSource>;
    /**
     * How long the last steps took to put their pictures on screen, and the
     * pace a held key steps at in RAW culling — what the culling speed check
     * reads (#745).
     */
    steps(): Promise<ViewSteps>;
}

declare class IptcNamespace {
    private http;
    constructor(http: HttpClient);
    /**
     * Suggest a caption and keywords for pictures (#577), from what can be
     * seen and what the app knows of them, by the model set in Settings → AI
     * Services. Nothing is written: save what you take with `update()`.
     */
    suggest(body?: CaptionSuggestBody): Promise<Suggested[]>;
    get(index?: number): Promise<IptcInfo>;
    update(patch: IptcPatch, index?: number): Promise<void>;
    copy(): Promise<void>;
    paste(): Promise<void>;
    findReplace(options: FindReplaceOptions): Promise<FindReplaceResult>;
    keywords(): Promise<string[]>;
}

declare class UiNamespace {
    private http;
    constructor(http: HttpClient);
    state(): Promise<UiStateInfo>;
    sidebar(enabled: boolean): Promise<void>;
    filmstrip(enabled: boolean): Promise<void>;
    folderTree(enabled: boolean): Promise<void>;
    /** Show or hide the develop panel. */
    developPanel(enabled: boolean): Promise<void>;
    /** Show or hide the Map panel: where the pictures were taken (#578). */
    map(enabled: boolean): Promise<void>;
    /** Show or hide the scopes (histogram, waveform) panel. */
    scopes(enabled: boolean): Promise<void>;
    fullscreen(enabled: boolean): Promise<void>;
    /** Presentation mode: fullscreen with every photographer overlay hidden, for showing a client. */
    presentation(enabled: boolean): Promise<void>;
    /**
     * Write a PNG of one of the app's windows to `path` (absolute). The file
     * appears after the window's next frame. Needs no screen-recording
     * permission.
     *
     * Only these two. A dialog is drawn inside the main window's frame and
     * cannot answer for its own pixels, so it can be opened with
     * {@link dialog} but not photographed.
     */
    /** The preview window: the develop view's picture in a window of its own. */
    previewWindow(enabled: boolean): Promise<void>;
    /** The Light Table: the grid in a window of its own. */
    lightTable(enabled: boolean): Promise<void>;
    /** The grid fullscreen here, the preview fullscreen on the second display; refused with one display. */
    dualFullscreen(enabled: boolean): Promise<void>;
    importPanel(enabled: boolean): Promise<void>;
    keywordsPanel(enabled: boolean): Promise<void>;
    /** The filter's columns above the grid (#665). */
    filterColumns(enabled: boolean): Promise<void>;
    /** Every panel out of the way, as Tab does; false brings the layout back. */
    panelsHidden(enabled: boolean): Promise<void>;
    workspaces(): Promise<WorkspaceList>;
    /** Save the panel layout as it is under `name`; one by that name is replaced. */
    saveWorkspace(name: string): Promise<WorkspaceList>;
    /** Lay the panels out as a workspace: a built-in one (`default`, `culling`, `review`, `metadata`) or a saved one. */
    applyWorkspace(name: string): Promise<void>;
    deleteWorkspace(name: string): Promise<WorkspaceList>;
    /** Pop a panel out to a window of its own, or dock it again. */
    popOut(name: PanelName, popped: boolean): Promise<void>;
    displays(): Promise<DisplayEntry[]>;
    /**
     * Place the main window, on a display (by id) or the one it is on. `size`
     * is its inner size in points, `[width, height]`, set first: the shape of a
     * video for {@link startRecording}.
     */
    arrangeWindow(arrange: WindowArrangement, display?: number, size?: [number, number]): Promise<void>;
    /**
     * Record the main window into `dir` (absolute, a folder of its own): one
     * JPEG per frame at `fps` (30 if left out) until {@link stopRecording}.
     * The frames are written off the UI thread, so the app moves as it would
     * unrecorded.
     */
    startRecording(dir: string, fps?: number): Promise<void>;
    /** Stop the recording, once every frame and `frames.txt` is on disk. */
    stopRecording(): Promise<RecordingSummary>;
    /**
     * Write a PNG of the main window to `path` (absolute) on its next frame.
     * With `panel`, only that docked panel, at the window's pixel density;
     * refused when the panel is not on screen. `window` may also be an open
     * dialog: the dialog cut out of the main window. One with a window of its
     * own (all but eventphotos, iptc and place_by_neighbours) only in a demo
     * build, which draws it inside the main window.
     */
    screenshot(window: 'main' | DialogName, path: string, panel?: PanelName): Promise<void>;
    /**
     * Every dockable panel: whether it is in the layout, and whether it is the
     * tab actually on screen in the slot it shares.
     */
    panels(): Promise<PanelInfo[]>;
    /**
     * Show a panel and bring it in front of whatever shares its slot.
     *
     * Making a panel visible is not the same as seeing it: the develop panel and
     * the metadata sidebar are two tabs in one slot, and only the front one is
     * on screen.
     */
    focusPanel(name: PanelName): Promise<void>;
    /** Show the Library view on Disks & Duplicates, on one of its tabs (#664). */
    libraryTab(name: LibraryTab): Promise<void>;
    /** Every dialog the API can open, and whether it is on screen. */
    dialogs(): Promise<DialogInfo[]>;
    /**
     * Open or close a dialog; Settings on one of its sections with `tab`.
     * {@link screenshot} photographs an open one by its name.
     */
    dialog(name: DialogName, open: boolean, tab?: SettingsSection): Promise<void>;
    /**
     * Zoom to the level a click on the picture goes to (`true`; `zoom_click_level`
     * in the settings, 1.0 = 100 %) or fit the whole frame in the window
     * (`false`), as the Z key does.
     */
    zoom(enabled: boolean): Promise<void>;
    /** The zoom level (screen pixels per picture pixel), the fit mode and the pan. */
    zoomLevel(): Promise<ZoomInfo>;
    /** Zoom to a level, or to a fit mode (`fit_screen`, `one_to_one`). */
    setZoomLevel(zoom: ZoomBody): Promise<void>;
    clipping(enabled: boolean): Promise<void>;
    focusPeaking(enabled: boolean): Promise<void>;
    shadowDetail(enabled: boolean): Promise<void>;
    highlightDetail(enabled: boolean): Promise<void>;
    channel(channel: Channel): Promise<void>;
    /** The composition guide over the picture (thirds, spiral …), and `turn` to face it the next way round. */
    compositionGuide(body: CompositionGuideBody): Promise<void>;
    /** Highlight where noise is, as an overlay. */
    noiseMap(enabled: boolean): Promise<void>;
    /** Zebra stripes over what is about to clip. */
    zebra(enabled: boolean): Promise<void>;
    /**
     * False colour: the picture in grey with the brightness zones painted over
     * it — purple under black, blue near it, green at middle grey, pink where
     * skin sits, yellow just under white, red at white.
     */
    falseColor(enabled: boolean): Promise<void>;
    /** The capture settings written over the picture. */
    exifOverlay(enabled: boolean): Promise<void>;
    /**
     * The focus box: the eyes, else the camera's focus point, else the
     * sharpest spot, magnified in a corner of the picture. Remembered.
     */
    focusBox(enabled: boolean): Promise<void>;
    /**
     * Read sharpness over the whole frame, the weakest part with something
     * in it counting — a landscape's soft corner shows (#811) — or, off, at
     * the camera's focus point. Every score is read again. Remembered.
     */
    landscapeSharpness(enabled: boolean): Promise<void>;
    /**
     * Outline each person (or animal) in the picture with how sharp they are
     * beside the sharpest, and their number when Identify read it (#810).
     * Remembered.
     */
    peopleSharpness(enabled: boolean): Promise<void>;
    /**
     * The global exposure aid, -4 to +4 EV.
     *
     * Applies to every image at once and is never written to a sidecar: it is a
     * way of looking at a dark shoot, not an edit to any frame in it.
     */
    exposureAid(ev: number): Promise<void>;
}

declare class SlideshowNamespace {
    private http;
    constructor(http: HttpClient);
    start(): Promise<void>;
    stop(): Promise<void>;
    setInterval(seconds: number): Promise<void>;
    autoAdvance(enabled: boolean): Promise<void>;
}

declare class CompareNamespace {
    private http;
    constructor(http: HttpClient);
    setAnchor(): Promise<void>;
    swap(): Promise<void>;
    setSplit(direction: CompareSplit): Promise<void>;
}

declare class SurveyNamespace {
    private http;
    constructor(http: HttpClient);
    /**
     * Pictures per Survey page, 2 to 4. It does not switch to Survey: in any
     * other view the count is kept for the next time Survey is entered.
     */
    setCount(count: number): Promise<void>;
}

declare class ExportNamespace {
    private http;
    constructor(http: HttpClient);
    /** Export the selection (or the picture on screen) with an export profile. */
    selected(options?: ExportOptions): Promise<void>;
    /** Export every visible picture with an export profile. */
    visible(options?: ExportOptions): Promise<void>;
    quickJpeg(options?: QuickJpegOptions): Promise<void>;
    /** The visible pictures as a printable contact-sheet PDF, in the background. */
    contactSheet(): Promise<void>;
    /** The visible pictures as a static HTML gallery, in the background. */
    htmlGallery(): Promise<void>;
    status(): Promise<ExportStatusInfo>;
}

declare class DenoiseNamespace {
    private http;
    constructor(http: HttpClient);
    send(): Promise<void>;
    flush(): Promise<void>;
    cancel(): Promise<void>;
    queue(): Promise<DenoiseQueueInfo>;
    removeFromQueue(index: number): Promise<void>;
    profiles(): Promise<DenoiseProfileInfo[]>;
    setProfile(name: string): Promise<void>;
    /** Every profile as stored, with every field (#607). */
    profileSetups(): Promise<SavedPresets>;
    /** Save a profile under its `name`; fields left out keep that one's values. */
    saveProfile(profile: Record<string, unknown> & {
        name: string;
    }): Promise<void>;
    deleteProfile(name: string): Promise<void>;
    /** Test Program: whether the profile's program is there and answers. */
    testProfile(name: string): Promise<void>;
    settings(): Promise<DenoiseSettingsInfo>;
    updateSettings(settings: DenoiseSettingsOptions): Promise<void>;
}

declare class UploadNamespace {
    private http;
    constructor(http: HttpClient);
    queue(indices?: number[]): Promise<void>;
    flush(): Promise<void>;
    /** Take one queued or failed file off the queue (its `index` in `queueStatus().items`). */
    removeItem(index: number): Promise<void>;
    /** Clear Completed: the sent files off the list. */
    clearCompleted(): Promise<void>;
    /** Retry Failed: every failed file queued again. */
    retryFailed(): Promise<void>;
    /** Send what is queued for one destination now. */
    flushDestination(index: number): Promise<void>;
    /** Clear Transferred: forget which pictures were sent where. */
    clearTransferred(): Promise<void>;
    queueStatus(): Promise<UploadQueueInfo>;
    destinations(): Promise<UploadDestinationInfo[]>;
    /** Send to `connection` (a connection that files can be written to), into `remotePath`. Returns the new list. */
    addDestination(connection: string, remotePath?: string): Promise<UploadDestinationInfo[]>;
    /** Change where destination `index` sends. Returns the new list. */
    updateDestination(index: number, change: UploadDestinationChange): Promise<UploadDestinationInfo[]>;
    /** Remove destination `index`, with what is queued for it. Returns the new list. */
    removeDestination(index: number): Promise<UploadDestinationInfo[]>;
}

/** What to do with a picture whose name is already in the folder it is moved to. */
type MoveConflictPolicy = 'overwrite' | 'skip';
/** Folders, and the folder tree's actions (#596). */
declare class FolderNamespace {
    private http;
    constructor(http: HttpClient);
    /** Open a folder, or a picture: its folder opens on it. */
    open(path: string): Promise<void>;
    /** Close the open folder. */
    close(): Promise<void>;
    /** Show a folder (or file) in Finder, Explorer on Windows. */
    reveal(path: string): Promise<void>;
    /** The pictures in a folder, by name — what Compare Folders lists. */
    pictures(path: string): Promise<PictureNames>;
    /** New Subfolder…: `name` is one folder name, no path. Returns the new folder's path. */
    createSubfolder(parent: string, name: string): Promise<string>;
    /** Rename…: an open folder, favourites and recent folders follow. Returns the new path. */
    rename(path: string, name: string): Promise<string>;
    /** Move to Trash…: it can be put back from the Trash. */
    trash(path: string): Promise<void>;
    /** Throw away the folder's cached previews; an open folder is read again. */
    clearCache(path: string): Promise<void>;
    /**
     * Move a folder into `destParent`. On one disk it is done when this
     * returns; across disks the copy has started, and the original is removed
     * only when every file has arrived.
     */
    move(path: string, destParent: string): Promise<void>;
    /**
     * Move pictures (rows; none is the current one) into a folder, as dropping
     * them on it does. With pictures of the same name there and no `onConflict`,
     * nothing moves and the call fails with 409 naming them.
     */
    movePictures(dest: string, indices?: number[], onConflict?: MoveConflictPolicy): Promise<void>;
    favorites(): Promise<string[]>;
    addFavorite(path: string): Promise<string[]>;
    removeFavorite(path: string): Promise<string[]>;
    /** Newest first. */
    recent(): Promise<string[]>;
    removeRecent(path: string): Promise<string[]>;
}

/**
 * What the window works on (#663): a folder, an album, a saved search or a
 * library search. Every action and the current-picture routes work on
 * whichever is open.
 */
declare class SourceNamespace {
    private http;
    constructor(http: HttpClient);
    /** What is open, and the folders its pictures are in. */
    get(): Promise<OpenSourceInfo>;
    /** In the open album's own order, put `files` before `before` (at the end without), as a drag in the grid does. */
    reorder(files: string[], before?: string): Promise<void>;
    /** Open it; refused with the reason when it has no pictures or does not exist. */
    open(source: Source): Promise<OpenSourceInfo>;
}

declare class FilesNamespace {
    private http;
    constructor(http: HttpClient);
    batchRename(options: BatchRenameOptions): Promise<void>;
    openInEditor(): Promise<void>;
    /** The programs on this machine the external-editor list can pick. */
    detectedPrograms(): Promise<DetectedProgram[]>;
    editors(): Promise<EditorInfo[]>;
    /** Show the current picture in Finder (Explorer on Windows). */
    revealInFinder(): Promise<void>;
    /** Convert RAWs (rows; none is the current one) to a DNG beside them, in the background. */
    convertToDng(indices?: number[]): Promise<void>;
    /** Lossless JPEG tools: mirror, trim the trailer, extract a RAW's preview, set DPI. */
    jpegTool(options: JpegToolBody): Promise<void>;
    /** Move every rejected picture into the quarantine subfolder; `undoQuarantine()` puts them back. */
    quarantineRejects(): Promise<void>;
    /** Put back the last quarantined batch. */
    undoQuarantine(): Promise<void>;
    /** Start the import the dialog would; follow it with `importJobs()` or `import_finished`. */
    importFrom(options: ImportOptions): Promise<ImportStarted>;
    /** The camera cards mounted now, as the Import dialog finds them; `mount_point` is a `source_path`. */
    cards(): Promise<CardInfo[]>;
    /** Every import this session, running and finished, as the import panel shows them. */
    importJobs(): Promise<ImportJobInfo[]>;
    /** Eject the card a finished import copied from. */
    ejectImportSource(job: number): Promise<void>;
    /** Take a finished import off the list. */
    dismissImport(job: number): Promise<void>;
    /** The batch jobs the progress panel shows: auto-crop, denoise, a folder moving to another disk. */
    batchJobs(): Promise<BatchJobInfo[]>;
    /** Stop a batch job; a folder move deletes nothing it has not finished copying. */
    cancelBatchJob(kind: BatchJobInfo['kind']): Promise<void>;
    /** Cancel a running import: what was copied stays, the card is not erased. */
    cancelImport(job: number): Promise<void>;
}

/** The Google account deliveries use. Never carries a token. */
interface DriveAccountInfo {
    signed_in: boolean;
    email: string;
    /** `.../auth/drive.file` cannot reach a folder this app did not create. */
    scope: string;
    client_id: string;
    /** A sign-in is open in a browser and has not come back yet. */
    pending: boolean;
}
interface DriveSignInOptions {
    /** OAuth client ID of a Desktop app client. Omit to reuse the stored one. */
    clientId?: string;
    clientSecret?: string;
    /** Defaults to per-file access. Pass the full `.../auth/drive` when
     *  delivering into a folder this app did not create. */
    scope?: string;
    /** Seconds to wait for the browser. Defaults to 300. */
    timeoutSecs?: number;
    /** Called with the consent URL, for a caller who is not at that browser. */
    onAuthUrl?: (url: string) => void;
}
/**
 * Signing in to Google Drive (#219).
 *
 * The browser opens on the machine running the app — that is where the
 * loopback redirect lands. A script driving this from elsewhere gets the URL
 * through `onAuthUrl` and hands it to whoever is sitting there.
 */
declare class GoogleDriveNamespace {
    private readonly http;
    constructor(http: HttpClient);
    /** Who this machine delivers as, if anyone. */
    account(): Promise<DriveAccountInfo>;
    /** Forget the refresh token. */
    signOut(): Promise<DriveAccountInfo>;
    /** Open the consent screen and wait for it to be answered. */
    signIn(options?: DriveSignInOptions): Promise<DriveAccountInfo>;
}

/** How one part of a mask combines with the parts before it. */
type MaskMode = 'add' | 'subtract' | 'intersect';
/** One freehand brush stroke, in normalised image coordinates. */
interface BrushStroke {
    /** Points along the stroke, each `[x, y]` in 0..1. */
    points: [number, number][];
    /** Radius as a fraction of the image's longer side. */
    radius: number;
    /** Soft edge: 0 = hard, 1 = the whole radius fades. */
    feather?: number;
    /** Strength this stroke lays down, 0..1. Defaults to 1. */
    flow?: number;
    /** An erase stroke takes coverage away instead of adding it. */
    erase?: boolean;
}
type MaskGeometry = {
    kind: 'linear';
    x0: number;
    y0: number;
    x1: number;
    y1: number;
} | {
    kind: 'radial';
    cx: number;
    cy: number;
    rx: number;
    ry: number;
    feather: number;
    invert: boolean;
    /** Turns the ellipse clockwise, in degrees. Defaults to 0. */
    angle?: number;
} | {
    kind: 'brush';
    strokes: BrushStroke[];
}
/** A coverage map made from the picture (a person's parts, #580): a grey PNG, base64. */
 | {
    kind: 'matte';
    png: string;
} | {
    kind: 'luminance';
    low: number;
    high: number;
    feather?: number;
} | {
    kind: 'color_range';
    hue: number;
    hue_tolerance?: number;
    sat_min?: number;
    sat_max?: number;
    lum_min?: number;
    lum_max?: number;
    feather?: number;
}
/** Every pixel: a correction over the whole picture, after the panel's own. */
 | {
    kind: 'frame';
}
/** The area another mask covers, by its name — one qualifier for several masks. */
 | {
    kind: 'key';
    mask: string;
};
interface MaskComponent {
    geometry: MaskGeometry;
    /** Defaults to `add`. Ignored on the first part, which starts the mask. */
    mode?: MaskMode;
}
/** What a mask does where it covers. All values default to 0. */
interface MaskValues {
    exposure_ev?: number;
    contrast?: number;
    highlights?: number;
    shadows?: number;
    whites?: number;
    blacks?: number;
    /** High key (+: the tones past a threshold go to white, the dark ones stay) or low key (−: to black, the light ones stay), -100..+100. */
    key?: number;
    wb_temp_shift?: number;
    wb_tint_shift?: number;
    saturation?: number;
    vibrance?: number;
    /** Brightens from the shadows up through the midtones, the top untouched (-100..+100). */
    lift?: number;
    /** Exposure + brightens without clipping what was not clipped. */
    exposure_protect?: boolean;
    /**
     * Detail. These read the pixels around each one, so a mask using any of
     * them costs a second pass over the frame — masks that leave them at zero
     * cost nothing.
     */
    clarity?: number;
    texture?: number;
    dehaze?: number;
    nr_luminance?: number;
    nr_color?: number;
    /** The colour mixer, on the pixel as it is shown. */
    hsl?: HslZones;
    /** The tone curve, after the mixer. */
    curve?: ToneCurve;
    /** Colour grading, last. */
    grading?: Grading;
}
/**
 * The colour mixer's eight zones, in this order: red, orange, yellow, green,
 * cyan, blue, purple, magenta. Hue in degrees, saturation and luminance in
 * percent; each array defaults to zeros.
 */
interface HslZones {
    hue?: number[];
    sat?: number[];
    lum?: number[];
}
/** The tone curve: four region sliders (-100..+100) and point curves, `[x, y]` objects in 0..1. */
interface ToneCurve {
    highlights?: number;
    lights?: number;
    darks?: number;
    shadows?: number;
    points?: CurvePoint[];
    points_r?: CurvePoint[];
    points_g?: CurvePoint[];
    points_b?: CurvePoint[];
}
/** A point of a mask's curve, 0..1 both ways. */
interface CurvePoint {
    x: number;
    y: number;
}
/** Colour grading: a wheel per tonal range (hue in degrees, saturation 0..100, luminance -100..+100), and blending and balance (-100..+100). */
interface Grading {
    shadow_hue?: number;
    shadow_sat?: number;
    shadow_lum?: number;
    mid_hue?: number;
    mid_sat?: number;
    mid_lum?: number;
    high_hue?: number;
    high_sat?: number;
    high_lum?: number;
    blend?: number;
    balance?: number;
}
/** One local adjustment: the parts it covers, and what it does there. */
interface LocalAdjustment {
    /** Shown in the mask list. Empty names after the first part. */
    name?: string;
    components: MaskComponent[];
    values?: MaskValues;
    /** Flip the finished coverage. */
    invert?: boolean;
    /** `false` leaves the mask in the list and out of the picture. Defaults to `true`. */
    enabled?: boolean;
    /** The saved look the mask came from, if any. */
    look?: LookRef;
    /** How the mask joins the chain. Defaults to `{ kind: 'serial' }`. */
    mix?: Mix;
    /** The edge of the mask's found and painted parts (a subject, sky or person mask, a brush). */
    edge?: MaskEdge;
}
/**
 * The edge of a mask's found and painted parts, as Lightroom's Feather and
 * Edge on an AI mask. 100 reaches 2 % of the picture's long side.
 */
interface MaskEdge {
    /** 0..100: how far the edge is softened. */
    feather?: number;
    /** −100..100: the edge moved in (shrinking the mask) or out (growing it). */
    shift?: number;
}
/** Which saved look a mask came from. A linked one follows the look when it is saved again. */
interface LookRef {
    id: string;
    name: string;
    version: number;
    /** Defaults to `true`. */
    linked?: boolean;
}
/** How a layer is laid over what is under it. */
type Blend = 'normal' | 'screen' | 'multiply' | 'overlay' | 'soft_light' | 'lighten' | 'darken' | 'add';
/**
 * How a mask joins the chain. Masks next to each other with the same
 * parallel or layer group make one block: each sees the picture as it was
 * before the block. Parallel changes add; layers are laid over each other.
 */
type Mix = {
    kind: 'serial';
} | {
    kind: 'parallel';
    group: number;
} | {
    kind: 'layer';
    group: number;
    blend?: Blend;
};
/**
 * Develop adjustments. Every field is optional on write — a patch changes
 * only the fields it names — and every field is present on read.
 */
interface DevelopPatch {
    wb_temp_shift?: number;
    wb_tint_shift?: number;
    exposure_ev?: number;
    /** Exposure + brightens without clipping what was not clipped: the top rolls off towards white. */
    exposure_protect?: boolean;
    /** Brightens from the shadows up through the midtones, the top untouched (-100..+100). */
    lift?: number;
    blacks?: number;
    whites?: number;
    /** High key (+) or low key (−), -100..+100. */
    key?: number;
    shadows?: number;
    highlights?: number;
    saturation?: number;
    contrast?: number;
    vibrance?: number;
    clarity?: number;
    texture?: number;
    dehaze?: number;
    bw?: boolean;
    cg_shadow_hue?: number;
    cg_shadow_sat?: number;
    cg_shadow_lum?: number;
    cg_mid_hue?: number;
    cg_mid_sat?: number;
    cg_mid_lum?: number;
    cg_high_hue?: number;
    cg_high_sat?: number;
    cg_high_lum?: number;
    cg_blend?: number;
    cg_balance?: number;
    /** The global wheel: tints every tone alike, as Lightroom's Global. */
    cg_global_hue?: number;
    cg_global_sat?: number;
    cg_global_lum?: number;
    pcv_amount?: number;
    pcv_midpoint?: number;
    pcv_feather?: number;
    pcv_roundness?: number;
    /** How much of the highlights a darkening vignette spares, 0–100. */
    pcv_highlights?: number;
    /** How the vignette darkens or lightens, as Lightroom's Style. */
    pcv_style?: 'highlight-priority' | 'color-priority' | 'paint-overlay';
    grain_amount?: number;
    grain_size?: number;
    grain_roughness?: number;
    tc_highlights?: number;
    tc_lights?: number;
    tc_darks?: number;
    tc_shadows?: number;
    /**
     * Where the curve's regions part, as offsets from 25, 50 and 75 (%):
     * Lightroom's split points.
     */
    tc_shadow_split?: number;
    tc_midtone_split?: number;
    tc_highlight_split?: number;
    tc_points?: [number, number][];
    tc_points_r?: [number, number][];
    tc_points_g?: [number, number][];
    tc_points_b?: [number, number][];
    /**
     * Which lens correction the file and Adobe's profile give: `as-shot` (what
     * the camera applied to its own JPEG), `all`, or `off`.
     */
    lens_mode?: 'as-shot' | 'all' | 'off';
    /**
     * The profile the picture is drawn with: `default` (Settings → Camera
     * profiles), `photosuite`, or one of Adobe's by the name Lightroom gives
     * it (`Adobe Standard v2`, `Adobe Standard v2 + Adobe Color`), or a
     * creative look at an amount in percent (`Adobe Standard + Vintage 01 @ 63`).
     * `client.develop.profiles()` lists the picture's.
     */
    profile?: string;
    lens_vignette?: number;
    ca_amount?: number;
    defringe_purple?: number;
    defringe_green?: number;
    distortion?: number;
    /**
     * Perspective. `tf_scale` is a percentage whose neutral value is 100, not
     * 0 — it pushes the empty corners a correction leaves behind out of the
     * frame. To turn the picture, set the crop's `angle` instead.
     */
    tf_vertical?: number;
    tf_horizontal?: number;
    tf_aspect?: number;
    tf_scale?: number;
    tf_offset_x?: number;
    tf_offset_y?: number;
    sharpen_amount?: number;
    sharpen_masking?: number;
    /** Sharpening radius in pixels, 0.5-3.0. Neutral is 1.0, not 0. */
    sharpen_radius?: number;
    /** How much of the high-pass survives in flat areas. Neutral is 25. */
    sharpen_detail?: number;
    /** Where the line between noise and edge falls. Neutral is 50. */
    nr_lum_detail?: number;
    /** How far the chroma blur reaches. Neutral is 50. */
    nr_color_smoothness?: number;
    nr_luminance?: number;
    nr_color?: number;
    /** AI noise reduction, 0-100, run before exposure and tone; the first change on a picture takes seconds. */
    ai_denoise?: number;
    hsl_hue_red?: number;
    hsl_hue_orange?: number;
    hsl_hue_yellow?: number;
    hsl_hue_green?: number;
    hsl_hue_cyan?: number;
    hsl_hue_blue?: number;
    hsl_hue_purple?: number;
    hsl_hue_magenta?: number;
    hsl_sat_red?: number;
    hsl_sat_orange?: number;
    hsl_sat_yellow?: number;
    hsl_sat_green?: number;
    hsl_sat_cyan?: number;
    hsl_sat_blue?: number;
    hsl_sat_purple?: number;
    hsl_sat_magenta?: number;
    hsl_lum_red?: number;
    hsl_lum_orange?: number;
    hsl_lum_yellow?: number;
    hsl_lum_green?: number;
    hsl_lum_cyan?: number;
    hsl_lum_blue?: number;
    hsl_lum_purple?: number;
    hsl_lum_magenta?: number;
    /** Local adjustments. An empty array clears them. */
    masks?: LocalAdjustment[];
    /** Blemishes removed by copying from elsewhere. An empty array clears them. */
    spots?: SpotFix[];
    /** The sections switched off: their values kept, left out of the picture. `[]` switches every section on. */
    off?: DevelopGroup[];
}
/** One blemish replaced by a patch taken from elsewhere in the same frame. */
interface SpotFix {
    /** The blemish, in normalised image coordinates. */
    x: number;
    y: number;
    /** Where the replacement comes from, in normalised image coordinates. */
    from_x: number;
    from_y: number;
    /** Radius as a fraction of the image's longer side. */
    radius: number;
    /** Soft edge: 0 = hard, 1 = the whole radius fades. Defaults to 0.5. */
    feather?: number;
    /**
     * Match the patch to the brightness and colour it lands in. Defaults to
     * true; false clones the source as it is.
     */
    heal?: boolean;
    /** How much of the patch is laid down, 0..1. Defaults to 1. */
    opacity?: number;
}
/**
 * The develop tools that are armed, and whether the mask overlay is shown.
 *
 * These are modes rather than values: they change what a click on the picture
 * does.
 */
interface DevelopTools {
    /** Wash the selected mask's coverage over the picture in red. */
    mask_overlay: boolean;
    /** While the selected mask's sliders move, draw its edge as a red line. */
    mask_edge: boolean;
    /** Show the masks as a graph rather than a list. */
    mask_graph: boolean;
    /** Solo mode: one section of the develop panel open at a time. */
    solo_sections: boolean;
    /** Dragging paints into the selected brush mask. */
    brush: boolean;
    /** Clicking places a spot fix. */
    spot: boolean;
    /** The untouched rendering beside the edited one, split down the middle. */
    before_after: boolean;
    /**
     * What the next brush stroke is laid down with. A script that writes
     * strokes gives each one its own values; these are what a hand on the
     * picture uses.
     */
    brush_size: number;
    brush_feather: number;
    brush_flow: number;
    brush_erase: boolean;
    /** What the next spot fix is placed with. */
    spot_size: number;
    spot_feather: number;
    spot_opacity: number;
    spot_heal: boolean;
}
/** A group of develop settings, for pasting part of an edit. */
type DevelopGroup = 'light' | 'color' | 'presence' | 'detail' | 'curve' | 'mixer' | 'grading' | 'transform' | 'lens' | 'effects' | 'spots' | 'masks';
interface DevelopPreset {
    name: string;
    values: DevelopPatch;
    /** The group it is listed under; left out for none. */
    group?: string;
    /** The develop schema its values mean what they do in; older ones are brought up when read. */
    develop_version?: number;
}
/** A develop that came from Lightroom's `crs:` settings. */
interface LightroomImportInfo {
    /** One line per Lightroom setting photosuite could not carry. */
    not_carried: string[];
}
/** 256 levels per channel, 0..1.25 of the output range (the top fifth is past white). */
interface ScopeHistogram {
    red: number[];
    green: number[];
    blue: number[];
    luma: number[];
}
/** One profile a picture can be drawn with. */
interface ProfileChoice {
    /** What `profile` in the develop values takes. */
    name: string;
    /** What Lightroom's profile browser calls it. */
    label: string;
}
/** GET /api/images/current/profiles: what the picture on screen can be drawn with. */
interface ProfilesInfo {
    /** The one it is drawn with: `default`, or a name from `profiles`. */
    profile: string;
    /** `photosuite` first, then Adobe's for the camera where Lightroom, Camera Raw or DNG Converter is installed; empty for a picture that is not a RAW. */
    profiles: ProfileChoice[];
}
/** GET /api/images/current/scopes: what the Scopes panel draws. Grids are 64 × 64, row 0 at the top. */
interface ScopesInfo {
    /** The picture the counts are of. */
    path: string;
    /** The output space they are in: `srgb`, `display_p3` or `adobe_rgb`. */
    space: string;
    /** From the develop chain, unclipped (`true`), or from the pixels on screen. */
    develop: boolean;
    /** Pixels counted (every second in each direction). */
    samples: number;
    /** The share the output transform has to bring inside the space. */
    outside_share: number;
    histogram: ScopeHistogram;
    /** Luma per column, levels 0..1.25 bottom to top. */
    waveform: number[][];
    /** Cb across, Cr down. */
    vectorscope: number[][];
    /** CIE 1931 x across 0..0.8, y down 0.9..0. */
    chromaticity: number[][];
}
/** One saved look: a group of masks, applied on top of a picture's own. */
interface LookInfo {
    /** Stays the same when the look is saved again. */
    id: string;
    name: string;
    /** Goes up each time the look is saved; a picture with an older one follows. */
    version: number;
    /** How many masks the look puts on a picture. */
    masks: number;
}
/** GET /api/images/current/develop/selected-mask */
interface SelectedMaskInfo {
    /** 0-based in the picture's masks; null without masks. */
    index: number | null;
    /** As the panel's list names it. */
    name: string | null;
    /** Whether the develop panel edits this node; false while it edits the picture's own develop. */
    panel_edits_it: boolean;
}
interface DevelopHistoryEntry {
    index: number;
    label: string;
    /** When the edit was committed, in Unix epoch seconds. */
    at: number;
}
/**
 * Develop: the adjustments on the current image, the presets and snapshots
 * around them, and the history that lets a script step back.
 */
/** What a drag on the scopes selected. */
interface ScopeMaskBody {
    kind: 'colour_range' | 'color_range' | 'luminance';
    /** colour_range: the hue in degrees (0-360). */
    hue?: number | null;
    /** colour_range: how far either side of `hue`, in degrees. */
    tolerance?: number | null;
    /** colour_range: the saturations it spans, 0-1 (default 0 and 1). */
    sat_min?: number | null;
    sat_max?: number | null;
    /** luminance: the span of levels, 0-1 of the output range. */
    low?: number | null;
    high?: number | null;
}
declare class DevelopNamespace {
    private http;
    constructor(http: HttpClient);
    /** Every adjustment on the current image, with all fields present. */
    get(): Promise<DevelopPatch>;
    /** Adjustments on the image at `index`. */
    getAt(index: number): Promise<DevelopPatch>;
    /**
     * What Lightroom's settings could not carry into the current image's
     * develop; `undefined` when the develop is not Lightroom's (none in the
     * XMP, or edited here since).
     */
    lightroom(): Promise<LightroomImportInfo | undefined>;
    /** Change only the fields given. */
    set(values: DevelopPatch): Promise<void>;
    /** Change only the fields given, on the image at `index`. */
    setAt(index: number, values: DevelopPatch): Promise<void>;
    /** Back to as-shot. */
    reset(): Promise<void>;
    /** Replace the mask list. Pass `[]` to remove every mask. */
    setMasks(masks: LocalAdjustment[]): Promise<void>;
    /** Replace the spot list. Pass `[]` to put every blemish back. */
    setSpots(spots: SpotFix[]): Promise<void>;
    /**
     * Mask the subject at its outline, with the subject model (#775): every
     * subject that stands out, down to the gaps between a bird's feathers.
     *
     * The call starts the search and returns; the mask lands on the picture a
     * few seconds later (the `develop_changed` event). The first time, the model
     * (490 MB) is downloaded before. Without it, Vision's outline (macOS 14+),
     * else the detector's box narrowed to the subject's tones.
     */
    addSubjectMask(): Promise<void>;
    /**
     * A mask of people's parts (#580): face skin, body skin, eyes, iris,
     * brows, lips, teeth, hair, clothes, of one person (from 1, left to
     * right) or everyone. The people are found (macOS 14+) on the first ask.
     * Eyes, irises, teeth and hair are put at the picture's own edges
     * by the edge model (#777) and land a few seconds later; without the
     * model they land as found, and it is downloaded (455 MB).
     */
    addPeopleMask(body: PeopleMaskBody): Promise<void>;
    /** The people found in the picture on screen; null until a people mask has looked. */
    people(): Promise<PeopleInfo>;
    /**
     * Mask the sky to the horizon, clouds and all, with the sky model (#775).
     *
     * The call starts the search and returns; the mask lands on the picture a
     * moment later (the `develop_changed` event). The first time, the model
     * (176 MB) is downloaded before. Without it, the top of the frame narrowed
     * to its colour and tones.
     */
    addSkyMask(): Promise<void>;
    /**
     * Mask everything but the subject, as Lightroom's Select Background
     * (#790): the subject model's mask turned inside out.
     *
     * The call starts the search and returns; the mask lands on the picture a
     * moment later (the `develop_changed` event). The first time, the model
     * (490 MB) is downloaded before.
     */
    addBackgroundMask(): Promise<void>;
    /**
     * Mask the thing under rough strokes, as Lightroom's Objects (#789): a
     * mask named Object of the strokes is added, and its brush part becomes
     * the thing's outline a few seconds later (the `develop_changed` event).
     */
    addObjectMask(body: ObjectMaskBody): Promise<void>;
    /**
     * A mask of a landscape's parts, as Lightroom's Landscape (#792): sky,
     * water, vegetation, mountains, architecture, natural or artificial
     * ground, any of them together. The parts are found on the first ask, a
     * few seconds; the mask lands then (the `develop_changed` event).
     */
    addLandscapeMask(body: LandscapeMaskBody): Promise<void>;
    /**
     * A mask of what a drag on the scopes selects (#600): a hue range on the
     * vectorscope, or a span of levels on the waveform.
     */
    addScopeMask(selection: ScopeMaskBody): Promise<void>;
    /** Append one mask, leaving the existing ones alone. */
    addMask(mask: LocalAdjustment): Promise<void>;
    /** Which develop tools are armed. */
    tools(): Promise<DevelopTools>;
    /** Arm or disarm develop tools. Fields left out are untouched. */
    setTools(tools: Partial<DevelopTools>): Promise<DevelopTools>;
    /** The same as `client.undo()`: the last step of any kind (#709). */
    undo(): Promise<void>;
    /** The same as `client.redo()`. */
    redo(): Promise<void>;
    copy(): Promise<void>;
    /**
     * Paste the copied settings onto the current image, or onto the whole
     * selection when more than one image is selected.
     *
     * `only` narrows it to those groups and leaves the rest of each target
     * alone — the white balance from one frame and nothing else, across a
     * shoot. An unknown group name is refused rather than ignored.
     */
    paste(only?: DevelopGroup[]): Promise<void>;
    /**
     * Let the app pick a starting point from the picture itself: exposure,
     * highlights, shadows and the black point, each by a bounded amount.
     *
     * White balance is deliberately not included — see {@link autoWhiteBalance}.
     */
    auto(): Promise<void>;
    /**
     * Nudge the white balance toward neutral, for a frame where the camera got
     * the light wrong.
     *
     * Separate from {@link auto}, and bounded, because the illuminant is a
     * guess in a way the histogram is not: no measurement can tell green light
     * on a grey wall from white light on a green one. Throws when the picture
     * holds nothing neutral enough to measure, which is the honest answer for a
     * frame that is all one colour.
     */
    autoWhiteBalance(): Promise<void>;
    /** Put a section of the panel back to neutral, as its header's reset does: one undo step. */
    resetSection(group: DevelopGroup): Promise<void>;
    /** Switch a section on, or off: its values are kept and left out of the picture. */
    setSectionOn(group: DevelopGroup, on: boolean): Promise<void>;
    presets(): Promise<DevelopPreset[]>;
    /**
     * Save a preset under `name`: `values` when given (as `presets()` lists
     * them), else the current image's adjustments.
     */
    savePreset(name: string, values?: DevelopPatch, group?: string): Promise<void>;
    applyPreset(name: string): Promise<void>;
    /** List the preset `name` under `group`; `''` for no group. */
    setPresetGroup(name: string, group: string): Promise<void>;
    deletePreset(name: string): Promise<void>;
    /**
     * What the develop view shows of the current picture's masks: all of them,
     * the picture as it leaves mask `index` (`up_to`), or that mask alone
     * (`solo`). A way of looking; nothing is written. The scopes follow it.
     */
    nodeView(view: 'whole' | 'up_to' | 'solo', index?: number): Promise<void>;
    /**
     * The mask the develop panel edits: its handles are on the picture, Show
     * what it covers washes it, and a brush stroke paints into it.
     */
    selectedMask(): Promise<SelectedMaskInfo>;
    /** Make mask `index` (0-based) the one the panel edits; `null` makes the panel edit the picture's own develop. */
    selectMask(index: number | null): Promise<void>;
    /**
     * Match the selected pictures (or the current one) to picture `reference`
     * (its index in the image list): each gets a whole-frame mask that brings
     * its exposure, contrast, saturation and colour to the reference's, added
     * when it has been worked out.
     */
    matchTo(reference: number): Promise<void>;
    /** Saved looks: groups of masks a picture can carry on top of its own. */
    looks(): Promise<LookInfo[]>;
    /**
     * Save the current picture's masks as the look `name`. A look of that name
     * gets its next version, and every picture in the open folder that has it
     * follows; other pictures follow when they are next opened.
     */
    saveLook(name: string): Promise<void>;
    /** Put the look's masks on the current picture, after its own, following the look. */
    applyLook(name: string): Promise<void>;
    /** Delete a look from the library. Pictures keep the masks they have. */
    deleteLook(name: string): Promise<void>;
    /** Keep the look's masks on the current picture as its own: they stop following the look. */
    unlinkLook(name: string): Promise<void>;
    /**
     * The scopes of the current picture — histogram, waveform, vectorscope and
     * chromaticity, and how much of it the output has to bring inside the
     * space. Needs the Scopes panel shown (`client.ui.scopes(true)`).
     */
    scopes(): Promise<ScopesInfo>;
    /**
     * The profiles the current picture can be drawn with, and the one it is.
     * Set one with `client.develop.set({ profile })`.
     */
    profiles(): Promise<ProfilesInfo>;
    history(): Promise<DevelopHistoryEntry[]>;
    /** Go back to a state the history recorded. */
    jumpTo(index: number): Promise<void>;
    snapshots(): Promise<string[]>;
    saveSnapshot(name: string): Promise<void>;
    restoreSnapshot(name: string): Promise<void>;
    deleteSnapshot(name: string): Promise<void>;
}
/** A part of a person, for a people mask (#580). */
type PeoplePart = 'face_skin' | 'body_skin' | 'eyes' | 'iris' | 'brows' | 'lips' | 'teeth' | 'hair' | 'facial_hair' | 'clothes' | 'whole';
/** POST /api/images/current/develop/people-mask */
interface PeopleMaskBody {
    /** From 1, left to right; left out, everyone. */
    person?: number | null;
    parts: PeoplePart[];
}
type LandscapePart = 'sky' | 'water' | 'vegetation' | 'mountains' | 'architecture' | 'natural_ground' | 'artificial_ground';
interface LandscapeMaskBody {
    parts: LandscapePart[];
}
interface ObjectMaskBody {
    strokes: ObjectStroke[];
}
/** One stroke over the thing. */
interface ObjectStroke {
    /** The dabs' centres, 0..1 of the picture as shown: `[x, y]`. */
    points: [number, number][];
    /** The brush's radius, a share of the picture's long side. */
    radius: number;
}
/** One person in the picture on screen. */
interface PersonInfo {
    /** From 0, left to right. */
    index: number;
    /** Normalised to the picture as shown: x, y, width, height. */
    rect: number[];
    has_face: boolean;
}
/** GET /api/images/current/people */
interface PeopleInfo {
    people?: PersonInfo[] | null;
}

/**
 * Looks learned from your own RAWs for cameras photosuite has no profile
 * for (#446). The app learns them in the background from each picture's
 * camera JPEG, and uses one only when it matches that JPEG in every colour
 * region. Results arrive as `camera_look_learned` / `camera_look_refused`.
 */
declare class CameraLooksNamespace {
    private http;
    constructor(http: HttpClient);
    /** What was learned, what was refused and why, and what is being learned now. */
    list(): Promise<CameraLooksInfo>;
    /** Learn a body's look again from its pictures in the open folder. */
    learn(body: CameraLookBody): Promise<void>;
    /** Forget a learned look; its RAWs render without it until it is learned again. */
    forget(body: CameraLookBody): Promise<void>;
}

/** A step of develop, in the order it runs them (#467). */
type RawStep = 'sensor' | 'levels' | 'filter' | 'white_balance' | 'demosaic' | 'highlights' | 'color' | 'developed';
/** A rectangle of sensor photosites. */
interface RawStepsArea {
    left: number;
    top: number;
    width: number;
    height: number;
}
/** What the RAW Steps view shows, and whether the current picture is read. */
interface RawStepsInfo {
    step: RawStep;
    /** Linear values shown as they are, without the display gamma. */
    linear: boolean;
    steps: RawStep[];
    /** The sensor data is read: the picture can be drawn and probed. */
    ready: boolean;
    /** Why the current picture has no steps (not a RAW, or already demosaiced). */
    error: string | null;
    sensor: RawStepsArea | null;
    /** The part of the plane develop keeps. */
    imageArea: RawStepsArea | null;
    /** Screen pixels per photosite; null while the whole plane is fitted. */
    zoom: number | null;
    /** The photosite in the middle of the view, [x, y]. */
    center: [number, number];
}
/** What to change in the RAW Steps view; anything left out stays. */
interface RawStepsBody {
    step?: RawStep;
    linear?: boolean;
    /** Screen pixels per photosite; from 56 each photosite carries its number. */
    zoom?: number;
    /** Fit the whole plane (wins over `zoom`). */
    fit?: boolean;
    center?: [number, number];
}
/** One photosite of the current picture through every step. */
interface RawStepsProbe {
    x: number;
    y: number;
    filter: 'red' | 'green' | 'blue';
    /** The number in the file. */
    stored: number;
    black: number;
    white: number;
    /** What the sensor step divides a stored number by. */
    fullScale: number;
    /** The white-balance gain on its channel. */
    gain: number;
    /** The colours a clipped photosite reached: 'r', 'g', 'b'. */
    clipped: string[];
    inImage: boolean;
    /** Linear RGB after each step but `developed`. */
    values: Record<Exclude<RawStep, 'developed'>, [number, number, number]>;
    /** The finished picture there, sRGB 0..255, once it has been developed. */
    developed: [number, number, number] | null;
}
declare class RawStepsNamespace {
    private http;
    constructor(http: HttpClient);
    /** The step shown, and whether the current picture's sensor data is read. */
    get(): Promise<RawStepsInfo>;
    /** Show another step, the linear values, or another part of the sensor. */
    set(body: RawStepsBody): Promise<RawStepsInfo>;
    /**
     * The photosite at (x, y) of the current picture through every step. The
     * RAW Steps view reads the sensor data: switch to it (`view.setMode('raw_steps')`)
     * and wait for `ready` first.
     */
    probe(x: number, y: number): Promise<RawStepsProbe>;
}

type Preset = Record<string, unknown> & {
    name: string;
};
/**
 * The setups saved in the app's config folder (#593): export profiles,
 * import presets, IPTC templates, the keyword tree, key bindings, camera
 * destinations, upload settings, colour label names and the whole settings
 * bundle — what Settings, Export and Import save, so a script can set up a
 * machine without anyone clicking through them.
 *
 * Saving a preset replaces the one with its name; fields left out keep that
 * one's values (or the defaults for a new one). Which preset the dialogs
 * open on is left alone.
 */
declare class SetupsNamespace {
    private http;
    constructor(http: HttpClient);
    exportProfiles(): Promise<SavedPresets>;
    saveExportProfile(profile: Preset): Promise<void>;
    deleteExportProfile(name: string): Promise<void>;
    importPresets(): Promise<SavedPresets>;
    saveImportPreset(preset: Preset): Promise<void>;
    deleteImportPreset(name: string): Promise<void>;
    iptcTemplates(): Promise<SavedPresets>;
    saveIptcTemplate(template: Preset): Promise<void>;
    deleteIptcTemplate(name: string): Promise<void>;
    /** The keyword tree the Keywords panel shows: `{roots: [{name, children}]}`. */
    keywordTree(): Promise<Record<string, unknown>>;
    setKeywordTree(tree: Record<string, unknown>): Promise<void>;
    keyBindings(): Promise<BindingInfo[]>;
    /**
     * Bind a key action ("Cmd+Shift+N", "Space"), or unbind it with `null`.
     * A combination another action has is refused (409), as Settings refuses it.
     */
    bindKey(action: string, combo: string | null): Promise<BindingInfo[]>;
    resetKeyBindings(): Promise<BindingInfo[]>;
    cameraDestinations(): Promise<Record<string, string>>;
    setCameraDestinations(destinations: Record<string, string>): Promise<void>;
    /** How uploads are sent: RAW+JPEG, IPTC on upload, a saved copy. */
    uploadSettings(): Promise<Record<string, unknown>>;
    updateUploadSettings(patch: Record<string, unknown>): Promise<Record<string, unknown>>;
    /** What each colour label is called: `{red, yellow, green, …}`. */
    labelNames(): Promise<Record<string, string>>;
    renameLabels(names: Record<string, string>): Promise<Record<string, string>>;
    /** Everything Settings → Export Settings… writes. */
    settingsBundle(): Promise<Record<string, unknown>>;
    /** Settings → Import Settings…: every setup in the bundle replaces the saved one. */
    importSettingsBundle(bundle: Record<string, unknown>): Promise<void>;
}

/** Burst groups: frames shot in one run, collapsed to one cell (#131). */
declare class BurstNamespace {
    private http;
    constructor(http: HttpClient);
    /** Whether detection is on, and every group. */
    groups(): Promise<BurstGroupsInfo>;
    setDetection(enabled: boolean): Promise<void>;
    /** Collapse or expand one group; left out, it flips. */
    collapse(id: number, collapsed?: boolean): Promise<void>;
    /** Collapse or expand every group; left out, all flip the way the menu does. */
    collapseAll(collapsed?: boolean): Promise<void>;
    /** Reject the frames in the current group that have no stars and no pick. */
    rejectUnmarked(): Promise<void>;
    /** Pick the best frame in each group: what you keep, then the sharpest. */
    pickBest(): Promise<void>;
    /** Split bursts where the race number, camera or lens changes (#806); off, by time alone. Remembered. */
    split(enabled: boolean): Promise<void>;
    /** Show each burst's pick with how far ahead of the runner-up it is (#807). Remembered. */
    pickMargin(enabled: boolean): Promise<void>;
    /** Go through bursts one by one (#808): to do, later, done, and how many are left. Remembered. */
    queue(enabled: boolean): Promise<void>;
    /** Put burst `id` to `to_do`, `later` or `done` by hand (#808). */
    setState(id: number, state: 'to_do' | 'later' | 'done'): Promise<void>;
    /** To the first frame of the next burst still to do (#808). */
    nextToDo(): Promise<void>;
    /**
     * Settle the burst on screen (#808): keep one frame — `index`, or
     * `keep: 'pick'`, else the frame on screen — reject the rest, and go to
     * the next burst to do. One Edit → Undo puts every mark back.
     */
    settle(options?: BurstSettleBody): Promise<void>;
    /** Reject near-duplicates, keeping the sharpest of each set (#133). */
    rejectDuplicates(): Promise<void>;
}

/** Search across every folder the app has read, and saved collections (#134). */
declare class CatalogNamespace {
    private http;
    constructor(http: HttpClient);
    search(query: CatalogSearchBody): Promise<CatalogResultInfo[]>;
    /** One page of a search (`offset`, `limit`), and how many match in all (#660). */
    searchPage(query: CatalogSearchBody): Promise<CatalogResults>;
    collections(): Promise<SmartCollection[]>;
    /** Save a smart collection (a named query); one by that name is replaced. */
    saveCollection(collection: SmartCollection): Promise<void>;
    deleteCollection(name: string): Promise<void>;
    runCollection(name: string): Promise<CatalogResultInfo[]>;
    manualCollections(): Promise<ManualCollection[]>;
    /** Save a hand-picked collection; one by that name is replaced. */
    saveManualCollection(collection: ManualCollection): Promise<void>;
    deleteManualCollection(name: string): Promise<void>;
    runManualCollection(name: string): Promise<CatalogResultInfo[]>;
}

/**
 * Connections: where files are sent and fetched from (FTP, SFTP, Drive, S3,
 * …). Secrets are write-only: a read leaves them out and names the stored
 * ones in `secrets_set`, and writing one needs `api_allow_credential_writes`
 * in the settings.
 */
declare class ConnectionsNamespace {
    private http;
    constructor(http: HttpClient);
    list(): Promise<ConnectionInfo[]>;
    /**
     * Create or replace the connection called `name`: the body is the
     * connection itself, one key naming its kind, as Settings → Connections
     * stores it (`{ Sftp: { host, port, username, password, ssh_key_path } }`,
     * `{ Local: { path } }`). Needs `api_allow_credential_writes` in the
     * settings.
     */
    save(name: string, connection: Record<string, unknown>): Promise<void>;
    delete(name: string): Promise<void>;
}

/** Metadata → Strip, Remap, Fix Encoding and Apply GPS Track, on the current picture, the selection or every visible one. */
declare class MetadataNamespace {
    private http;
    constructor(http: HttpClient);
    /** Strip EXIF, GPS, IPTC, XMP or thumbnails: copies to `output_dir`, or the originals in place. */
    strip(options: StripBody): Promise<void>;
    /** Copy, move or swap values between IPTC fields. */
    remap(options: RemapBody): Promise<void>;
    /** Find (and unless `dry_run`, fix) mojibake in IPTC fields. */
    fixEncoding(options?: FixEncodingBody): Promise<EncodingFixResult>;
    /** Match a GPX track against capture times and write the positions. */
    gpsTracklog(options: GpsTracklogBody): Promise<void>;
    /**
     * Read the pictures' sidecars again, as Lightroom's Read Metadata from
     * File: what changed in the file wins — Lightroom's settings when they
     * changed outside photosuite, else the file's own develop. Applied as the
     * reads land (`develop_changed`).
     */
    readFromFile(options?: ScopeBody): Promise<void>;
    /** Keep photosuite's develop where Lightroom's settings changed outside it. */
    keepOwnDevelop(options?: ScopeBody): Promise<void>;
    /**
     * Import a Lightroom Classic catalog: each picture's Lightroom settings,
     * marks, keywords and virtual copies into its sidecar. Runs on; poll
     * `lightroomCatalogStatus()` for the report.
     */
    importLightroomCatalog(body: CatalogImportBody): Promise<CatalogImportStatus>;
    /** How the Lightroom catalog import goes, with its report once done. */
    lightroomCatalogStatus(): Promise<CatalogImportStatus>;
    /** Fill IPTC city, state and country from each picture's position. */
    reverseGeocode(options?: ScopeBody): Promise<void>;
}

/**
 * What is eventphotos' own (#265): the events a connection's token can
 * publish to. An album bound to one is synced through `client.publish`.
 */
declare class EventphotosNamespace {
    private http;
    constructor(http: HttpClient);
    /** Events the connection's token can publish to. */
    events(connection: string): Promise<RemoteEvent[]>;
}

/**
 * The photographer's gallery (gallery.flogvit.com, #750): what it sells
 * with, and what it shows for a picture — texts per language, sizes and
 * prices, edition, collections, awards, status — edited straight against
 * the gallery. The album that is the gallery syncs through `client.publish`.
 */
declare class GalleryNamespace {
    private http;
    constructor(http: HttpClient);
    /** The gallery, its presets and collections, and the album that is it. */
    view(connection?: string): Promise<GalleryView>;
    /** What the gallery shows for a picture now; `artwork` is null when it is not in a gallery. */
    artwork(path: string): Promise<GalleryArtworkOf>;
    /** Change what the gallery shows for a picture; the fields left out stay. */
    edit(path: string, change: Record<string, unknown>): Promise<GalleryArtwork>;
    /** A preset's sizes and prices on a picture's artwork. */
    applyPreset(path: string, presetId: number): Promise<GalleryArtwork>;
    /** A status (`draft`, `published`, `hidden`), a preset, or both, on many pictures. */
    bulk(paths: string[], change: {
        status?: 'draft' | 'published' | 'hidden';
        presetId?: number;
    }): Promise<GalleryBulkDone>;
    /** Put pictures in the gallery: in its album, made and bound when there is none. */
    add(paths: string[], connection?: string): Promise<GalleryAdded>;
}

/**
 * Albums kept in sync with where they are published (#753): an eventphotos
 * event, a folder on a connection. What is put in an album goes out by
 * itself, what is taken out comes down, what is edited is sent again.
 */
declare class PublishNamespace {
    private http;
    constructor(http: HttpClient);
    /** Every bound album, each picture's state there, and what the destination holds besides. */
    albums(): Promise<AlbumStatus[]>;
    /** One album, each picture's state, and the sync running or last run. */
    album(name: string): Promise<AlbumStatus>;
    /** Bind the album to a destination; binding to another starts it over there. */
    bind(name: string, binding: BindBody): Promise<PublishTarget>;
    /** Stop syncing the album; what is out there stays. */
    unbind(name: string): Promise<Unbound>;
    /** Put pictures (paths) in the album, or take them out. */
    items(name: string, change: {
        add?: string[];
        remove?: string[];
    }): Promise<ItemsChanged>;
    /** Sync now in the background, whether or not it syncs by itself; follow it with `album(name).job`. */
    sync(name: string): Promise<SyncStarted>;
    /**
     * Take down something at the album's destination that photosuite did not
     * put there (an id from `album(name).outside`): deleted, or hidden where a
     * sold picture is kept.
     */
    removeOutside(name: string, id: string): Promise<OutsideTakenDown>;
    /**
     * Something at the album's destination that photosuite did not put there
     * (an id from `album(name).outside`) is this picture: it joins the album
     * as that item, and the next sync sends it over it.
     */
    linkOutside(name: string, id: string, path: string): Promise<Linked>;
}

/** Workflows: a list of steps run on each file a source delivers (FTP → Google Drive, …). */
declare class WorkflowNamespace {
    private http;
    constructor(http: HttpClient);
    profiles(): Promise<WorkflowProfileInfo[]>;
    profile(index: number): Promise<WorkflowProfileInfo>;
    /** Add a workflow from its JSON, as Settings → Workflow stores it. */
    create(profile: Record<string, unknown>): Promise<WorkflowProfileInfo>;
    /** Add a workflow from one of the built-in templates. */
    fromTemplate(templateId: string): Promise<WorkflowProfileInfo>;
    /**
     * The workflow as a file another machine can import: its steps and what
     * they name, with no password, token or key in it. Write it out with
     * `JSON.stringify` as `<name>.photosuite-workflow`.
     */
    exportFile(index: number): Promise<SharedWorkflow>;
    /**
     * Add a workflow from a file `exportFile` (or Settings → Workflows →
     * Export…) wrote. Nothing this machine has is written over; `needs` says
     * what to fill in before it runs.
     */
    importFile(file: SharedWorkflow): Promise<WorkflowImported>;
    /** The workflow as stored: every step with every field. */
    config(index: number): Promise<Record<string, unknown>>;
    /** Change a workflow in place (a `steps` list replaces the steps); one that is running is refused. */
    update(index: number, patch: Record<string, unknown>): Promise<WorkflowProfileInfo>;
    /** Set the timing point's camera clock from the picture on screen, which shows `number`. */
    setClockOffset(index: number, number: string): Promise<WorkflowProfileInfo>;
    /** Delete a workflow; one that is running is refused. */
    delete(index: number): Promise<void>;
    templates(): Promise<WorkflowTemplateInfo[]>;
    start(index: number): Promise<void>;
    stop(): Promise<void>;
    status(): Promise<WorkflowStatus>;
    /**
     * What the workflow did to each picture it finished after `since` (the
     * last `id` seen, or `next` from the answer before), oldest first: every
     * step's outcome, what went out and where, and the first failure (#905).
     */
    files(options?: {
        since?: number;
        limit?: number;
    }): Promise<WorkflowFiles>;
    /** Hold new pictures in the queue (#907); they keep arriving and wait. */
    pause(): Promise<void>;
    /** Process the pictures that waited, in the order they arrived. */
    resume(): Promise<void>;
    /**
     * Run the picture of record `id` again, before any new picture, with
     * `overrides` for it alone. Says per destination whether the new picture
     * replaces the first; its record comes with `rerun_of: id`.
     */
    rerun(id: number, overrides?: WorkflowRerunBody): Promise<WorkflowRerun>;
    /** Keep a picture back: out of the retry queue, a local delivery folder and its albums. */
    hold(id: number, reason: string, by?: WorkflowHoldBody['by']): Promise<WorkflowFile>;
    /** Let a held picture go where it was going. */
    release(id: number): Promise<WorkflowFile>;
    /** The reports of this session's workflow runs, oldest first (#912). */
    runs(): Promise<WorkflowRun[]>;
    /** One run's report. */
    run(id: number): Promise<WorkflowRun>;
    /** Write a note into the latest run's report, about a picture's record or not. */
    note(text: string, picture?: number): Promise<WorkflowNote>;
    /** One picture's record. */
    file(id: number): Promise<WorkflowFile>;
    /**
     * A record's picture as JPEG bytes (#906): `delivered` (default), the file
     * the Export step wrote, or `source`, the file that arrived, whole (its develop, not the
     * workflow's crop); `longEdge` defaults to 1568, what a model reads a picture at.
     */
    picture(id: number, options?: {
        stage?: 'delivered' | 'source';
        longEdge?: number;
    }): Promise<Uint8Array>;
    /**
     * One contact sheet (JPEG) of the last `n` delivered pictures after `since`,
     * four to a row with each record's id and file name under it.
     */
    sheet(options?: {
        n?: number;
        since?: number;
    }): Promise<Uint8Array>;
    /** The watcher over the running workflow (#844): its settings, what it found, what Claude costs. */
    watch(): Promise<WatchInfo>;
    /** Change the watcher; the fields left out stay. */
    setWatch(settings: WatchSettingsBody): Promise<WatchInfo>;
}

/** The FTP tether: the camera sends each frame to the app's own FTP server. */
declare class TetherNamespace {
    private http;
    constructor(http: HttpClient);
    /** The tether setup, the password left out (`password_set` says whether one is stored). */
    config(): Promise<Record<string, unknown>>;
    /** Whether the server runs, where a camera connects, and what has arrived. */
    status(): Promise<TetherStatus>;
    /** Start the FTP server with the setup; `connect_addresses` fill in when it listens. */
    start(): Promise<TetherStatus>;
    stop(): Promise<TetherStatus>;
    /** Change the setup; a `password` is write-only. */
    updateConfig(patch: Record<string, unknown>): Promise<Record<string, unknown>>;
}

/**
 * Everything the app can do as one list (#610): every menu item and shortcut
 * as a command, every develop slider and the view's own dials (picture,
 * zoom, exposure aid, stars, Survey count, slideshow pace) as something to
 * turn. What a Logitech console's plugin builds its keys and dial from.
 */
declare class ActionsNamespace {
    private http;
    constructor(http: HttpClient);
    catalog(): Promise<ActionCatalog>;
    /**
     * Run a menu item or a shortcut by its id (`SetRating(3)`,
     * `Key(ToggleRawDecode)`), as the menu item or the key does.
     */
    run(id: string): Promise<void>;
    /**
     * Press one of the keys the app answers itself: the arrows, Home and End,
     * Enter, Escape and a view's own keys — as the key does in the view on
     * screen (`next` is the next picture, or the next cell in Survey).
     */
    pressKey(key: FixedKey): Promise<void>;
    /**
     * Turn a dial by `steps` of its step (negative down). A develop slider
     * turns on the picture on screen, and a turn that rests is one undo step.
     */
    adjust(field: string, steps: number): Promise<AdjustedValue>;
    /** Press a dial: back to neutral, zoom to fit, the first picture. */
    reset(field: string): Promise<AdjustedValue>;
    /** Every dial's value now, and the commands that are on. */
    state(): Promise<ActionState>;
}

declare class CullingClient {
    private http;
    private events;
    readonly navigate: NavigateNamespace;
    readonly images: ImagesNamespace;
    readonly selection: SelectionNamespace;
    readonly filter: FilterNamespace;
    readonly sort: SortNamespace;
    readonly batch: BatchNamespace;
    readonly view: ViewNamespace;
    readonly iptc: IptcNamespace;
    readonly ui: UiNamespace;
    readonly slideshow: SlideshowNamespace;
    readonly compare: CompareNamespace;
    readonly survey: SurveyNamespace;
    readonly export: ExportNamespace;
    readonly denoise: DenoiseNamespace;
    readonly upload: UploadNamespace;
    readonly folder: FolderNamespace;
    /** What the window works on: a folder, an album, a saved search or a library search (#663). */
    readonly source: SourceNamespace;
    readonly files: FilesNamespace;
    /** Signing in to Google Drive, and who we are signed in as (#219). */
    readonly drive: GoogleDriveNamespace;
    /** Disks, scans and duplicates across them (#258). */
    readonly library: LibraryNamespace;
    readonly develop: DevelopNamespace;
    /** Looks learned from your own RAWs for cameras without a profile (#446). */
    readonly cameraLooks: CameraLooksNamespace;
    /** The RAW as it is after each step of develop, and a photosite through them (#467). */
    readonly rawSteps: RawStepsNamespace;
    /** Export profiles, import presets, IPTC templates, key bindings and the other saved setups (#593). */
    readonly setups: SetupsNamespace;
    /** Burst groups, best-of-burst and near-duplicates (#131, #133). */
    readonly burst: BurstNamespace;
    /** Search across every folder read, and saved collections (#134). */
    readonly catalog: CatalogNamespace;
    /** Where files are sent and fetched from; secrets write-only. */
    readonly connections: ConnectionsNamespace;
    /** Metadata → Strip, Remap, Fix Encoding and Apply GPS Track: strip, remap, fix encoding, GPS track, reverse geocode. */
    readonly metadata: MetadataNamespace;
    /** eventphotos' events a connection can publish to (#265). */
    readonly eventphotos: EventphotosNamespace;
    /** The photographer's gallery: what it shows for a picture, edited there (#750). */
    readonly gallery: GalleryNamespace;
    /** Albums kept in sync with where they are published (#753). */
    readonly publish: PublishNamespace;
    /** Workflows: the steps run on each file a source delivers. */
    readonly workflow: WorkflowNamespace;
    /** The FTP tether. */
    readonly tether: TetherNamespace;
    /** Every menu item, shortcut and develop slider as one list (#610). */
    readonly actions: ActionsNamespace;
    /** Pipeline handles: one step in, one handle out (#211). */
    readonly pipeline: PipelineNamespace;
    /** The ingest work queue. */
    readonly ingest: IngestNamespace;
    /** The root of the instance this client was made for (`root` in the options), if any. */
    readonly root: string | undefined;
    constructor(options?: CullingClientOptions);
    /**
     * Somewhere pictures are sent — a Drive folder, or a folder on a connection.
     *
     * Made once and named on every delivery: `deliver(desk, '{base}_crop')`.
     * Where a picture went is the first thing anyone asks when one is missing,
     * so it belongs on the line that sent it and not in a setting further up.
     *
     *   const desk    = culling.to(shareLink, 'Rosseland-Bryne')
     *   const archive = culling.to('bryne-sftp', '/football/full')
     */
    to(connection: string | null, path: string): Place;
    /** Get app status */
    status(): Promise<StatusInfo>;
    /**
     * Throw unless the app runs with a root of its own (`--dir`, #941). A
     * script that writes to `/delivery` means the instance's root there, and
     * the machine's root against an app without one: call this first.
     */
    requireRoot(): Promise<void>;
    /**
     * Fetch a start list into an event folder, for an Identify step's rosters.
     * Give the page's address or a CSV file; give only `dir` to fetch it again.
     */
    fetchStartList(body: StartListFetchBody): Promise<StartListSummary>;
    /**
     * Pick at most `count` pictures of the open folder (or the selection) for a
     * delivery, nobody twice; they get the label and an album.
     */
    pickForDelivery(body: PickForDeliveryBody): Promise<PickReport>;
    /**
     * Record which of the open folder's pictures the editor published, from
     * their list; once enough are known, Pick for Delivery scores by it.
     */
    importPublished(body: ImportPublishedBody): Promise<ImportReport>;
    /** Download a Flickr or SmugMug album into a folder, one folder per photographer when asked. */
    downloadAlbum(body: AlbumDownloadBody): Promise<DownloadStatus>;
    /** How far the album download has come. */
    albumDownloadStatus(): Promise<DownloadStatus>;
    /** Stop the album download after the picture being fetched. */
    stopAlbumDownload(): Promise<DownloadStatus>;
    /** Write a gallery site's manifest.json for a delivered event. */
    eventSite(body: EventSiteBody): Promise<SiteSummary>;
    /** Pick a delivered event's best into <event>_best/. */
    pickBestOfYear(body: BestOptions): Promise<BestStatus>;
    /** How far the year's best has come. */
    bestOfYearStatus(): Promise<BestStatus>;
    /** Run a workflow once over every picture in a folder and its subfolders (an event's in/). */
    runOnFolder(body: FolderRunBody): Promise<FolderRunStatus>;
    /** How far the folder run has come. */
    folderRunStatus(): Promise<FolderRunStatus>;
    /** Stop the folder run after the pictures being worked on. */
    stopFolderRun(): Promise<FolderRunStatus>;
    /** Set the numbers of the pictures in scope; names follow from the start lists. */
    setNumbers(body: NumbersBody): Promise<NumbersSet>;
    /** The open folder's pictures with a read held for a person; settle one with setNumbers. */
    reviewQueue(): Promise<ReviewQueue>;
    /** The numbers on the picture on screen, and who wears each. */
    currentNumbers(): Promise<CurrentNumbers>;
    /** The start lists the open folder's numbers are looked up in. */
    folderStartLists(): Promise<FolderStartLists>;
    /** Choose the open folder's start lists (remembered with the folder). */
    setFolderStartLists(body: FolderStartLists): Promise<FolderStartLists>;
    /**
     * Count what the open folder (or the selection) delivers and write
     * delivery-report.txt and a captioned delivery-report.pdf beside it.
     */
    deliveryReport(body?: DeliveryReportBody): Promise<DeliveryReport>;
    /** How many each photographer had published, per event, from the imported lists */
    publishedByPhotographer(): Promise<PublishedByPhotographer>;
    /** Camera serial number → photographer, which deliveries are credited by */
    photographers(): Promise<PhotographerList>;
    /** Replace the photographers table */
    setPhotographers(list: PhotographerList): Promise<PhotographerList>;
    /** Send a bug report or feedback to photosuite.flogvit.com, with the app's log */
    feedback(report: Report): Promise<void>;
    /** This build's version, and a newer release if the update check found one */
    update(): Promise<AppUpdate>;
    /** What changed in each version this build carries, and what a newer version brings (Help → What's New…) */
    changelog(): Promise<AppChangelog>;
    /**
     * Download the newer release, check its signature, put it in place of the
     * app and start it again (macOS; `update().update.installable`). The app
     * closes when it is in place, so the connection goes with it.
     */
    installUpdate(): Promise<AppUpdate>;
    /** Every app setting (secrets left out). */
    settings(): Promise<Record<string, unknown>>;
    /** Merge `patch` into the settings, as the Settings dialog saves them; an unknown key is refused. */
    updateSettings(patch: Record<string, unknown>): Promise<Record<string, unknown>>;
    /** Whether the transcription service has its key (#607). */
    transcriptionKey(): Promise<TranscriptionKeyInfo>;
    /** Store the transcription service's key (needs `api_allow_credential_writes`). */
    setTranscriptionKey(key: string): Promise<TranscriptionKeyInfo>;
    /**
     * A new API token (needs `api_allow_credential_writes`). The server takes
     * it once this answer is out; make a new client with it.
     */
    regenerateToken(): Promise<string>;
    /** The detection models, and which are downloaded. */
    models(): Promise<ModelList>;
    /** Download a detection model (`nano` … `xlarge`); `models()` says when it is there. */
    downloadModel(model: string): Promise<ModelList>;
    /** The geocoding dataset: downloaded, loaded, or on its way. */
    geocodeDataset(): Promise<GeocodeDatasetInfo>;
    /** Download a geocoding dataset from GeoNames (`cities500`, `all_countries`). */
    downloadGeocodeDataset(dataset?: 'cities500' | 'all_countries'): Promise<GeocodeDatasetInfo>;
    loadGeocodeDataset(): Promise<GeocodeDatasetInfo>;
    /** Every optional download (models, place names), as Help → Downloads… lists them: here, downloading, missing or failed. */
    downloads(): Promise<DownloadList>;
    /** Start downloads ahead of their features' first use; `downloads()` says how far they have come. */
    startDownloads(items: DownloadName[]): Promise<DownloadList>;
    /** Throw away every folder's cached previews. */
    clearCache(): Promise<void>;
    /** The licence on this machine, as Settings → License shows it (#594). */
    license(): Promise<LicenseInfo>;
    /**
     * Activate a licence key. The request runs on; its outcome comes as
     * `license_changed` and in `license()`.
     */
    activateLicense(licenseKey: string): Promise<LicenseInfo>;
    /** Sign in with FLOGVIT: opens the browser on this machine; the licence lands when it is confirmed there. */
    signInLicense(): Promise<LicenseInfo>;
    /** Stop a sign-in waiting for the browser, as Cancel does (#788). */
    cancelSignIn(): Promise<LicenseInfo>;
    startTrial(): Promise<LicenseInfo>;
    /** Ask the server for the licence as it is now: an extension, a revocation. */
    refreshLicense(): Promise<LicenseInfo>;
    /** Free this machine's activation. */
    deactivateLicense(): Promise<LicenseInfo>;
    /** What Edit → Undo and Redo would do now (#709). */
    history(): Promise<UndoInfo>;
    /**
     * Edit → Undo: take back the last step, whatever it was — a crop, a batch
     * of auto-crops, marks, develop, metadata, copies. A picture changed since
     * by something else is left as it is, and the answer says so.
     */
    undo(): Promise<Done>;
    /** Edit → Redo: do the last undone step again. */
    redo(): Promise<Done>;
    /** The log file and its last lines (default 200), for a bug report. */
    log(lines?: number): Promise<LogInfo>;
    /**
     * Help → Scripts Folder: the folder where a script runs as it is, with the
     * SDK that belongs to this app written beside it, and the address of the
     * script collection on the website.
     */
    scriptsFolder(): Promise<ScriptsFolderInfo>;
    /** Every third-party notice the app ships with, as one plain-text document */
    licenses(): Promise<LicenseNotices>;
    /** Subscribe to a specific event */
    on<E extends EventName>(event: E, handler: EventHandler<E>): () => void;
    on(event: '*', handler: WildcardHandler): () => void;
    /** Wait for a specific event (promise-based) */
    waitFor<E extends EventName>(event: E, timeoutMs?: number): Promise<EventMap[E]>;
    /** Connect WebSocket (if autoConnect was false) */
    connect(): void;
    /** Whether the WebSocket is connected */
    get isConnected(): boolean;
    /** Disconnect WebSocket and clean up */
    disconnect(): void;
}

export { ApiError, type ApplyScope, type BatchOptions, type BracketInfo, type CaptionSuggestBody, Channel, type ColorLabel, type CompareSplit, type CropOptions, type CropSubject, CullingClient, type CullingClientOptions, type DenoiseProfileInfo, type DenoiseQueueInfo, type DenoiseQueueItemInfo, type DenoiseSettingsInfo, type Detected, type DriveAccountInfo, type DriveSignInOptions, type EachOptions, type EditorInfo, type EventHandler, type EventMap, type EventName, type ExportStatusInfo, type Eyes, FileType, type FilterOptions, type FindReplaceOptions, Flag, GoogleDriveNamespace, type ImageInfo, IngestNamespace, type IngestedFile, type IptcInfo, type IptcPatch, Label, type LearningScore, type LearningStatus, type MapPosition, type MapPositionBody, type MapPositions, type MergePicturesBody, type Merged, type NavigateTarget, type NeighbourPlace, type OpenOptions, PipelineHandle, PipelineNamespace, type Place, type PlaceByNeighboursBody, type PlacedByNeighbours, type QueuedFile, type RawStep, type RawStepsArea, type RawStepsBody, type RawStepsInfo, RawStepsNamespace, type RawStepsProbe, type Relearning, type SelectionCriteria, type SelectionInfo, type Session, type SortOptions, SortOrder, type StatusInfo, StepError, type StepOptions, type StepOutcome, type StepResult, type Suggested, type Suggestion, type UiStateInfo, type UndoInfo, type UploadDestinationInfo, type UploadQueueInfo, type VideoBody, type VideoState, ViewMode, Watch, type WatchOptions, type WatchStatus, type WildcardHandler, type WorkflowFile, type WorkflowFiles, type WorkflowHeld, type WorkflowHoldBody, type WorkflowNote, type WorkflowNoteBody, type WorkflowOutput, type WorkflowOverrides, type WorkflowRerun, type WorkflowRerunBody, type WorkflowRun, type WorkflowRunPicture, type WorkflowStep, type WorkflowTaken, type YoloxModel };
