/** * Sync client (Phase 6) — `dim cloud link` + `dim sync`. * * Local-first LWW sync: * push: rows changed since last push (+ tombstones) → server * pull: latest remote rows since cursor → apply if remote.updatedAt > local * * Config (per-project): * .aidimag/config.json { server, brain, token } — per-project, add to .gitignore if token is sensitive * AIDIMAG_API_KEY env var — alternative to storing token in config */ import type { MemoryStore } from "../db/store.js"; import type { RemoteSnapshot } from "./server.js"; import { type QuotaStatus } from "./quota.js"; import { type SelectionStrategy } from "./selection.js"; export interface CloudConfig { server: string; brain: string; } /** Scope sync cursors per brain so linking a new cloud project triggers a fresh upload. */ export declare function syncMetaKey(base: string, brain: string): string; export declare function configPath(repoRoot: string): string; export declare function readCloudConfig(repoRoot: string): CloudConfig | null; export declare function writeCloudConfig(repoRoot: string, cfg: CloudConfig): void; export declare function getToken(server: string, repoRoot?: string): string | null; export interface DeviceStart { device_code: string; user_code: string; verification_uri: string; interval: number; expires_in: number; } /** Begin a device-code login: server hands back a user code + approval URL. */ export declare function startDeviceLogin(server: string, brain?: string): Promise; /** Poll until the device is approved in the browser. Returns the token (caller must save it). */ export declare function pollDeviceLogin(server: string, start: DeviceStart): Promise<{ token: string; brain: string | null; }>; export interface SyncResult { pushed: number; /** Memory rows accepted by the server this run (excludes proposals/tombstones). */ memoriesPushed: number; /** Memory rows in the push batch this run (excludes proposals/tombstones). */ memoriesQueued: number; pulled: number; applied: number; skippedOlder: number; /** lifecycle events shipped to the server (consensus input) */ eventsPushed: number; /** Local rows considered for push this run. */ pushQueued: number; /** Local memories exist but incremental push sent nothing (remote may already have data). */ pushSkipped: boolean; /** Remote brain is empty while local has memories — user was not asked or declined upload. */ needsFullUploadConfirm?: boolean; /** Auto-recovery detected local data loss and reset cursor to pull all remote data. */ autoRecovered?: boolean; /** Quota status from server (if applicable). */ quota?: QuotaStatus; /** Quota exceeded, user needs to select memories. */ quotaExceeded?: boolean; /** Number of unsynced memories. */ unsyncedCount?: number; /** Warning message from server. */ warning?: string; /** Rows skipped because a single payload exceeds the server's per-row limit. */ oversized?: number; } export interface SyncOptions { /** Push every local memory/proposal, not just rows changed since the last push. */ full?: boolean; /** Called when local has memories missing on the remote; return true to run a full upload. */ confirmFullUpload?: (localMemoryCount: number, remoteMemoryCount: number | null) => Promise; /** Reset pull cursor to 0 and fetch all remote data (useful for recovery after local DB corruption). */ forcePull?: boolean; /** Selection strategy for quota limits. */ selectionStrategy?: SelectionStrategy; /** Specific memory IDs to sync (for custom selection). */ selectedIds?: string[]; /** Skip quota checks and selection UI (for testing). */ skipQuotaCheck?: boolean; } export type { RemoteSnapshot, RemoteSnapshotItem } from "./server.js"; export interface FetchRemoteSnapshotOpts { id?: string; tbl?: "memories" | "proposals"; limit?: number; /** When false, return counts/seq only. Default true unless fetching by id. */ list?: boolean; full?: boolean; /** When listing proposals, include resolved (APPROVED/REJECTED) rows. Default: pending only. */ all?: boolean; } /** Read the server's current latest-state snapshot without pulling into local DB. */ export declare function fetchRemoteSnapshot(repoRoot: string, opts?: FetchRemoteSnapshotOpts): Promise; export declare function sync(store: MemoryStore, repoRoot: string, opts?: SyncOptions): Promise; /** * Best-effort background sync after local mutations (CLOUD_DESIGN: "runs * automatically (debounced) after remember / review / verify"). * No-ops when the repo isn't cloud-linked, no token is stored, the last * auto-sync was <30s ago, or AIDIMAG_AUTO_SYNC=off. Never throws. */ export declare function maybeAutoSync(store: MemoryStore, repoRoot: string): Promise;