import { EventEmitter } from 'events';
import { type SyncDirection } from './sync-config';
/**
 * rsync-driven sync service.
 *
 * Design choices worth noting:
 *
 * - **SSH options go through a wrapper script**, not through `rsync -e "ssh -p 22 -i …"`.
 *   Rsync parses the `-e` value by splitting on whitespace, so any keyPath with a space
 *   or an attacker-crafted `-oProxyCommand=…` would escalate self-config to code execution
 *   as the ccweb process.  A wrapper `sh -c 'exec ssh <hardcoded opts> "$@"'` file written
 *   with user values quoted by `shell-quote` equivalent semantics removes this vector
 *   entirely.
 * - **Password auth** uses `sshpass -e` wrapping the same script.  `SSHPASS` env stays
 *   in the parent and is inherited by child only — never appears in argv or logs.
 * - **Concurrency**: one in-flight sync per (user, project); subsequent calls return
 *   `{ skipped: true }`.
 * - **Logs**: written via createWriteStream (ordered); file truncated to keep the last
 *   ~20 runs, preventing long-term growth.
 * - **bidirectional**: is NOT a safe two-way sync (rsync can't).  The push leg is run
 *   without --delete and with -u (update-if-newer), so remote-newer files survive the
 *   pull leg that follows.  Deletions must be handled manually.
 * - **openrsync (macOS 15+)**: shipped at `/usr/bin/rsync`, protocol 29.  Doesn't support
 *   `--stats`; `-v` output doesn't include per-file lines.  We use `-avzi` (itemize
 *   changes) which works on both GNU and openrsync, and parses telemetry from formats
 *   both emit (`>f`/`<f`-prefixed lines and the `total size is N` tail).  A homebrew
 *   GNU rsync at `/opt/homebrew/bin/rsync` is preferred when present for richer output.
 */
export interface SyncResult {
    ok: boolean;
    exitCode: number | null;
    durationMs: number;
    bytes: number;
    filesTransferred: number;
    logTail: string;
    skipped?: true;
    reason?: string;
}
export type SyncEvent = {
    kind: 'start';
    username: string;
    projectId: string;
    direction: 'push' | 'pull';
    leg: 'single' | 'bidi-push' | 'bidi-pull';
} | {
    kind: 'progress';
    username: string;
    projectId: string;
    currentFile: string;
    filesTransferred: number;
} | {
    kind: 'done';
    username: string;
    projectId: string;
    ok: boolean;
    filesTransferred: number;
    bytes: number;
    durationMs: number;
    reason?: string;
};
export declare const syncEvents: EventEmitter<[never]>;
/**
 * Sync one project. Bidirectional runs the push leg without --delete (so the
 * following pull can restore remote-newer files), then the pull leg.
 */
export declare function syncProject(username: string, projectId: string, projectName: string, localPath: string, overrideDirection?: SyncDirection, opts?: {
    runId?: string;
}): Promise<SyncResult>;
/**
 * Cancel a running sync for (username, projectId) by sending SIGTERM to the
 * live rsync child process. Returns true if a process was signalled.
 *
 * Safe to call when nothing is in flight — returns false without side effects.
 * The cancelled flag is set first so runOne's close handler can distinguish
 * user cancel from spontaneous rsync failure even if SIGTERM races with a
 * natural exit.
 */
export declare function cancelSync(username: string, projectId: string): boolean;
/**
 * Cancel every in-flight sync for a user AND set the bulk-cancel flag so a
 * running `syncAll` loop breaks between projects. Returns the list of
 * projectIds that had a process cancelled.
 */
export declare function cancelAllForUser(username: string): string[];
/** True when the bulk-cancel flag has been set for this user. */
export declare function isBulkCancelled(username: string): boolean;
/** Clear the bulk-cancel flag. syncAll calls this on entry and exit. */
export declare function clearBulkCancel(username: string): void;
export declare function isSyncing(username: string, projectId: string): boolean;
export declare function listInFlight(username: string): string[];
/**
 * Non-destructive connection test: runs the wrapper script directly with
 * `<user@host> true`.  Uses the identical exec path as rsync's `-e`, so if
 * this succeeds the sync's SSH setup will also succeed.
 */
export declare function testConnection(username: string): Promise<{
    ok: boolean;
    message: string;
}>;
//# sourceMappingURL=sync-service.d.ts.map