@rigxyz/tapd 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/binding-config.ts","../src/relay-client.ts","../src/state-db.ts","../src/tapignore.ts","../src/walker.ts","../../core/src/sync/capability.ts","../../core/src/sync/object.ts","../../core/src/sync/path.ts","../../core/src/sync/protocol.ts","../../core/src/ids.ts","../src/hash.ts","../src/init.ts","../src/invite.ts","../src/join.ts","../src/applier.ts","../src/sse.ts","../src/uploader.ts","../src/start.ts"],"sourcesContent":["/**\n * .rig/tap-binding.local.json — machine-local per-binding pointer.\n *\n * Holds the bindingId, the device's capability token, and where to find\n * the relay. NEVER synced (validated by isAlwaysExcluded in tapignore.ts;\n * the relay also rejects the path via validateRigPath).\n *\n * Tokens live here in plaintext for now. A future revision can move\n * them to the OS keychain; the file structure is designed to make that\n * swap localized.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport const BINDING_CONFIG_PATH = \".rig/tap-binding.local.json\";\n\nexport type BindingConfig = {\n bindingId: string;\n relayUrl: string;\n deviceId: string;\n /** Capability-token secret. Returned once by the relay, never re-derivable. */\n token: string;\n};\n\nexport function bindingConfigFile(rootDir: string): string {\n return join(rootDir, BINDING_CONFIG_PATH);\n}\n\nexport function readBindingConfig(rootDir: string): BindingConfig | null {\n const path = bindingConfigFile(rootDir);\n if (!existsSync(path)) return null;\n const raw = readFileSync(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<BindingConfig>;\n if (\n typeof parsed.bindingId !== \"string\" ||\n typeof parsed.relayUrl !== \"string\" ||\n typeof parsed.deviceId !== \"string\" ||\n typeof parsed.token !== \"string\"\n ) {\n throw new Error(`malformed binding config at ${path}`);\n }\n return {\n bindingId: parsed.bindingId,\n relayUrl: parsed.relayUrl,\n deviceId: parsed.deviceId,\n token: parsed.token,\n };\n}\n\nexport function writeBindingConfig(rootDir: string, config: BindingConfig): void {\n const path = bindingConfigFile(rootDir);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Thrown when an operation requires a bound workspace but\n * `.rig/tap-binding.local.json` is missing. All three callers (`invite`,\n * `status`, `uninit`) raise the same class so bin.ts maps it to a\n * single `not_initialized` JSON error code for rig.\n */\nexport class NotInitializedError extends Error {\n constructor(public readonly rootDir: string) {\n super(`not initialized — no binding config at ${rootDir}/${BINDING_CONFIG_PATH}`);\n this.name = \"NotInitializedError\";\n }\n}\n","/**\n * Thin fetch wrapper around the Phase 1 relay routes.\n *\n * Module-level `createBinding` is unauthenticated (Phase 3 will Clerk-gate\n * it). Everything else binds to a (baseUrl, bindingId, token) triple via\n * the `RelayClient` class, since every binding-scoped route needs the\n * same three values on every call.\n *\n * `RelayError` carries the route + status + parsed body so caller logic\n * can map specific 400s (\"object_not_uploaded\", \"case_collision_with\",\n * etc.) to user-facing warnings instead of \"something went wrong\".\n */\n\nimport type {\n AcceptInviteRequest,\n AcceptInviteResponse,\n BindingInvite,\n ChangesSinceResponse,\n CompleteUploadRequest,\n CompleteUploadResponse,\n CreateBindingRequest,\n CreateBindingResponse,\n CreateInviteRequest,\n CreateInviteResponse,\n DownloadUrlResponse,\n GetBindingResponse,\n ManifestSnapshotResponse,\n PrepareUploadRequest,\n PrepareUploadResponse,\n SubmitChangeEvent,\n SubmitChangesRequest,\n SubmitChangesResponse,\n} from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nexport class RelayError extends Error {\n constructor(\n public readonly route: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n const bodySummary =\n typeof body === \"object\" && body !== null && \"error\" in body\n ? String((body as { error: unknown }).error)\n : `status ${status}`;\n super(`relay ${route}: ${bodySummary}`);\n this.name = \"RelayError\";\n }\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (!ct.includes(\"application/json\")) return undefined;\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n}\n\nasync function expectJson<T>(route: string, res: Response): Promise<T> {\n const body = await readJson(res);\n if (!res.ok) {\n throw new RelayError(route, res.status, body);\n }\n return body as T;\n}\n\n/**\n * Accept an invite. User-authenticated via X-Tap-User-Id (+ optional\n * X-Tap-User-Email when the invite has an emailConstraint).\n *\n * Returns the device's per-machine capability token exactly once.\n */\nexport async function acceptInvite(opts: {\n baseUrl: string;\n secret: string;\n userId: string;\n email?: string;\n request?: AcceptInviteRequest;\n fetch?: FetchLike;\n}): Promise<AcceptInviteResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/invites/:secret/accept\";\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-tap-user-id\": opts.userId,\n };\n if (opts.email) headers[\"x-tap-user-email\"] = opts.email;\n const res = await fn(\n joinUrl(opts.baseUrl, `/v1/invites/${encodeURIComponent(opts.secret)}/accept`),\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(opts.request ?? {}),\n },\n );\n return expectJson<AcceptInviteResponse>(route, res);\n}\n\n/**\n * Create a new binding. User-authenticated via the X-Tap-User-Id header\n * (Phase 3 placeholder; Clerk JWT later). The response carries the\n * owner's first capability token, returned exactly once.\n */\nexport async function createBinding(opts: {\n baseUrl: string;\n request: CreateBindingRequest;\n userId: string;\n email?: string;\n fetch?: FetchLike;\n}): Promise<CreateBindingResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/bindings\";\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-tap-user-id\": opts.userId,\n };\n if (opts.email) headers[\"x-tap-user-email\"] = opts.email;\n const res = await fn(joinUrl(opts.baseUrl, \"/v1/bindings\"), {\n method: \"POST\",\n headers,\n body: JSON.stringify(opts.request),\n });\n return expectJson<CreateBindingResponse>(route, res);\n}\n\nexport type RelayClientOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n fetch?: FetchLike;\n};\n\n/**\n * Binding-scoped relay client. One instance per active binding; cheap to\n * construct, holds no resources beyond the closed-over fetch impl.\n */\nexport class RelayClient {\n private readonly baseUrl: string;\n private readonly bindingId: string;\n private readonly token: string;\n private readonly fetch: FetchLike;\n\n constructor(opts: RelayClientOptions) {\n this.baseUrl = opts.baseUrl;\n this.bindingId = opts.bindingId;\n this.token = opts.token;\n this.fetch = opts.fetch ?? fetch;\n }\n\n private authHeaders(extra?: Record<string, string>): Record<string, string> {\n return { authorization: `Bearer ${this.token}`, ...extra };\n }\n\n private bindingPath(suffix: string): string {\n return joinUrl(\n this.baseUrl,\n `/v1/bindings/${encodeURIComponent(this.bindingId)}${suffix}`,\n );\n }\n\n async getBinding(): Promise<GetBindingResponse> {\n const route = \"GET /v1/bindings/:id\";\n const res = await this.fetch(this.bindingPath(\"\"), {\n headers: this.authHeaders(),\n });\n return expectJson<GetBindingResponse>(route, res);\n }\n\n // ────────── objects ──────────\n\n async prepareUpload(body: PrepareUploadRequest): Promise<PrepareUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/prepare-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/prepare-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<PrepareUploadResponse>(route, res);\n }\n\n async completeUpload(body: CompleteUploadRequest): Promise<CompleteUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/complete-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/complete-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CompleteUploadResponse>(route, res);\n }\n\n async downloadUrl(hash: string): Promise<DownloadUrlResponse> {\n const route = \"GET /v1/bindings/:id/objects/:hash/download-url\";\n const res = await this.fetch(\n this.bindingPath(`/objects/${encodeURIComponent(hash)}/download-url`),\n { headers: this.authHeaders() },\n );\n return expectJson<DownloadUrlResponse>(route, res);\n }\n\n /** PUT bytes to a presigned URL. Throws on non-2xx. */\n async putObjectBytes(uploadUrl: string, bytes: Uint8Array | Buffer): Promise<void> {\n const route = \"PUT <uploadUrl>\";\n const res = await this.fetch(uploadUrl, {\n method: \"PUT\",\n body: new Uint8Array(bytes),\n headers: { \"content-length\": String(bytes.byteLength) },\n });\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n }\n\n /** GET bytes from a presigned URL. Throws on non-2xx. */\n async getObjectBytes(downloadUrl: string): Promise<Buffer> {\n const route = \"GET <downloadUrl>\";\n const res = await this.fetch(downloadUrl);\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n // ────────── changes ──────────\n\n async submitChanges(events: ReadonlyArray<SubmitChangeEvent>): Promise<SubmitChangesResponse> {\n const route = \"POST /v1/bindings/:id/changes\";\n const body: SubmitChangesRequest = { events };\n const res = await this.fetch(this.bindingPath(\"/changes\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<SubmitChangesResponse>(route, res);\n }\n\n async listChanges(opts?: { after?: string; limit?: number }): Promise<ChangesSinceResponse> {\n const route = \"GET /v1/bindings/:id/changes\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/changes\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ChangesSinceResponse>(route, res);\n }\n\n // ────────── invites ──────────\n\n /**\n * Mint a new invite (owner-only). Returns the secret + a ready-to-share\n * accept URL, exactly once. Caller must surface to the user immediately\n * and rely on `listInvites()` later if they want to refer back without\n * the secret.\n */\n async mintInvite(body: CreateInviteRequest): Promise<CreateInviteResponse> {\n const route = \"POST /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CreateInviteResponse>(route, res);\n }\n\n async listInvites(): Promise<{ invites: BindingInvite[] }> {\n const route = \"GET /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n headers: this.authHeaders(),\n });\n return expectJson<{ invites: BindingInvite[] }>(route, res);\n }\n\n async revokeInvite(\n inviteId: string,\n ): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const route = \"DELETE /v1/bindings/:id/invites/:inviteId\";\n const res = await this.fetch(\n this.bindingPath(`/invites/${encodeURIComponent(inviteId)}`),\n { method: \"DELETE\", headers: this.authHeaders() },\n );\n return expectJson<{ revoked: boolean; alreadyRevoked: boolean }>(route, res);\n }\n\n // ────────── manifest ──────────\n\n async getManifest(opts?: { after?: string; limit?: number }): Promise<ManifestSnapshotResponse> {\n const route = \"GET /v1/bindings/:id/manifest\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/manifest\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ManifestSnapshotResponse>(route, res);\n }\n}\n\nfunction joinUrl(base: string, suffix: string): string {\n const b = base.replace(/\\/+$/, \"\");\n const s = suffix.startsWith(\"/\") ? suffix : `/${suffix}`;\n return b + s;\n}\n","/**\n * Local sync metadata — one SQLite file per binding, at\n * `<rig>/.rig/tap/state.local.db`. Machine-local; never synced.\n *\n * Tracks:\n * - per-path: last_applied_change_id, last_seen_hash, local_dirty,\n * last_scan_at\n * - cursor: the change_event id we've consumed up to (so a daemon\n * restart resumes from the right place)\n *\n * Schema is small and applied inline on open — no migrations file.\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport Database from \"better-sqlite3\";\n\nconst CURSOR_PREFIX = \"chg_\";\n\n/** Compare two `chg_<n>` cursors numerically. Non-prefixed values are treated as -∞. */\nfunction cursorGreaterThan(a: string, b: string): boolean {\n const an = a.startsWith(CURSOR_PREFIX) ? safeBigInt(a.slice(CURSOR_PREFIX.length)) : null;\n const bn = b.startsWith(CURSOR_PREFIX) ? safeBigInt(b.slice(CURSOR_PREFIX.length)) : null;\n if (an === null) return false;\n if (bn === null) return true;\n return an > bn;\n}\n\nfunction safeBigInt(s: string): bigint | null {\n try { return BigInt(s); } catch { return null; }\n}\n\nexport type LocalPathState = {\n path: string;\n lastAppliedChangeId: string | null;\n lastSeenHash: string | null;\n localDirty: boolean;\n lastScanAt: string | null;\n};\n\nconst SCHEMA = `\n CREATE TABLE IF NOT EXISTS paths (\n path TEXT PRIMARY KEY,\n last_applied_change_id TEXT,\n last_seen_hash TEXT,\n local_dirty INTEGER NOT NULL DEFAULT 0,\n last_scan_at TEXT\n );\n\n CREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n`;\n\nexport type StateDb = {\n /** Cursor we've applied up to, or \"chg_0\" if nothing yet. */\n getCursor(): string;\n setCursor(cursor: string): void;\n\n upsertPath(state: LocalPathState): void;\n getPath(path: string): LocalPathState | null;\n deletePath(path: string): void;\n /** Enumerate every tracked path. Cheap — state DBs are small. */\n listPaths(): string[];\n\n setMeta(key: string, value: string): void;\n getMeta(key: string): string | null;\n\n close(): void;\n};\n\nexport function openStateDb(dbPath: string): StateDb {\n mkdirSync(dirname(dbPath), { recursive: true });\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA);\n\n const stmtUpsert = db.prepare(`\n INSERT INTO paths (path, last_applied_change_id, last_seen_hash, local_dirty, last_scan_at)\n VALUES (@path, @last_applied_change_id, @last_seen_hash, @local_dirty, @last_scan_at)\n ON CONFLICT(path) DO UPDATE SET\n last_applied_change_id = excluded.last_applied_change_id,\n last_seen_hash = excluded.last_seen_hash,\n local_dirty = excluded.local_dirty,\n last_scan_at = excluded.last_scan_at\n `);\n const stmtGet = db.prepare(`SELECT * FROM paths WHERE path = ?`);\n const stmtDelete = db.prepare(`DELETE FROM paths WHERE path = ?`);\n const stmtListPaths = db.prepare(`SELECT path FROM paths`);\n\n const stmtSetMeta = db.prepare(`\n INSERT INTO meta (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n `);\n const stmtGetMeta = db.prepare(`SELECT value FROM meta WHERE key = ?`);\n\n type PathRow = {\n path: string;\n last_applied_change_id: string | null;\n last_seen_hash: string | null;\n local_dirty: number;\n last_scan_at: string | null;\n };\n\n return {\n getCursor() {\n const row = stmtGetMeta.get(\"cursor\") as { value: string } | undefined;\n return row?.value ?? \"chg_0\";\n },\n setCursor(cursor: string) {\n // Monotonic write: refuse to move the cursor backwards. Belt and\n // suspenders for the SSE/poll race — even if two applies overlap\n // and try to commit different cursors, we never regress.\n const current = (stmtGetMeta.get(\"cursor\") as { value: string } | undefined)?.value;\n if (current && !cursorGreaterThan(cursor, current)) return;\n stmtSetMeta.run(\"cursor\", cursor);\n },\n upsertPath(state: LocalPathState) {\n stmtUpsert.run({\n path: state.path,\n last_applied_change_id: state.lastAppliedChangeId,\n last_seen_hash: state.lastSeenHash,\n local_dirty: state.localDirty ? 1 : 0,\n last_scan_at: state.lastScanAt,\n });\n },\n getPath(path: string): LocalPathState | null {\n const row = stmtGet.get(path) as PathRow | undefined;\n if (!row) return null;\n return {\n path: row.path,\n lastAppliedChangeId: row.last_applied_change_id,\n lastSeenHash: row.last_seen_hash,\n localDirty: row.local_dirty !== 0,\n lastScanAt: row.last_scan_at,\n };\n },\n deletePath(path: string) {\n stmtDelete.run(path);\n },\n listPaths(): string[] {\n return (stmtListPaths.all() as Array<{ path: string }>).map((r) => r.path);\n },\n setMeta(key: string, value: string) {\n stmtSetMeta.run(key, value);\n },\n getMeta(key: string): string | null {\n const row = stmtGetMeta.get(key) as { value: string } | undefined;\n return row?.value ?? null;\n },\n close() {\n db.close();\n },\n };\n}\n","/**\n * .tapignore loader.\n *\n * Layers:\n * 1. Built-in defaults — common secrets, dependencies, OS noise, and\n * bulky generated artifacts (per design doc § \"Large File Policy\").\n * A user CAN override these via `!pattern` in their .tapignore.\n * 2. Project .tapignore.\n * 3. Always-excluded paths — Tap's own local metadata. These cannot be\n * re-enabled by `!pattern`; the relay would reject the upload anyway\n * (validateRigPath rejects reserved paths).\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nexport const BUILTIN_IGNORE_LINES: ReadonlyArray<string> = [\n \"# Secrets and machine-local\",\n \".env\",\n \".env.*\",\n \"*.local.*\",\n \"\",\n \"# Dependencies and build output\",\n \"node_modules/\",\n \"dist/\",\n \".venv/\",\n \"__pycache__/\",\n \"*.pyc\",\n \"\",\n \"# Git and OS noise\",\n \".git/\",\n \".DS_Store\",\n \"\",\n \"# Bulky generated artifacts\",\n \"*.mov\",\n \"*.mp4\",\n \"*.zip\",\n \"*.tar.gz\",\n \"*.sqlite\",\n \"\",\n \"# Tap conflict sidecars — local-only by default\",\n \"*.conflict-from.*\",\n];\n\n// `.rig/` as a whole is local-metadata namespace: tap state DB +\n// binding config (this repo) plus rig's per-machine provenance\n// (`.rig/instance.toml`, `.rig/setup.json` — see ~/Code/rig). Nothing\n// in here is portable across machines, so the walker never sees it.\n// Tightening from the original `.rig/tap/`-only exclusion also fixes\n// a latent bug: the walker would push `.rig` to emptyDirs (its only\n// children being always-excluded) and scanAndPush would submit a\n// phantom `mkdir .rig` on every fresh daemon's first scan.\nconst ALWAYS_EXCLUDED_PREFIXES: ReadonlyArray<string> = [\".rig/\"];\n\nexport function loadIgnore(rootDir: string): Ignore {\n const ig = ignore();\n ig.add(BUILTIN_IGNORE_LINES.join(\"\\n\"));\n const projectIgnore = join(rootDir, \".tapignore\");\n if (existsSync(projectIgnore)) {\n ig.add(readFileSync(projectIgnore, \"utf8\"));\n }\n return ig;\n}\n\n/**\n * Hard-exclude check that runs in addition to .tapignore. Tap's own\n * local metadata is invisible to sync no matter what the user puts in\n * their .tapignore — including `!pattern` overrides.\n */\nexport function isAlwaysExcluded(posixPath: string): boolean {\n for (const prefix of ALWAYS_EXCLUDED_PREFIXES) {\n const exact = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (posixPath === exact) return true;\n if (posixPath.startsWith(prefix)) return true;\n }\n return false;\n}\n","/**\n * Recursive directory walker for a rig. Yields:\n * - `files`: candidates for upload (size + posix path relative to root)\n * - `emptyDirs`: paths to mkdir markers (dirs with no tracked children)\n * - `warnings`: paths that won't sync (oversized, symlink, invalid path)\n *\n * Rules enforced:\n * - `.tapignore` (loadIgnore)\n * - Tap's own reserved metadata (isAlwaysExcluded)\n * - MAX_FILE_BYTES (100 MB) — daemon refuses upload above this\n * - Symlinks (lstat-detected) — rejected; warning emitted\n * - validateRigPath — defends against control chars / weird segments\n * before the relay sees them\n *\n * Path strings are POSIX (forward slashes) even on Windows so they\n * match what the relay stores and what the manifest returns.\n */\n\nimport { readdirSync, statSync, type Dirent } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport { MAX_FILE_BYTES, validateRigPath } from \"@tap/core\";\nimport { isAlwaysExcluded } from \"./tapignore.js\";\n\nexport type WalkFile = { path: string; size: number };\nexport type WalkWarning = {\n path: string;\n reason: \"too_large\" | \"symlink\" | \"invalid_path\" | \"unreadable\";\n};\n\nexport type WalkResult = {\n files: WalkFile[];\n emptyDirs: string[];\n /**\n * Every directory visited during the walk, including non-empty ones.\n * Used by `scanAndPush` to detect \"previously-tracked dir no longer\n * on disk\" cases that emptyDirs alone wouldn't catch — e.g. a\n * directory that was empty-and-tracked, then gained files (now\n * non-empty and on disk: should NOT be deleted) vs one that was\n * `rm -rf`d (gone from disk: should be rmdir'd).\n */\n directoriesScanned: string[];\n warnings: WalkWarning[];\n};\n\nexport function walk(rootDir: string, opts: { ignore: Ignore }): WalkResult {\n const files: WalkFile[] = [];\n const emptyDirs: string[] = [];\n const directoriesScanned: string[] = [];\n const warnings: WalkWarning[] = [];\n\n walkDir(\".\");\n\n return { files, emptyDirs, directoriesScanned, warnings };\n\n /** Returns true if this directory contained any tracked child (file or dir). */\n function walkDir(rel: string): boolean {\n const abs = join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n warnings.push({ path: posixOf(rel), reason: \"unreadable\" });\n return false;\n }\n let hasVisible = false;\n for (const ent of entries) {\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n const posix = posixOf(childRel);\n const isDir = ent.isDirectory();\n // ignore.ignores() expects a trailing slash for directories so\n // patterns like `node_modules/` match.\n if (isAlwaysExcluded(posix)) continue;\n if (opts.ignore.ignores(posix + (isDir ? \"/\" : \"\"))) continue;\n\n if (ent.isSymbolicLink()) {\n warnings.push({ path: posix, reason: \"symlink\" });\n continue;\n }\n const pathOk = validateRigPath(posix);\n if (!pathOk.ok) {\n warnings.push({ path: posix, reason: \"invalid_path\" });\n continue;\n }\n if (isDir) {\n directoriesScanned.push(posix);\n const subHas = walkDir(childRel);\n if (!subHas) emptyDirs.push(posix);\n hasVisible = true;\n continue;\n }\n if (ent.isFile()) {\n let size: number;\n try {\n size = statSync(join(abs, ent.name)).size;\n } catch {\n warnings.push({ path: posix, reason: \"unreadable\" });\n continue;\n }\n if (size > MAX_FILE_BYTES) {\n warnings.push({ path: posix, reason: \"too_large\" });\n continue;\n }\n files.push({ path: posix, size });\n hasVisible = true;\n }\n }\n return hasVisible;\n }\n}\n\nfunction posixOf(s: string): string {\n return sep === \"/\" ? s : s.replaceAll(sep, \"/\");\n}\n","/**\n * Capability tokens — the daemon's access primitive. Every sync API\n * request resolves to an `AuthContext`. Roles (owner/editor/viewer) only\n * gate token *minting*; per-request authorization reads the token's\n * scope, not the underlying role.\n */\n\nexport type CapabilityOp = \"read\" | \"write\" | \"subscribe\";\n\nexport type CapabilityToken = {\n tokenHash: string; // sha256 of the secret; secret never stored\n bindingId: string;\n userId: string | null;\n deviceId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n expiresAt: string | null;\n revokedAt: string | null;\n label: string | null;\n createdAt: string;\n};\n\n/**\n * What auth middleware resolves a bearer token into. Carried in\n * request context throughout the relay; never serialized to the wire.\n */\nexport type AuthContext = {\n tokenHash: string;\n bindingId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n userId: string | null;\n deviceId: string;\n};\n\nexport function hasOp(auth: AuthContext, op: CapabilityOp): boolean {\n return auth.ops.includes(op);\n}\n","/**\n * Object record — metadata for one file blob in object storage.\n *\n * Objects are content-addressed by full-file sha256 (no chunking in v1).\n * Composite key is (bindingId, hash). v1 deliberately avoids cross-tenant\n * dedup; each binding has its own object pool, scoping IAM and lifecycle\n * boundaries cleanly.\n *\n * Storage key shape:\n * s3://<bucket>/bindings/<bindingId>/sha256/<first2>/<hash>\n */\n\n/** v1 hard ceiling on a single file's bytes. Daemon refuses oversize files. */\nexport const MAX_FILE_BYTES = 100 * 1024 * 1024;\n\nexport type ObjectRecord = {\n bindingId: string;\n hash: string; // \"sha256:…\"\n size: number;\n storageKey: string; // \"s3://bucket/path…\"\n createdAt: string;\n};\n\n/**\n * Build the canonical storage key for a content-addressed object.\n * Same layout in every environment; only the bucket differs.\n */\nexport function objectStorageKey(bindingId: string, hash: string): string {\n const bare = hash.startsWith(\"sha256:\") ? hash.slice(\"sha256:\".length) : hash;\n const fanout = bare.slice(0, 2);\n return `bindings/${bindingId}/sha256/${fanout}/${bare}`;\n}\n","/**\n * Path safety + case-fold for cross-platform filesystem rules.\n *\n * Rules enforced by `validateRigPath`:\n * - reject absolute paths (leading \"/\")\n * - reject \"..\" anywhere\n * - reject backslash separators (windows-style)\n * - reject embedded NUL or other control characters\n * - reject empty segments and leading/trailing slashes\n * - reject the two paths Tap reserves for local metadata\n *\n * Symlink rejection happens at the daemon (it requires an `lstat`).\n */\n\nconst RESERVED_PATHS = [\n \".rig/tap/\",\n \".rig/tap-binding.local.json\",\n];\n\n/**\n * Lower-case + NFC-normalize. Matches what macOS APFS / Windows NTFS\n * treat as case-equivalent in the common case. Pure ASCII is unchanged.\n * Non-ASCII case-folding edge cases (Turkish dotted I, German ß, etc.)\n * are out of scope; we prefer false-positive collisions over false-negative\n * ones since file *sync* should be stricter than the OS.\n */\nexport function foldPath(path: string): string {\n return path.normalize(\"NFC\").toLowerCase();\n}\n\nexport type PathValidationError =\n | \"empty\"\n | \"absolute\"\n | \"parent_segment\"\n | \"backslash\"\n | \"control_char\"\n | \"empty_segment\"\n | \"trailing_slash\"\n | \"reserved\";\n\nexport function validateRigPath(\n path: string,\n): { ok: true } | { ok: false; reason: PathValidationError } {\n if (path.length === 0) return { ok: false, reason: \"empty\" };\n if (path.startsWith(\"/\")) return { ok: false, reason: \"absolute\" };\n if (path.endsWith(\"/\")) return { ok: false, reason: \"trailing_slash\" };\n if (path.includes(\"\\\\\")) return { ok: false, reason: \"backslash\" };\n // Control chars 0x00–0x1F and 0x7F. Any of these in a filename is\n // a sign of bytes-treated-as-text confusion at minimum.\n // eslint-disable-next-line no-control-regex\n if (/[\u0000-\u001f]/.test(path)) return { ok: false, reason: \"control_char\" };\n const segments = path.split(\"/\");\n for (const seg of segments) {\n if (seg.length === 0) return { ok: false, reason: \"empty_segment\" };\n if (seg === \"..\") return { ok: false, reason: \"parent_segment\" };\n }\n for (const reserved of RESERVED_PATHS) {\n if (path === reserved.replace(/\\/$/, \"\") || path.startsWith(reserved)) {\n return { ok: false, reason: \"reserved\" };\n }\n }\n return { ok: true };\n}\n","/**\n * Sync API path constants and version header.\n *\n * The relay advertises the API version it speaks in every response via\n * `Tap-Sync-Version`. Daemons compare and refuse to talk to incompatible\n * versions.\n */\n\nexport const TAP_SYNC_VERSION = \"1\";\nexport const TAP_SYNC_VERSION_HEADER = \"tap-sync-version\";\n\n// Binding lifecycle\nexport const RIG_BINDINGS_PATH = \"/v1/bindings\";\nexport const RIG_BINDING_PATH = \"/v1/bindings/:bindingId\";\n\n// Sync\nexport const RIG_MANIFEST_PATH = \"/v1/bindings/:bindingId/manifest\";\nexport const RIG_CHANGES_PATH = \"/v1/bindings/:bindingId/changes\";\nexport const RIG_STREAM_PATH = \"/v1/bindings/:bindingId/stream\";\n\n// Objects\nexport const RIG_OBJECTS_PREPARE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/prepare-upload\";\nexport const RIG_OBJECTS_COMPLETE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/complete-upload\";\nexport const RIG_OBJECT_DOWNLOAD_URL_PATH =\n \"/v1/bindings/:bindingId/objects/:hash/download-url\";\n\n// Membership (Phase 3)\nexport const RIG_INVITES_PATH = \"/v1/bindings/:bindingId/invites\";\nexport const RIG_INVITE_ACCEPT_PATH = \"/v1/invites/:secret/accept\";\nexport const RIG_MEMBERS_PATH = \"/v1/bindings/:bindingId/members\";\nexport const RIG_DEVICES_PATH = \"/v1/bindings/:bindingId/devices\";\n","import { randomBytes } from \"node:crypto\";\n\nconst ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\";\n\nexport function shortId(prefix: string, bytes = 6): string {\n const buf = randomBytes(bytes);\n let out = \"\";\n for (let i = 0; i < buf.length; i++) {\n out += ALPHABET[buf[i]! % ALPHABET.length];\n }\n return `${prefix}_${out}`;\n}\n\n// Hosted rig sync. Change-event ids come from Postgres (a bigint\n// sequence, formatted as `chg_<n>` at the wire boundary), so there\n// is no client-side minter for them.\nexport const newBindingId = () => shortId(\"bnd\");\nexport const newDeviceId = () => shortId(\"dev\");\nexport const newInviteId = () => shortId(\"inv\");\n\n/**\n * Capability-token secret. ~192 bits, base64url-encoded. The relay\n * only stores `sha256(secret)`; the secret is returned exactly once,\n * at mint time, and never re-derivable from the DB.\n */\nexport function newCapabilitySecret(): string {\n return `tap_cap_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Invite secret. Same shape and storage discipline as capability\n * secrets — relay stores sha256(secret), plaintext returned once at\n * mint time. URL form: `<baseUrl>/v1/invites/<secret>/accept`.\n */\nexport function newInviteSecret(): string {\n return `tap_inv_${randomBytes(24).toString(\"base64url\")}`;\n}\n","/**\n * Streaming sha256 of a file path, returned in the relay's wire format\n * (\"sha256:\" + 64 hex). Used by walker → uploader and by watcher → uploader.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\n\nexport async function hashFile(absPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n await new Promise<void>((resolve, reject) => {\n const stream = createReadStream(absPath);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve());\n stream.on(\"error\", (err) => reject(err));\n });\n return `sha256:${hash.digest(\"hex\")}`;\n}\n","/**\n * `tapd init` — bootstrap a fresh binding from the current working\n * directory.\n *\n * Steps (mapped to design doc § \"Init Flow\"):\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Walk the rig, applying .tapignore + reserved-path rules.\n * 3. Hash every tracked file (de-duplicated by hash so identical\n * content uploads once).\n * 4. For each unique hash: prepare-upload → PUT bytes → complete-upload.\n * 5. Submit one batched POST /changes with one event per file +\n * one mkdir per empty-dir marker.\n * 6. Write .rig/tap-binding.local.json (mode 0600).\n * 7. Populate .rig/tap/state.local.db with last_applied_change_id\n * per path, and the resulting cursor.\n *\n * Returns a summary with the binding, the owner token (so the caller\n * CLI can print it once), upload + change counts, and any walker\n * warnings (oversized files / symlinks / invalid paths). Caller is\n * responsible for surfacing the warnings to the user.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { CreateBindingResponse, SubmitChangeEvent } from \"@tap/core\";\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\nimport { createBinding, RelayClient } from \"./relay-client.js\";\nimport { openStateDb, type LocalPathState } from \"./state-db.js\";\nimport { loadIgnore } from \"./tapignore.js\";\nimport { walk, type WalkResult } from \"./walker.js\";\nimport { hashFile } from \"./hash.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\n\nexport type InitOptions = {\n rootDir: string;\n relayUrl: string;\n ownerUserId: string;\n bindingName: string;\n deviceLabel?: string;\n /** Test seam: override fetch (used to drive the in-process relay). */\n fetch?: typeof fetch;\n};\n\nexport type InitResult = {\n binding: CreateBindingResponse[\"binding\"];\n device: CreateBindingResponse[\"device\"];\n /** Owner secret. Returned exactly once. The caller should display it. */\n ownerSecret: string;\n uploadedHashes: number;\n reusedHashes: number;\n submittedEvents: number;\n warnings: WalkResult[\"warnings\"];\n /** Cursor written to state.local.db after the initial submit. */\n cursor: string;\n};\n\nexport class AlreadyInitializedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyInitializedError\";\n }\n}\n\nexport async function init(opts: InitOptions): Promise<InitResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyInitializedError(join(opts.rootDir, BINDING_CONFIG_PATH));\n }\n\n // 1. Walk the rig before touching the relay, so we fail fast on a\n // structurally invalid project (e.g. wholly unreadable root).\n const ig = loadIgnore(opts.rootDir);\n const scan = walk(opts.rootDir, { ignore: ig });\n\n // 2. Hash everything. Stream sha256 so a multi-GB rig stays bounded.\n type HashedFile = { path: string; size: number; hash: string };\n const hashed: HashedFile[] = [];\n for (const f of scan.files) {\n const h = await hashFile(join(opts.rootDir, f.path));\n hashed.push({ path: f.path, size: f.size, hash: h });\n }\n\n // 3. Create the binding (the response carries the owner token once).\n // User identity goes in the X-Tap-User-Id header (relay-client\n // handles that), NOT in the request body — Phase 3 dropped\n // ownerUserId from CreateBindingRequest.\n const created = await createBinding({\n baseUrl: opts.relayUrl,\n request: { name: opts.bindingName, deviceLabel: opts.deviceLabel },\n userId: opts.ownerUserId,\n fetch: opts.fetch,\n });\n\n // 3a. Persist the binding config IMMEDIATELY. The owner secret is\n // returned exactly once by the relay; if upload or submit fails\n // downstream, the user can resume via `tapd start` (which scans\n // and pushes anything not yet known to the relay). Without this\n // write, a mid-init failure orphans the binding — token unknown,\n // no way to access from this machine ever again.\n writeBindingConfig(opts.rootDir, {\n bindingId: created.binding.id,\n relayUrl: opts.relayUrl,\n deviceId: created.device.id,\n token: created.token.secret,\n });\n\n const client = new RelayClient({\n baseUrl: opts.relayUrl,\n bindingId: created.binding.id,\n token: created.token.secret,\n fetch: opts.fetch,\n });\n\n // 4. Upload each unique hash exactly once. Identical content across\n // paths (e.g. duplicated assets) reuses the same object.\n const uniqueHashes = new Map<string, HashedFile>();\n for (const f of hashed) {\n if (!uniqueHashes.has(f.hash)) uniqueHashes.set(f.hash, f);\n }\n const mutatedDuringScan = new Set<string>();\n let uploaded = 0;\n let reused = 0;\n for (const [hash, f] of uniqueHashes) {\n const prep = await client.prepareUpload({ hash, size: f.size });\n if (prep.exists) {\n reused += 1;\n continue;\n }\n const bytes = readFileSync(join(opts.rootDir, f.path));\n // Re-verify that the buffer we're PUTting matches the hash we\n // promised the relay. If the file was modified between the\n // streaming hash above and this read, skip the upload and warn —\n // the user can re-run `tapd start` to pick it up.\n const actual = sha256OfBuffer(bytes);\n if (actual !== hash) {\n mutatedDuringScan.add(f.path);\n scan.warnings.push({\n path: f.path,\n reason: \"invalid_path\", // closest existing warning kind; semantics are file-mutated-mid-scan\n });\n continue;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash, size: f.size });\n uploaded += 1;\n }\n\n // 5. Build one batch: writes first, then mkdir markers. Skip writes\n // for any file whose hash failed to upload (mutated mid-scan).\n const events: SubmitChangeEvent[] = [\n ...hashed\n .filter((f) => !mutatedDuringScan.has(f.path))\n .map((f) => ({\n op: \"write\" as const,\n path: f.path,\n hash: f.hash,\n size: f.size,\n })),\n ...scan.emptyDirs.map((path) => ({ op: \"mkdir\" as const, path })),\n ];\n\n let cursor = \"chg_0\";\n let submittedEvents: ReadonlyArray<import(\"@tap/core\").ChangeEvent> = [];\n if (events.length > 0) {\n const submit = await client.submitChanges(events);\n submittedEvents = submit.events;\n cursor = submit.events[submit.events.length - 1]!.id;\n }\n\n // 6. Populate state.local.db. The binding config was written above\n // (step 3a) so the token isn't lost on a mid-init failure.\n const stateDb = openStateDb(\n join(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n const now = new Date().toISOString();\n for (const ev of submittedEvents) {\n const state: LocalPathState = {\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash, // null for mkdirs\n localDirty: false,\n lastScanAt: now,\n };\n stateDb.upsertPath(state);\n }\n stateDb.setCursor(cursor);\n stateDb.setMeta(\"device_id\", created.device.id);\n stateDb.setMeta(\"binding_id\", created.binding.id);\n } finally {\n stateDb.close();\n }\n\n return {\n binding: created.binding,\n device: created.device,\n ownerSecret: created.token.secret,\n uploadedHashes: uploaded,\n reusedHashes: reused,\n submittedEvents: submittedEvents.length,\n warnings: scan.warnings,\n cursor,\n };\n}\n","/**\n * `tapd invite` — owner-side helpers for the invite lifecycle.\n *\n * Reads .rig/tap-binding.local.json for the relay URL + token, builds\n * a RelayClient, and calls the matching endpoint. Designed to be a\n * thin wrapper that the CLI dispatcher (bin.ts) can call without\n * thinking about plumbing.\n */\n\nimport type {\n BindingInvite,\n CapabilityOp,\n CreateInviteRequest,\n CreateInviteResponse,\n Role,\n} from \"@tap/core\";\nimport { NotInitializedError, readBindingConfig } from \"./binding-config.js\";\nimport { RelayClient } from \"./relay-client.js\";\n\n// Re-exported so existing `import { NotInitializedError } from \"./invite.js\"`\n// imports + tests don't need to change. Class lives in binding-config.\nexport { NotInitializedError };\n\nfunction clientFor(rootDir: string, fetchImpl?: typeof fetch): RelayClient {\n const cfg = readBindingConfig(rootDir);\n if (!cfg) throw new NotInitializedError(rootDir);\n return new RelayClient({\n baseUrl: cfg.relayUrl,\n bindingId: cfg.bindingId,\n token: cfg.token,\n fetch: fetchImpl,\n });\n}\n\nexport type MintInviteOptions = {\n rootDir: string;\n ops: ReadonlyArray<CapabilityOp>;\n role?: Role | null;\n pathGlobs?: ReadonlyArray<string>;\n ttlSeconds?: number;\n maxUses?: number;\n emailConstraint?: string;\n label?: string;\n fetch?: typeof fetch;\n};\n\nexport async function mint(opts: MintInviteOptions): Promise<CreateInviteResponse> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const body: CreateInviteRequest = {\n ops: opts.ops,\n ...(opts.role !== undefined ? { role: opts.role } : {}),\n ...(opts.pathGlobs ? { pathGlobs: opts.pathGlobs } : {}),\n ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}),\n ...(opts.maxUses !== undefined ? { maxUses: opts.maxUses } : {}),\n ...(opts.emailConstraint ? { emailConstraint: opts.emailConstraint } : {}),\n ...(opts.label ? { label: opts.label } : {}),\n };\n return client.mintInvite(body);\n}\n\nexport async function list(opts: { rootDir: string; fetch?: typeof fetch }): Promise<BindingInvite[]> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const res = await client.listInvites();\n return res.invites;\n}\n\nexport async function revoke(opts: {\n rootDir: string;\n inviteId: string;\n fetch?: typeof fetch;\n}): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const client = clientFor(opts.rootDir, opts.fetch);\n return client.revokeInvite(opts.inviteId);\n}\n","/**\n * `tapd join` — accept an invite URL and set up a fresh local binding.\n *\n * Unlike `tapd init`, join does NOT touch the rig contents:\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Parse the invite URL → (baseUrl, secret).\n * 3. POST /v1/invites/:secret/accept with the user's identity header,\n * receive {bindingId, device, token}.\n * 4. Write .rig/tap-binding.local.json (mode 0600) IMMEDIATELY — same\n * \"secret-returned-once survives downstream failure\" discipline as\n * `tapd init`.\n * 5. Open .rig/tap/state.local.db with cursor `chg_0`.\n *\n * After join, `tapd start` will:\n * - applyOnce: pull the remote manifest + change log\n * - scanAndPush: push any pre-existing local divergence\n *\n * That covers both the bootstrap-from-empty-dir case and the\n * \"user already has some files locally\" merge case.\n */\n\nimport { join as joinPath } from \"node:path\";\nimport type { AcceptInviteResponse, BindingDevice } from \"@tap/core\";\nimport { acceptInvite } from \"./relay-client.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\nimport { openStateDb } from \"./state-db.js\";\n\nexport type JoinOptions = {\n rootDir: string;\n inviteUrl: string;\n userId: string;\n email?: string;\n deviceLabel?: string;\n /** Test seam: override fetch. */\n fetch?: typeof fetch;\n};\n\nexport type JoinResult = {\n bindingId: string;\n device: BindingDevice;\n /** Capability secret. Returned exactly once. */\n tokenSecret: string;\n /** Whether the invite granted a member role (vs pure-capability). */\n becameMember: boolean;\n};\n\nexport class AlreadyJoinedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyJoinedError\";\n }\n}\n\nexport class InvalidInviteUrlError extends Error {\n constructor(public readonly url: string) {\n super(`invalid invite URL: ${url} (expected <baseUrl>/v1/invites/<secret>/accept)`);\n this.name = \"InvalidInviteUrlError\";\n }\n}\n\n/**\n * Parse `https://relay.example/v1/invites/<secret>/accept` into\n * (baseUrl, secret). Trailing slash + missing /accept both accepted.\n */\nexport function parseInviteUrl(url: string): { baseUrl: string; secret: string } {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new InvalidInviteUrlError(url);\n }\n const match = /^\\/v1\\/invites\\/([^/]+)(?:\\/accept)?\\/?$/.exec(parsed.pathname);\n if (!match) throw new InvalidInviteUrlError(url);\n return {\n baseUrl: `${parsed.protocol}//${parsed.host}`,\n secret: decodeURIComponent(match[1]!),\n };\n}\n\nexport async function join(opts: JoinOptions): Promise<JoinResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyJoinedError(joinPath(opts.rootDir, BINDING_CONFIG_PATH));\n }\n const { baseUrl, secret } = parseInviteUrl(opts.inviteUrl);\n\n const accepted: AcceptInviteResponse = await acceptInvite({\n baseUrl,\n secret,\n userId: opts.userId,\n email: opts.email,\n request: opts.deviceLabel ? { deviceLabel: opts.deviceLabel } : undefined,\n fetch: opts.fetch,\n });\n\n // Persist config FIRST. The token is returned exactly once; if the\n // state-DB open ever fails (unlikely but possible — disk full,\n // permissions), the user can still re-attempt via `tapd start`.\n writeBindingConfig(opts.rootDir, {\n bindingId: accepted.bindingId,\n relayUrl: baseUrl,\n deviceId: accepted.device.id,\n token: accepted.token.secret,\n });\n\n const stateDb = openStateDb(\n joinPath(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n stateDb.setMeta(\"binding_id\", accepted.bindingId);\n stateDb.setMeta(\"device_id\", accepted.device.id);\n // cursor stays at default \"chg_0\" — start's initial applyOnce\n // pulls everything from the manifest.\n } finally {\n stateDb.close();\n }\n\n return {\n bindingId: accepted.bindingId,\n device: accepted.device,\n tokenSecret: accepted.token.secret,\n becameMember: accepted.member !== null,\n };\n}\n","/**\n * Apply remote change events to the local filesystem.\n *\n * Phase 2 keeps this simple: last-write-wins, no conflict materialization\n * (Phase 4 adds it). Echo suppression is handled via the state DB:\n * if the remote event's hash matches our last_seen_hash for the path,\n * we know it's our own write coming back through the poll loop and skip\n * the download.\n *\n * Materialization is atomic: download to `<file>.tap-tmp.<rand>` then\n * `rename` into place. Atomic on POSIX; on Windows, `fs.rename` falls\n * back to MoveFileEx-style replace.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmdirSync,\n rmSync,\n writeFileSync,\n type Dirent,\n} from \"node:fs\";\nimport { basename, dirname, extname, join, sep } from \"node:path\";\nimport type { ChangeEvent } from \"@tap/core\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\n\n/** State-DB meta key — running count of conflicts surfaced by `tapd status`. */\nexport const STATE_KEY_CONFLICT_COUNT = \"conflict_count\";\n\n/**\n * Thrown when downloaded bytes don't hash to the value we trusted via\n * the change event. Caught by applyOnce so the cursor still advances\n * (the daemon would otherwise spin forever).\n */\nexport class HashMismatchError extends Error {\n constructor(\n public readonly path: string,\n public readonly expectedHash: string,\n public readonly actualHash: string,\n ) {\n super(\n `hash mismatch at ${path}: expected ${expectedHash}, got ${actualHash}`,\n );\n this.name = \"HashMismatchError\";\n }\n}\n\nfunction sha256Hex(bytes: Buffer | Uint8Array): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction sha256OfFile(absPath: string): string {\n return `sha256:${sha256Hex(readFileSync(absPath))}`;\n}\n\n/**\n * Sidecar path for a conflict materialization. Original extension is\n * preserved at the end so editor highlighting still works.\n *\n * decisions/pricing.md → decisions/pricing.conflict-from.dev_xyz.chg_456.md\n *\n * Device label resolution lands when GET /v1/bindings/:id/devices does\n * (Phase 5); until then we use the raw deviceId, which is at least an\n * audit-stable reference.\n */\nexport function conflictSidecarPath(\n path: string,\n ev: { id: string; actorDeviceId: string | null },\n): string {\n const dir = dirname(path);\n const ext = extname(path);\n const stem = basename(path, ext);\n const actor = ev.actorDeviceId ?? \"unknown\";\n const sidecar = `${stem}.conflict-from.${actor}.${ev.id}${ext}`;\n return dir === \".\" ? sidecar : `${dir}/${sidecar}`;\n}\n\n/**\n * Inverse of conflictSidecarPath — given a sidecar filename, recover the\n * original path it was forked from. Returns null for any filename that\n * doesn't match the sidecar format. Used by `tapd status` to surface\n * conflicts to rig.\n */\nconst SIDECAR_RE = /^(.*)\\.conflict-from\\.[^.]+\\.(chg_\\d+)(\\.[^.]+)?$/;\nexport function parseConflictSidecar(\n filename: string,\n): { stem: string; ext: string; chgId: string } | null {\n const m = SIDECAR_RE.exec(filename);\n if (!m) return null;\n return { stem: m[1]!, chgId: m[2]!, ext: m[3] ?? \"\" };\n}\n\n/**\n * Recursively walk `rootDir` for `*.conflict-from.*` sidecars. Bypasses\n * .tapignore (sidecars are in the default ignore list — that's why\n * scanAndPush doesn't push them — but we still want `tapd status` to\n * surface them). Skips `.rig/`, `.git/`, `node_modules/` for speed.\n */\nconst SCAN_SKIP_DIRS = new Set([\".git\", \"node_modules\", \".rig\"]);\nexport function findConflictSidecars(\n rootDir: string,\n): Array<{ path: string; sidecarPath: string }> {\n const out: Array<{ path: string; sidecarPath: string }> = [];\n walk(\".\");\n return out;\n\n function walk(rel: string): void {\n const abs = rel === \".\" ? rootDir : join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n return;\n }\n for (const ent of entries) {\n if (ent.isSymbolicLink()) continue;\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n if (ent.isDirectory()) {\n if (SCAN_SKIP_DIRS.has(ent.name)) continue;\n walk(childRel);\n continue;\n }\n if (!ent.isFile()) continue;\n const parsed = parseConflictSidecar(ent.name);\n if (!parsed) continue;\n const sidecarPosix = sep === \"/\" ? childRel : childRel.replaceAll(sep, \"/\");\n const dir = dirname(sidecarPosix);\n const original = dir === \".\" ? `${parsed.stem}${parsed.ext}` : `${dir}/${parsed.stem}${parsed.ext}`;\n out.push({ path: original, sidecarPath: sidecarPosix });\n }\n }\n}\n\nfunction bumpConflictCount(stateDb: StateDb): void {\n const current = Number.parseInt(\n stateDb.getMeta(STATE_KEY_CONFLICT_COUNT) ?? \"0\",\n 10,\n );\n stateDb.setMeta(STATE_KEY_CONFLICT_COUNT, String(current + 1));\n}\n\nexport type ApplyEventError = {\n eventId: string;\n path: string;\n op: string;\n reason: string;\n};\n\nexport type ApplyResult = {\n applied: number;\n echoSkipped: number;\n errored: number;\n events: ReadonlyArray<ChangeEvent>;\n errors: ReadonlyArray<ApplyEventError>;\n};\n\nconst STATE_KEY_LAST_APPLY_ERROR = \"last_apply_error\";\nconst STATE_KEY_LAST_APPLY_ERROR_AT = \"last_apply_error_at\";\n\n/**\n * One iteration of the apply loop: poll `GET /changes?after=<cursor>`,\n * materialize each event, update the cursor.\n *\n * Per-event errors (hash mismatch, file/directory collision, disk full,\n * etc.) are caught, logged, and recorded to the state DB — the cursor\n * still advances past the failed event so the daemon doesn't loop\n * forever on the same broken row. Phase 4 conflict materialization\n * will replace the \"skip + log\" behavior for the file/dir collision\n * case; for now it's the safe default.\n */\nexport async function applyOnce(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n limit?: number;\n}): Promise<ApplyResult> {\n const cursor = opts.stateDb.getCursor();\n const since = await opts.client.listChanges({ after: cursor, limit: opts.limit });\n let applied = 0;\n let echoSkipped = 0;\n let errored = 0;\n const errors: ApplyEventError[] = [];\n\n for (const ev of since.events) {\n try {\n const wasEcho = await applyEvent({\n rootDir: opts.rootDir,\n client: opts.client,\n stateDb: opts.stateDb,\n event: ev,\n });\n if (wasEcho) echoSkipped += 1;\n else applied += 1;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n const recorded: ApplyEventError = {\n eventId: ev.id,\n path: ev.path,\n op: ev.op,\n reason,\n };\n errors.push(recorded);\n errored += 1;\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR, JSON.stringify(recorded));\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR_AT, new Date().toISOString());\n // Surface to the operator. We keep going so a single broken event\n // doesn't wedge the rest of the queue.\n console.error(\n `tapd apply: skipping ${ev.id} (${ev.op} ${ev.path}): ${reason}`,\n );\n }\n // Always advance the cursor, regardless of success/skip/error.\n // Crash-mid-batch resumes from the last-touched event, not the start.\n opts.stateDb.setCursor(ev.id);\n }\n\n return { applied, echoSkipped, errored, events: since.events, errors };\n}\n\nasync function applyEvent(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n event: ChangeEvent;\n}): Promise<boolean> {\n const { rootDir, client, stateDb, event: ev } = opts;\n const abs = join(rootDir, ev.path);\n\n switch (ev.op) {\n case \"write\": {\n if (!ev.hash) throw new Error(`write event ${ev.id} missing hash`);\n const local = stateDb.getPath(ev.path);\n if (local?.lastSeenHash === ev.hash && existsSync(abs)) {\n // Echo: we already have these bytes (likely our own write\n // coming back through the change stream).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return true;\n }\n\n // File-vs-directory collision: refuse to clobber a local directory\n // with a file write. Caught by applyOnce, recorded, cursor still\n // advances.\n if (existsSync(abs) && lstatSync(abs).isDirectory()) {\n throw new Error(\n `file_vs_dir_collision: ${ev.path} is a local directory; refusing to overwrite with a file`,\n );\n }\n\n // Local-divergence detection. If the local file's bytes differ\n // from our recorded last_seen_hash AND we have a recorded\n // baseline (i.e., we've seen this path before), the user edited\n // locally without pushing yet. Applying the remote would\n // overwrite those edits — instead, materialize the remote as a\n // conflict sidecar and leave local alone. The next scanAndPush\n // pushes the local edit upstream as a normal change event.\n const localDirty =\n existsSync(abs) &&\n local?.lastSeenHash != null &&\n sha256OfFile(abs) !== local.lastSeenHash;\n\n const dl = await client.downloadUrl(ev.hash);\n const bytes = await client.getObjectBytes(dl.downloadUrl);\n // Hash-verify the downloaded bytes BEFORE any disk touch.\n const actualHash = `sha256:${sha256Hex(bytes)}`;\n if (actualHash !== ev.hash) {\n throw new HashMismatchError(ev.path, ev.hash, actualHash);\n }\n\n if (localDirty) {\n const sidecarPath = conflictSidecarPath(ev.path, ev);\n const sidecarAbs = join(rootDir, sidecarPath);\n mkdirSync(dirname(sidecarAbs), { recursive: true });\n const tmp = `${sidecarAbs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: ev.executable ? 0o755 : 0o644 });\n renameSync(tmp, sidecarAbs);\n // Cursor advances + lastAppliedChangeId updates, but\n // lastSeenHash is NOT updated — local file still differs from\n // baseline, so the next scanAndPush sees the unpushed local\n // edit and submits it (with baseChangeId = ev.id, so the relay\n // can record the causality).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: local!.lastSeenHash,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n return false;\n }\n\n mkdirSync(dirname(abs), { recursive: true });\n // Atomic rename — tmp file in the same dir so it's the same fs/volume.\n const tmp = `${abs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: ev.executable ? 0o755 : 0o644 });\n renameSync(tmp, abs);\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"delete\": {\n if (existsSync(abs)) {\n if (lstatSync(abs).isDirectory()) {\n throw new Error(\n `delete_vs_dir: ${ev.path} is a local directory; refusing recursive removal`,\n );\n }\n // Symmetric to write: if the user edited the file locally\n // since we last synced (or if there's no baseline at all but\n // the file is on disk), don't destroy their unpushed work.\n // Keep the file. Record state with lastSeenHash=null and\n // lastAppliedChangeId = ev.id so the next scanAndPush detects\n // the file as \"needs push\" (hash !== null), submits a write\n // event with baseChangeId = ev.id (the delete), and the relay\n // resurrects the file on peers with proper causality.\n const localStateRow = stateDb.getPath(ev.path);\n const localBytesHash = sha256OfFile(abs);\n const localDirty =\n localStateRow?.lastSeenHash != null &&\n localBytesHash !== localStateRow.lastSeenHash;\n const localUntracked = localStateRow?.lastSeenHash == null;\n if (localDirty || localUntracked) {\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n // null so scanAndPush's `prev?.lastSeenHash === hash` check\n // falls through and the file gets re-submitted as a write.\n lastSeenHash: null,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n // Cursor advance happens in applyOnce regardless of return value.\n return false;\n }\n rmSync(abs, { force: true });\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n case \"mkdir\": {\n mkdirSync(abs, { recursive: true });\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: null,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"rmdir\": {\n // Best-effort, non-recursive: if the user has put new content\n // inside since the rmdir was issued, leave it alone. rmdirSync is\n // the right primitive — fs.rmSync requires recursive:true for any\n // directory, even an empty one.\n try {\n rmdirSync(abs);\n } catch {\n // ignore — directory was non-empty or already gone\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n }\n}\n","/**\n * SSE consumer for the relay's /stream endpoint.\n *\n * Phase 4c — push delivery so applyOnce fires within milliseconds of a\n * remote change instead of waiting on the poll interval. The poll loop\n * remains the source of truth and keeps running underneath as a\n * fallback (slower cadence is fine when SSE is healthy).\n *\n * Reconnect semantics:\n * - Server-side disconnect (stream end) or client-side fetch error\n * → backoff 1s, 2s, 4s, 8s, 16s, capped at 30s; reset on success.\n * - Heartbeat watchdog: if no event/heartbeat lands within\n * `heartbeatTimeoutMs` (default 45s), abort + reconnect.\n * - `stop()` aborts the in-flight fetch + cancels pending reconnects.\n *\n * The subscriber NEVER advances the cursor — it just nudges applyOnce.\n * Cursor management stays in the apply loop, which reads /changes as\n * the durable source of truth.\n */\n\nimport type { ChangeEvent } from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nexport type SseSubscriberOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n onChange: (change: ChangeEvent) => void | Promise<void>;\n onSubscribed?: (info: { bindingId: string; pathGlobs: string[] }) => void;\n onError?: (err: Error) => void;\n fetch?: FetchLike;\n /** Watchdog window. Default 45s — matches a server heartbeat of 15s × 3. */\n heartbeatTimeoutMs?: number;\n /** Backoff for the Nth reconnect attempt (0-indexed). */\n reconnectDelayMs?: (attempt: number) => number;\n};\n\nexport type SseSubscriber = {\n stop(): Promise<void>;\n};\n\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\nfunction defaultBackoff(attempt: number): number {\n return Math.min(30_000, 1_000 * 2 ** attempt);\n}\n\nexport function subscribe(opts: SseSubscriberOptions): SseSubscriber {\n const fetchImpl = opts.fetch ?? fetch;\n const heartbeatTimeoutMs = opts.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n const backoff = opts.reconnectDelayMs ?? defaultBackoff;\n\n let stopped = false;\n let attempt = 0;\n let activeAbort: AbortController | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let loop: Promise<void>;\n\n const surface = (err: Error) => {\n if (opts.onError) opts.onError(err);\n else console.error(\"tapd sse:\", err.message);\n };\n\n const connect = async (): Promise<void> => {\n if (stopped) return;\n const ac = new AbortController();\n activeAbort = ac;\n const url = `${opts.baseUrl.replace(/\\/+$/, \"\")}/v1/bindings/${encodeURIComponent(opts.bindingId)}/stream`;\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method: \"GET\",\n headers: {\n accept: \"text/event-stream\",\n authorization: `Bearer ${opts.token}`,\n },\n signal: ac.signal,\n });\n } catch (err) {\n if (stopped) return;\n surface(asError(err, \"sse_connect_failed\"));\n scheduleReconnect();\n return;\n }\n\n if (!res.ok || !res.body) {\n surface(new Error(`sse_bad_status:${res.status}`));\n scheduleReconnect();\n return;\n }\n\n // Success — reset backoff so a subsequent disconnect retries promptly.\n attempt = 0;\n await pumpStream(res.body, ac);\n if (stopped) return;\n // Stream ended without error → reconnect.\n scheduleReconnect();\n };\n\n const pumpStream = async (\n body: ReadableStream<Uint8Array>,\n ac: AbortController,\n ): Promise<void> => {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lastTick = Date.now();\n\n // Watchdog: bail out if no chunk arrives within heartbeatTimeoutMs.\n const watchdog = setInterval(() => {\n if (Date.now() - lastTick > heartbeatTimeoutMs) {\n surface(new Error(\"sse_heartbeat_timeout\"));\n ac.abort();\n }\n }, Math.max(1_000, heartbeatTimeoutMs / 5));\n\n try {\n while (!stopped) {\n let result: Awaited<ReturnType<typeof reader.read>>;\n try {\n result = await reader.read();\n } catch (err) {\n if (stopped) break;\n surface(asError(err, \"sse_read_failed\"));\n break;\n }\n if (result.done) break;\n lastTick = Date.now();\n buffer += decoder.decode(result.value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n await dispatchFrame(frame);\n }\n }\n } finally {\n clearInterval(watchdog);\n try {\n await reader.cancel();\n } catch {\n /* already cancelled */\n }\n }\n };\n\n const dispatchFrame = async (frame: string): Promise<void> => {\n if (frame.trim().length === 0) return;\n let event: string | undefined;\n let data: string | undefined;\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue;\n if (line.startsWith(\"event: \")) event = line.slice(7);\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n }\n if (!event || !data) return;\n try {\n if (event === \"subscribed\" && opts.onSubscribed) {\n opts.onSubscribed(JSON.parse(data) as { bindingId: string; pathGlobs: string[] });\n } else if (event === \"change_created\") {\n await opts.onChange(JSON.parse(data) as ChangeEvent);\n } else if (event === \"heartbeat\") {\n // Already accounted for via watchdog tick reset.\n }\n } catch (err) {\n surface(asError(err, `sse_handler_failed:${event}`));\n }\n };\n\n const scheduleReconnect = () => {\n if (stopped) return;\n const delay = backoff(attempt++);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n void connect();\n }, delay);\n };\n\n loop = connect();\n\n return {\n async stop() {\n stopped = true;\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (activeAbort) activeAbort.abort();\n await loop.catch(() => {});\n },\n };\n}\n\nfunction asError(err: unknown, fallbackName: string): Error {\n if (err instanceof Error) return err;\n const e = new Error(String(err));\n e.name = fallbackName;\n return e;\n}\n","/**\n * Local → remote sync direction: compute the diff between the file\n * system and the state DB, upload any new bytes, submit one batched\n * `POST /changes`.\n *\n * Phase 2 calls this on startup (initial reconcile) and on each\n * watcher event (debounced).\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport type { ChangeEvent, SubmitChangeEvent } from \"@tap/core\";\nimport { hashFile } from \"./hash.js\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\nimport { walk } from \"./walker.js\";\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\n\nexport type ScanAndPushResult = {\n /** Bytes uploaded this cycle. */\n uploaded: number;\n /** Hashes that were already on the relay (no PUT needed). */\n reused: number;\n /** Change events submitted (writes + deletes + mkdir + rmdir). */\n submitted: ReadonlyArray<ChangeEvent>;\n /** Warnings from the walker (symlinks / oversize / invalid) +\n * mid-scan mutations detected before upload. */\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\n/**\n * Pure-read diff: what would scanAndPush submit right now, if called?\n * Hashes every visible file (cost: one read pass), compares against\n * state DB. No relay calls, no writes. Both scanAndPush and `tapd\n * status` use this — keeps the \"is sync clean?\" gate honest because\n * status sees exactly what the next push will see.\n */\nexport type LocalDiff = {\n writes: ReadonlyArray<{\n path: string;\n size: number;\n hash: string;\n baseChangeId?: string;\n }>;\n deletes: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n mkdirs: ReadonlyArray<string>;\n /** Explicit rmdirs carry baseChangeId; synthesized ones don't. */\n rmdirs: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\nexport async function computeLocalDiff(opts: {\n rootDir: string;\n stateDb: StateDb;\n ignore: Ignore;\n}): Promise<LocalDiff> {\n const { rootDir, stateDb, ignore } = opts;\n const scan = walk(rootDir, { ignore });\n\n // Writes — every file whose hash differs from the state DB's last-seen.\n const writes: Array<{ path: string; size: number; hash: string; baseChangeId?: string }> = [];\n for (const f of scan.files) {\n const hash = await hashFile(join(rootDir, f.path));\n const prev = stateDb.getPath(f.path);\n if (prev?.lastSeenHash === hash) continue;\n const baseChangeId = prev?.lastAppliedChangeId ?? undefined;\n writes.push({ path: f.path, size: f.size, hash, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n\n // Deletes + rmdirs. A tracked path is \"still on disk\" iff it appears\n // as a file in this scan OR appears as ANY directory in this scan\n // (including non-empty ones — `directoriesScanned` covers both).\n // Using `emptyDirs` alone would mis-classify a previously-empty\n // tracked dir that now contains files as \"deleted\".\n const filesNow = new Set(scan.files.map((f) => f.path));\n const dirsNow = new Set(scan.directoriesScanned);\n const deletes: Array<{ path: string; baseChangeId?: string }> = [];\n const explicitRmdirs: Array<{ path: string; baseChangeId?: string }> = [];\n const removedPaths = new Set<string>();\n for (const path of stateDb.listPaths()) {\n const prev = stateDb.getPath(path);\n if (!prev) continue;\n const stillOnDisk = filesNow.has(path) || dirsNow.has(path);\n if (stillOnDisk) continue;\n const baseChangeId = prev.lastAppliedChangeId ?? undefined;\n if (prev.lastSeenHash === null) {\n explicitRmdirs.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n } else {\n deletes.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n removedPaths.add(path);\n }\n\n // Synthesize rmdir for ancestor dirs of removed paths that are no\n // longer on disk. The walker only emits `emptyDirs` for currently-\n // visible dirs, so a `rm -rf docs/` that wiped a previously-untracked\n // (because non-empty) directory tree wouldn't produce an rmdir for\n // `docs/` without this step.\n const synthesizedRmdirs = new Set<string>();\n for (const path of removedPaths) {\n let parent = parentDir(path);\n while (parent !== null) {\n if (filesNow.has(parent) || dirsNow.has(parent)) break;\n if (synthesizedRmdirs.has(parent)) break;\n if (!removedPaths.has(parent)) synthesizedRmdirs.add(parent);\n parent = parentDir(parent);\n }\n }\n // Deepest-first so peer-side best-effort rmSync succeeds.\n const synthDeepest = [...synthesizedRmdirs].sort(\n (a, b) => b.split(\"/\").length - a.split(\"/\").length,\n );\n\n // Empty-dir markers: anything in scan.emptyDirs that doesn't already\n // have a state entry.\n const mkdirs: string[] = [];\n for (const dir of scan.emptyDirs) {\n if (stateDb.getPath(dir)) continue;\n mkdirs.push(dir);\n }\n\n // Combine explicit + synthesized rmdirs into one stream; explicit\n // first so they retain their baseChangeId at submit time, then\n // synthesized deepest-first so peer rmSync(recursive:false) succeeds.\n const rmdirs = [\n ...explicitRmdirs,\n ...synthDeepest.map((path) => ({ path })),\n ];\n\n return {\n writes,\n deletes,\n mkdirs,\n rmdirs,\n warnings: scan.warnings.map((w) => ({ path: w.path, reason: w.reason })),\n };\n}\n\nexport async function scanAndPush(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n ignore: Ignore;\n}): Promise<ScanAndPushResult> {\n const { rootDir, client, stateDb } = opts;\n const diff = await computeLocalDiff(opts);\n\n // Dedupe writes by hash so identical content uploads exactly once.\n const uniqueHashes = new Map<string, { path: string; size: number; hash: string }>();\n for (const w of diff.writes) if (!uniqueHashes.has(w.hash)) uniqueHashes.set(w.hash, w);\n\n // Files whose content changed between the walker's streaming hash and\n // our `readFileSync` for PUT. We skip those — the next watcher tick\n // re-hashes with the current bytes.\n const mutatedPaths = new Set<string>();\n const skipWarnings: Array<{ path: string; reason: string }> = [];\n\n let uploaded = 0;\n let reused = 0;\n for (const [hash, f] of uniqueHashes) {\n const prep = await client.prepareUpload({ hash, size: f.size });\n if (prep.exists) {\n reused += 1;\n continue;\n }\n const bytes = readFileSync(join(rootDir, f.path));\n const actual = sha256OfBuffer(bytes);\n if (actual !== hash) {\n mutatedPaths.add(f.path);\n skipWarnings.push({ path: f.path, reason: `mutated_during_scan:${actual}` });\n continue;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash, size: f.size });\n uploaded += 1;\n }\n\n // Build the submit batch. Skip writes whose dedup-representative hash\n // didn't actually upload (the file mutated mid-scan).\n const events: SubmitChangeEvent[] = [];\n for (const w of diff.writes) {\n if (mutatedPaths.has(w.path)) continue;\n const repPath = (uniqueHashes.get(w.hash) ?? w).path;\n if (mutatedPaths.has(repPath)) continue;\n events.push({\n op: \"write\",\n path: w.path,\n hash: w.hash,\n size: w.size,\n ...(w.baseChangeId ? { baseChangeId: w.baseChangeId } : {}),\n });\n }\n for (const d of diff.deletes) {\n events.push({\n op: \"delete\",\n path: d.path,\n ...(d.baseChangeId ? { baseChangeId: d.baseChangeId } : {}),\n });\n }\n // Explicit rmdirs (with baseChangeId) come first then synth-deepest-\n // first, which is what we want on the wire too — emit in that order.\n for (const r of diff.rmdirs) {\n events.push({ op: \"rmdir\", path: r.path, ...(r.baseChangeId ? { baseChangeId: r.baseChangeId } : {}) });\n }\n for (const path of diff.mkdirs) {\n events.push({ op: \"mkdir\", path });\n }\n\n let submitted: ReadonlyArray<ChangeEvent> = [];\n if (events.length > 0) {\n const result = await client.submitChanges(events);\n submitted = result.events;\n const now = new Date().toISOString();\n for (const ev of submitted) {\n if (ev.op === \"delete\" || ev.op === \"rmdir\") {\n stateDb.deletePath(ev.path);\n continue;\n }\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.op === \"write\" ? ev.hash : null,\n localDirty: false,\n lastScanAt: now,\n });\n }\n // Advance cursor past our own writes so the apply loop doesn't\n // re-fetch them as \"remote\" events.\n const lastId = submitted[submitted.length - 1]!.id;\n if (cursorLessThan(stateDb.getCursor(), lastId)) {\n stateDb.setCursor(lastId);\n }\n }\n\n return {\n uploaded,\n reused,\n submitted,\n warnings: [...diff.warnings, ...skipWarnings],\n };\n}\n\n/** Returns the immediate parent of a POSIX path, or null at the root. */\nfunction parentDir(posix: string): string | null {\n const i = posix.lastIndexOf(\"/\");\n if (i < 0) return null;\n return posix.slice(0, i);\n}\n\nfunction cursorLessThan(a: string, b: string): boolean {\n const prefix = \"chg_\";\n const an = a.startsWith(prefix) ? BigInt(a.slice(prefix.length)) : 0n;\n const bn = b.startsWith(prefix) ? BigInt(b.slice(prefix.length)) : 0n;\n return an < bn;\n}\n","/**\n * `tapd start` — the daemon main loop.\n *\n * Three concurrent jobs (Phase 4):\n * - SSE subscriber: opens /v1/bindings/:id/stream and triggers\n * applyOnce on every push. Fast path — latency is network +\n * download time, not pollSeconds.\n * - poll loop: every `pollSeconds`, runs applyOnce as a fallback\n * for SSE outages (and for multi-relay deployments where the SSE\n * bus is single-process; see relay's event-bus.ts).\n * - watcher: chokidar emits add / change / unlink / addDir / unlinkDir.\n * Debounced rescan calls `scanAndPush` so file diffs flow upstream.\n *\n * All three share the state DB and the same RelayClient. Echo\n * suppression in `applyEvent` prevents a self-write from being applied\n * back over the same bytes.\n */\n\nimport { join } from \"node:path\";\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport { applyOnce, type ApplyResult } from \"./applier.js\";\nimport { readBindingConfig } from \"./binding-config.js\";\nimport { RelayClient } from \"./relay-client.js\";\nimport { subscribe, type SseSubscriber } from \"./sse.js\";\nimport { openStateDb, type StateDb } from \"./state-db.js\";\nimport { loadIgnore, isAlwaysExcluded } from \"./tapignore.js\";\nimport { scanAndPush, type ScanAndPushResult } from \"./uploader.js\";\n\nexport type StartOptions = {\n rootDir: string;\n pollSeconds?: number;\n /** Quiescence window before the watcher triggers a rescan. */\n debounceMs?: number;\n /** Test seam — override fetch (passed to RelayClient + SSE subscriber). */\n fetch?: typeof fetch;\n /** Test seam — disable the chokidar watcher (poll loop only). */\n noWatch?: boolean;\n /** Test seam — disable SSE (poll-only mode). */\n noSse?: boolean;\n /** SSE watchdog window in ms. Default 45s. */\n sseHeartbeatTimeoutMs?: number;\n};\n\nexport type DaemonHandle = {\n /** Cleanly shut down the watcher + poll loop + close the state DB. */\n stop(): Promise<void>;\n /** Force one apply cycle now. Useful for tests + `tapd status`. */\n applyNow(): Promise<ApplyResult>;\n /** Force one upstream scan+push now. Useful for tests + `tapd status`. */\n scanNow(): Promise<ScanAndPushResult>;\n /** Underlying state DB. Caller MUST NOT close it — stop() does that. */\n stateDb: StateDb;\n};\n\nconst DEFAULT_POLL_SECONDS = 3;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nexport async function start(opts: StartOptions): Promise<DaemonHandle> {\n const config = readBindingConfig(opts.rootDir);\n if (!config) {\n throw new Error(\n `no binding config at ${opts.rootDir}/.rig/tap-binding.local.json — run \\`tapd init\\` first`,\n );\n }\n\n const stateDb = openStateDb(\n join(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n const ignore = loadIgnore(opts.rootDir);\n const client = new RelayClient({\n baseUrl: config.relayUrl,\n bindingId: config.bindingId,\n token: config.token,\n fetch: opts.fetch,\n });\n\n const pollSeconds = opts.pollSeconds ?? DEFAULT_POLL_SECONDS;\n const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let stopped = false;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let scanTimer: ReturnType<typeof setTimeout> | undefined;\n let sseApplyTimer: ReturnType<typeof setTimeout> | undefined;\n let watcher: FSWatcher | undefined;\n let scanInFlight: Promise<unknown> = Promise.resolve();\n let sseSubscriber: SseSubscriber | undefined;\n\n // ────────── apply mutex ──────────\n // SSE and poll both want to trigger applyOnce. Running them\n // concurrently is a correctness bug: applyOnce reads the cursor\n // once at the top and writes per-event, so two overlapping runs\n // re-fetch + re-materialize the same events, double-counting\n // conflicts. We serialize: at most one applyOnce in flight; if a\n // trigger arrives mid-flight, set a \"rerun-after\" flag so the next\n // cycle picks up whatever landed since.\n let applyInFlight: Promise<ApplyResult | undefined> | null = null;\n let rerunRequested = false;\n let lastApplyResult: ApplyResult | undefined;\n const triggerApply = (): Promise<ApplyResult | undefined> => {\n if (applyInFlight) {\n rerunRequested = true;\n return applyInFlight;\n }\n const loop = (async () => {\n try {\n do {\n rerunRequested = false;\n try {\n lastApplyResult = await applyOnce({\n rootDir: opts.rootDir,\n client,\n stateDb,\n });\n } catch (err) {\n console.error(\"tapd apply error:\", err);\n }\n } while (rerunRequested && !stopped);\n return lastApplyResult;\n } finally {\n applyInFlight = null;\n }\n })();\n applyInFlight = loop;\n return loop;\n };\n\n // Run one poll then schedule the next — `setTimeout` chain instead of\n // `setInterval` so a slow cycle doesn't queue up overlapping calls.\n const schedulePoll = () => {\n if (stopped) return;\n pollTimer = setTimeout(() => {\n triggerApply().finally(() => schedulePoll());\n }, pollSeconds * 1000);\n };\n\n const scheduleScan = () => {\n if (stopped) return;\n if (scanTimer) clearTimeout(scanTimer);\n scanTimer = setTimeout(() => {\n scanInFlight = scanAndPush({ rootDir: opts.rootDir, client, stateDb, ignore })\n .catch((err) => {\n console.error(\"tapd scan error:\", err);\n });\n }, debounceMs);\n };\n\n // SSE-triggered apply is debounced — bursts of remote events should\n // coalesce into one apply instead of one per event. Goes through the\n // same mutex as the poll loop.\n const SSE_APPLY_DEBOUNCE_MS = 50;\n const scheduleSseApply = () => {\n if (stopped) return;\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n sseApplyTimer = setTimeout(() => {\n sseApplyTimer = undefined;\n void triggerApply();\n }, SSE_APPLY_DEBOUNCE_MS);\n };\n\n if (!opts.noSse) {\n sseSubscriber = subscribe({\n baseUrl: config.relayUrl,\n bindingId: config.bindingId,\n token: config.token,\n fetch: opts.fetch,\n heartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n onChange: () => scheduleSseApply(),\n onError: (err) => {\n // Single-line log; SSE retries on its own.\n console.error(\"tapd sse:\", err.message);\n },\n });\n }\n\n if (!opts.noWatch) {\n watcher = chokidar.watch(opts.rootDir, {\n ignoreInitial: true,\n // Ignore tap's own state files at the watcher level too — saves\n // wakeups from sqlite WAL churn.\n ignored: (path: string) => {\n const rel = path.startsWith(opts.rootDir)\n ? path.slice(opts.rootDir.length).replace(/^[/\\\\]/, \"\")\n : path;\n if (isAlwaysExcluded(rel)) return true;\n return false;\n },\n // Reasonable defaults for laptop workloads.\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },\n atomic: true,\n });\n for (const ev of [\"add\", \"change\", \"unlink\", \"addDir\", \"unlinkDir\"] as const) {\n watcher.on(ev, () => scheduleScan());\n }\n }\n\n // Startup reconciliation: apply remote changes first, then scan the\n // local FS for offline edits and push them upstream. The order matters —\n // applying first means an offline-edited file whose hash differs from\n // both the remote AND the state DB results in \"local wins\" (the local\n // bytes get uploaded). Reverse order would briefly overwrite the local\n // edits with the remote version before re-uploading.\n await triggerApply();\n\n // Catch up on edits made while the daemon was stopped. chokidar's\n // `ignoreInitial: true` means existing files don't generate events\n // when the watcher starts, so without this step a daemon that was\n // off when the user saved would never push those changes until the\n // next watcher tick on any file.\n scanInFlight = scanAndPush({ rootDir: opts.rootDir, client, stateDb, ignore }).catch((err) => {\n console.error(\"tapd initial scan error:\", err);\n });\n await scanInFlight;\n\n schedulePoll();\n\n return {\n async stop() {\n stopped = true;\n if (pollTimer) clearTimeout(pollTimer);\n if (scanTimer) clearTimeout(scanTimer);\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n if (sseSubscriber) await sseSubscriber.stop();\n await Promise.allSettled([applyInFlight ?? Promise.resolve(), scanInFlight]);\n if (watcher) await watcher.close();\n stateDb.close();\n },\n async applyNow() {\n // Route through the mutex so test-triggered applies serialize\n // with SSE/poll-triggered ones — no double-materialization.\n const result = await triggerApply();\n // triggerApply returns the LAST loop iteration's result; if all\n // iterations errored, fall back to a no-op result so callers can\n // still inspect the shape.\n return (\n result ?? {\n applied: 0,\n echoSkipped: 0,\n errored: 0,\n events: [],\n errors: [],\n }\n );\n },\n async scanNow() {\n return scanAndPush({ rootDir: opts.rootDir, client, stateDb, ignore });\n },\n stateDb,\n };\n}\n"],"mappings":"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","names":["mkdirSync","dirname","existsSync","readFileSync","join","join","join","createHash","readFileSync","join","createHash","join","readFileSync","join","createHash","existsSync","mkdirSync","readdirSync","readFileSync","writeFileSync","dirname","join","sep","walk","tmp","createHash","readFileSync","join","sha256OfBuffer","createHash","ignore","join","readFileSync","join","join","ignore"]}