/**
 * The dashboard's user store: a JSON file of {@link DashboardUser} records at
 * `dashboard-users.json` (0600) inside the state directory, holding scrypt
 * hashes and site grants. Small on purpose — a box hosts a handful of
 * collaborators, not a directory service.
 *
 * On first use the store bootstraps a single admin so a freshly provisioned
 * dashboard is never reachable without credentials. The generated password is
 * returned to the caller to print once; only its hash is ever written.
 */
import type { DashboardUser, SiteRole } from './dashboard-auth';
/** Project-relative location of the user store, for messages and docs. */
export declare function usersFile(): string;
/** Usernames are used in URLs and shell-free contexts; keep them boring. */
export declare function isValidUsername(value: string): boolean;
export declare function parseUsersFile(text: string): DashboardUser[];
export declare function usersFilePath(cwd: string): string;
export declare function loadUsers(cwd: string): DashboardUser[];
export declare function saveUsers(cwd: string, users: DashboardUser[]): void;
export declare function findUser(users: DashboardUser[], username: string): DashboardUser | undefined;
export interface BootstrapResult {
    users: DashboardUser[];
    /** Set only when an admin was just created — print it once, then it's gone. */
    generated?: {
        username: string;
        password: string;
    };
}
/**
 * Ensure at least one admin exists. When the store is empty, create an admin
 * using `TS_CLOUD_UI_PASSWORD` if set, else a generated password that is
 * returned for one-time display.
 */
export declare function ensureAdminUser(cwd: string, username?: string): BootstrapResult;
export interface UpsertMemberInput {
    username: string;
    password?: string;
    name?: string;
    email?: string;
    sites: Record<string, SiteRole>;
}
/**
 * Create or update a member and their site grants. Returns the generated
 * password when one was minted (a new user without an explicit password).
 *
 * Members are created through this path only — it cannot produce an admin, so
 * an invite flow can never escalate someone to box-wide control.
 */
export declare function upsertMember(cwd: string, input: UpsertMemberInput): {
    user: DashboardUser;
    password?: string;
};
/** Replace a local credential while preserving the user's role and grants. */
export declare function updateUserPassword(cwd: string, username: string, passwordHash: string): DashboardUser;
/**
 * Remove a user. The last admin cannot be removed — that would lock everyone
 * out of the box with no way back in short of editing the file by hand.
 */
export declare function removeUser(cwd: string, username: string): {
    ok: boolean;
    error?: string;
};
/** Public shape for the UI — never leaks the password hash. */
export declare function describeUser(user: DashboardUser): Record<string, any>;
