@wspc/cli 0.1.15 → 0.1.17

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.
package/dist/cli.js CHANGED
@@ -995,6 +995,15 @@ var driveManifestGet = (options) => (options.client ?? client).get({
995
995
  url: "/drive/libraries/{id}/manifest",
996
996
  ...options
997
997
  });
998
+ var driveFileMove = (options) => (options.client ?? client).post({
999
+ security: [{ scheme: "bearer", type: "http" }],
1000
+ url: "/drive/libraries/{id}/files/move",
1001
+ ...options,
1002
+ headers: {
1003
+ "Content-Type": "application/json",
1004
+ ...options.headers
1005
+ }
1006
+ });
998
1007
  var driveFileRestore = (options) => (options.client ?? client).post({
999
1008
  security: [{ scheme: "bearer", type: "http" }],
1000
1009
  url: "/drive/libraries/{id}/files/restore",
@@ -1520,9 +1529,9 @@ function createConsistencyFetch(opts) {
1520
1529
  }
1521
1530
 
1522
1531
  // src/version.ts
1523
- var VERSION = "0.1.15";
1524
- var SPEC_SHA = "bec760cd";
1525
- var SPEC_FETCHED_AT = "2026-07-13T04:21:22.596Z";
1532
+ var VERSION = "0.1.17";
1533
+ var SPEC_SHA = "74d4defc";
1534
+ var SPEC_FETCHED_AT = "2026-07-13T06:40:42.304Z";
1526
1535
  var API_BASE = "https://api.wspc.ai";
1527
1536
 
1528
1537
  // src/index.ts
@@ -3020,7 +3029,7 @@ var driveFileHistoryCommand = new Command29("history").description("Get drive fi
3020
3029
 
3021
3030
  // src/generated/cli/drive/manifest/get.ts
3022
3031
  import { Command as Command30 } from "commander";
3023
- var driveManifestGetCommand = new Command30("get").description("Get a drive library manifest").argument("<id>", "id").option("--limit <value>", "limit").option("--cursor <value>", "cursor").option("--include-deleted <value>", "include_deleted").option("--path-prefix <value>", "path_prefix").action(async (id, opts) => {
3032
+ var driveManifestGetCommand = new Command30("get").description("Get a drive library manifest").argument("<id>", "id").option("--limit <value>", "limit").option("--cursor <value>", "cursor").option("--include-deleted <value>", "include_deleted").option("--path-prefix <value>", "path_prefix").option("--since-cursor <value>", "since_cursor").action(async (id, opts) => {
3024
3033
  const client2 = await loadSdkClient();
3025
3034
  const result = await driveManifestGet({
3026
3035
  client: client2._rawClient,
@@ -3031,7 +3040,8 @@ var driveManifestGetCommand = new Command30("get").description("Get a drive libr
3031
3040
  limit: opts.limit,
3032
3041
  cursor: opts.cursor,
3033
3042
  include_deleted: opts.includeDeleted,
3034
- path_prefix: opts.pathPrefix
3043
+ path_prefix: opts.pathPrefix,
3044
+ since_cursor: opts.sinceCursor
3035
3045
  }
3036
3046
  });
3037
3047
  if (result.error || !result.response?.ok) {
@@ -4851,7 +4861,9 @@ async function expectJsonResult(result) {
4851
4861
  if (result.data == null) throw new Error("empty response");
4852
4862
  return result.data;
4853
4863
  }
4854
- var SYNC_CLIENT_HEADERS = { "x-wspc-client": "drive-sync" };
4864
+ function syncClientHeaders(clientId) {
4865
+ return { "x-wspc-client": clientId === void 0 ? "drive-sync" : `drive-sync/${clientId}` };
4866
+ }
4855
4867
  function driveContentUrl(baseUrl, id) {
4856
4868
  const baseWithTrailingSlash = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
4857
4869
  return new URL(`drive/libraries/${encodeURIComponent(id)}/files/content`, baseWithTrailingSlash);
@@ -4859,6 +4871,7 @@ function driveContentUrl(baseUrl, id) {
4859
4871
  async function createDriveApi(opts = {}) {
4860
4872
  const client2 = await loadSdkClientWithAuthedFetch(opts);
4861
4873
  const rawClient = client2._rawClient;
4874
+ const clientHeaders = syncClientHeaders(opts.clientId);
4862
4875
  return {
4863
4876
  async getLibrary(id) {
4864
4877
  const result = await driveLibraryGet({
@@ -4867,11 +4880,14 @@ async function createDriveApi(opts = {}) {
4867
4880
  });
4868
4881
  return expectJsonResult(result);
4869
4882
  },
4870
- async getManifest(id, cursor) {
4883
+ async getManifest(id, cursor, sinceCursor) {
4884
+ const query = {};
4885
+ if (cursor !== void 0) query.cursor = cursor;
4886
+ if (sinceCursor !== void 0) query.since_cursor = sinceCursor;
4871
4887
  const result = await driveManifestGet({
4872
4888
  client: rawClient,
4873
4889
  path: { id },
4874
- ...cursor ? { query: { cursor } } : {}
4890
+ ...Object.keys(query).length > 0 ? { query } : {}
4875
4891
  });
4876
4892
  return expectJsonResult(result);
4877
4893
  },
@@ -4879,7 +4895,7 @@ async function createDriveApi(opts = {}) {
4879
4895
  const result = await driveFileDelete({
4880
4896
  client: rawClient,
4881
4897
  path: { id },
4882
- headers: SYNC_CLIENT_HEADERS,
4898
+ headers: clientHeaders,
4883
4899
  body: {
4884
4900
  path,
4885
4901
  expected_entry_version: expectedEntryVersion
@@ -4887,6 +4903,19 @@ async function createDriveApi(opts = {}) {
4887
4903
  });
4888
4904
  return expectJsonResult(result);
4889
4905
  },
4906
+ async moveFile(id, fromPath, toPath, expectedEntryVersion) {
4907
+ const result = await driveFileMove({
4908
+ client: rawClient,
4909
+ path: { id },
4910
+ headers: clientHeaders,
4911
+ body: {
4912
+ from_path: fromPath,
4913
+ to_path: toPath,
4914
+ ...expectedEntryVersion === void 0 ? {} : { expected_entry_version: expectedEntryVersion }
4915
+ }
4916
+ });
4917
+ return expectJsonResult(result);
4918
+ },
4890
4919
  async uploadFile(id, path, body, sha256, expectedEntryVersion) {
4891
4920
  const url = driveContentUrl(client2.baseUrl, id);
4892
4921
  url.searchParams.set("path", path);
@@ -4898,7 +4927,7 @@ async function createDriveApi(opts = {}) {
4898
4927
  headers: {
4899
4928
  "content-type": "application/octet-stream",
4900
4929
  "x-drive-content-sha256": sha256,
4901
- ...SYNC_CLIENT_HEADERS
4930
+ ...clientHeaders
4902
4931
  },
4903
4932
  body
4904
4933
  });
@@ -5113,7 +5142,7 @@ function isOptionalIsoTimestamp(value) {
5113
5142
  }
5114
5143
  function isValidDriveState(value) {
5115
5144
  if (!isRecord2(value)) return false;
5116
- if (value.schema_version !== 1 || typeof value.library_id !== "string" || typeof value.created_at !== "string" || typeof value.updated_at !== "string" || !isRecord2(value.entries) || !isRecord2(value.conflicts) || value.realtime !== void 0 && !isDriveRealtimeState(value.realtime)) {
5145
+ if (value.schema_version !== 1 || typeof value.library_id !== "string" || typeof value.created_at !== "string" || typeof value.updated_at !== "string" || !isRecord2(value.entries) || !isRecord2(value.conflicts) || value.realtime !== void 0 && !isDriveRealtimeState(value.realtime) || value.scan_cache !== void 0 && !isRecord2(value.scan_cache) || value.manifest_cursor !== void 0 && typeof value.manifest_cursor !== "string") {
5117
5146
  return false;
5118
5147
  }
5119
5148
  for (const entry of Object.values(value.entries)) {
@@ -5122,8 +5151,16 @@ function isValidDriveState(value) {
5122
5151
  for (const conflict2 of Object.values(value.conflicts)) {
5123
5152
  if (!isDriveConflict(conflict2)) return false;
5124
5153
  }
5154
+ if (value.scan_cache !== void 0) {
5155
+ for (const entry of Object.values(value.scan_cache)) {
5156
+ if (!isDriveScanCacheEntry(entry)) return false;
5157
+ }
5158
+ }
5125
5159
  return true;
5126
5160
  }
5161
+ function isDriveScanCacheEntry(value) {
5162
+ return isRecord2(value) && typeof value.mtime_ms === "number" && typeof value.size_bytes === "number" && typeof value.sha256 === "string";
5163
+ }
5127
5164
 
5128
5165
  // src/handwritten/commands/drive/bind.ts
5129
5166
  async function assertExistingDirectory(path) {
@@ -5158,7 +5195,7 @@ function driveBindCommand() {
5158
5195
  // src/handwritten/commands/drive/sync.ts
5159
5196
  import { Command as Command80 } from "commander";
5160
5197
  import { mkdir as mkdir3, readFile as readFile4, rm as rm3, writeFile as writeFile3 } from "fs/promises";
5161
- import { basename as basename3, dirname as dirname2, join as join6, posix as pathPosix2, resolve as resolve3 } from "path";
5198
+ import { basename as basename3, dirname as dirname2, join as join6, posix as pathPosix3, resolve as resolve3 } from "path";
5162
5199
  import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
5163
5200
 
5164
5201
  // src/handwritten/commands/drive/debug-log.ts
@@ -5204,7 +5241,7 @@ function decideDriveAction(entry, local, remote) {
5204
5241
  const remoteStatus = getRemoteStatus(entry, remote);
5205
5242
  if (!remote) return decideRemoteMissing(localStatus);
5206
5243
  if (!local) return decideLocalMissing(entry, remoteStatus);
5207
- return decideBothPresent(entry, localStatus, remoteStatus);
5244
+ return decideBothPresent(entry, localStatus, remoteStatus, local, remote);
5208
5245
  }
5209
5246
  function decideWithoutBase(local, remote) {
5210
5247
  if (local && !remote) return { type: "upload_create", expectedEntryVersion: 0 };
@@ -5226,7 +5263,8 @@ function decideLocalMissing(entry, remoteStatus) {
5226
5263
  if (remoteStatus === "unchanged") return { type: "delete_remote", expectedEntryVersion: entry.entry_version };
5227
5264
  return { type: "conflict", reason: "remote_changed_before_delete" };
5228
5265
  }
5229
- function decideBothPresent(entry, localStatus, remoteStatus) {
5266
+ function decideBothPresent(entry, localStatus, remoteStatus, local, remote) {
5267
+ const converged = remote.content_sha256 !== void 0 && local.sha256 === remote.content_sha256;
5230
5268
  if (localStatus === "unchanged" && remoteStatus === "content_same_new_version") {
5231
5269
  return { type: "state_only" };
5232
5270
  }
@@ -5234,7 +5272,7 @@ function decideBothPresent(entry, localStatus, remoteStatus) {
5234
5272
  return { type: "conflict", reason: "remote_missing_entry_version" };
5235
5273
  }
5236
5274
  if (localStatus === "unknown" && remoteStatus !== "unchanged") {
5237
- return { type: "conflict", reason: "unknown_local_base_remote_changed" };
5275
+ return converged ? { type: "state_only" } : { type: "conflict", reason: "unknown_local_base_remote_changed" };
5238
5276
  }
5239
5277
  if (localStatus === "unknown") return { type: "conflict", reason: "unknown_local_base" };
5240
5278
  if (localStatus === "unchanged" && remoteStatus === "changed") return { type: "download" };
@@ -5242,7 +5280,7 @@ function decideBothPresent(entry, localStatus, remoteStatus) {
5242
5280
  return { type: "upload_update", expectedEntryVersion: entry.entry_version };
5243
5281
  }
5244
5282
  if (localStatus !== "unchanged" && remoteStatus !== "unchanged") {
5245
- return { type: "conflict", reason: "local_and_remote_changed" };
5283
+ return converged ? { type: "state_only" } : { type: "conflict", reason: "local_and_remote_changed" };
5246
5284
  }
5247
5285
  return { type: "unchanged" };
5248
5286
  }
@@ -5416,8 +5454,7 @@ function mergeText3(base, local, remote) {
5416
5454
  }
5417
5455
  function conflictCopyPath(path, side, timestamp, versionId) {
5418
5456
  const parsed = pathPosix.parse(path);
5419
- const shortVersionId = safeShortVersionId(versionId);
5420
- const fileName = `${parsed.name}.${side}-conflict-${timestamp}-${shortVersionId}${parsed.ext}`;
5457
+ const fileName = `${parsed.name}.${side}-conflict-${timestamp}-${safeVersionId(versionId)}${parsed.ext}`;
5421
5458
  if (parsed.dir === "") {
5422
5459
  return fileName;
5423
5460
  }
@@ -5446,25 +5483,33 @@ function hasTextHint(path, mimeType) {
5446
5483
  function normalizeLines(text) {
5447
5484
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
5448
5485
  }
5449
- function safeShortVersionId(versionId) {
5450
- const safeVersionId = versionId.replace(/[^A-Za-z0-9_-]/g, "_");
5451
- return (safeVersionId.length > 0 ? safeVersionId : "unknown").slice(0, 8);
5486
+ function safeVersionId(versionId) {
5487
+ const safeVersionId2 = versionId.replace(/[^A-Za-z0-9_-]/g, "_");
5488
+ return safeVersionId2.length > 0 ? safeVersionId2 : "unknown";
5452
5489
  }
5453
5490
 
5454
5491
  // src/handwritten/commands/drive/scanner.ts
5455
5492
  import { createHash } from "crypto";
5456
5493
  import { constants as fsConstants } from "fs";
5457
5494
  import { open as open2, readdir, lstat } from "fs/promises";
5458
- import { join as join4 } from "path";
5495
+ import { join as join4, posix as pathPosix2 } from "path";
5459
5496
  async function scanDriveFiles(root, options = {}) {
5460
5497
  const candidates = [];
5461
5498
  const files = {};
5462
- const absRoot = root;
5463
- await walk(absRoot, "");
5499
+ const startDrivePath = options.startDrivePath ?? "";
5500
+ await walk(startDrivePath === "" ? root : join4(root, ...startDrivePath.split("/")), startDrivePath);
5464
5501
  await addNonCollidingFiles(candidates);
5465
5502
  return files;
5466
5503
  async function walk(currentPath, currentDrivePath) {
5467
- const entries = await readdir(currentPath, { withFileTypes: true });
5504
+ let entries;
5505
+ try {
5506
+ entries = await readdir(currentPath, { withFileTypes: true });
5507
+ } catch (error) {
5508
+ if (currentDrivePath === startDrivePath && startDrivePath !== "" && isNotFoundError(error)) return;
5509
+ if (currentDrivePath === "" || !isTransientScanError(error) || !options.onPathError) throw error;
5510
+ await options.onPathError(currentDrivePath, error);
5511
+ return;
5512
+ }
5468
5513
  entries.sort((left, right) => left.name.localeCompare(right.name));
5469
5514
  for (const entry of entries) {
5470
5515
  if (isExcludedRootEntry(currentDrivePath, entry)) {
@@ -5482,22 +5527,34 @@ async function scanDriveFiles(root, options = {}) {
5482
5527
  continue;
5483
5528
  }
5484
5529
  const nextPath = join4(currentPath, entry.name);
5485
- const stats = await lstat(nextPath);
5486
- if (stats.isSymbolicLink()) {
5487
- continue;
5488
- }
5489
- if (stats.isDirectory()) {
5490
- await walk(nextPath, nextDrivePath);
5491
- continue;
5492
- }
5493
- if (!stats.isFile()) {
5494
- continue;
5495
- }
5496
- const digest = await hashDriveFile(nextPath);
5497
- if (!digest) {
5498
- continue;
5530
+ try {
5531
+ const stats = await lstat(nextPath);
5532
+ if (stats.isSymbolicLink()) {
5533
+ continue;
5534
+ }
5535
+ if (stats.isDirectory()) {
5536
+ await walk(nextPath, nextDrivePath);
5537
+ continue;
5538
+ }
5539
+ if (!stats.isFile()) {
5540
+ continue;
5541
+ }
5542
+ const cached = options.cache?.[nextDrivePath];
5543
+ if (cached !== void 0 && cached.mtime_ms === stats.mtimeMs && cached.size_bytes === stats.size) {
5544
+ options.onCacheUpdate?.(nextDrivePath, cached);
5545
+ candidates.push({ path: nextDrivePath, entry: { sha256: cached.sha256, size_bytes: cached.size_bytes } });
5546
+ continue;
5547
+ }
5548
+ const digest = await hashDriveFile(nextPath);
5549
+ if (!digest) {
5550
+ continue;
5551
+ }
5552
+ options.onCacheUpdate?.(nextDrivePath, { mtime_ms: stats.mtimeMs, size_bytes: digest.sizeBytes, sha256: digest.sha256 });
5553
+ candidates.push({ path: nextDrivePath, entry: { sha256: digest.sha256, size_bytes: digest.sizeBytes } });
5554
+ } catch (error) {
5555
+ if (!isTransientScanError(error) || !options.onPathError) throw error;
5556
+ await options.onPathError(nextDrivePath, error);
5499
5557
  }
5500
- candidates.push({ path: nextDrivePath, entry: { sha256: digest.sha256, size_bytes: digest.sizeBytes } });
5501
5558
  }
5502
5559
  }
5503
5560
  async function addNonCollidingFiles(candidates2) {
@@ -5526,6 +5583,75 @@ async function scanDriveFiles(root, options = {}) {
5526
5583
  return currentDrivePath === "" && entry.name === DRIVE_DIR;
5527
5584
  }
5528
5585
  }
5586
+ async function rescanDriveFiles(root, dirtyPaths, options = {}) {
5587
+ const kept = { ...options.cache ?? {} };
5588
+ for (const dirtyPath of new Set(dirtyPaths)) {
5589
+ if (dirtyPath === "" || isInternalSyncArtifactName(pathPosix2.basename(dirtyPath))) continue;
5590
+ try {
5591
+ validateDrivePath(dirtyPath);
5592
+ } catch (error) {
5593
+ if (!options.onPathError) throw error;
5594
+ await options.onPathError(dirtyPath, error);
5595
+ continue;
5596
+ }
5597
+ removePathAndChildren(kept, dirtyPath);
5598
+ const absPath = join4(root, ...dirtyPath.split("/"));
5599
+ let stats;
5600
+ try {
5601
+ stats = await lstat(absPath);
5602
+ } catch (error) {
5603
+ if (isNotFoundError(error)) continue;
5604
+ if (!isTransientScanError(error) || !options.onPathError) throw error;
5605
+ await options.onPathError(dirtyPath, error);
5606
+ continue;
5607
+ }
5608
+ if (stats.isSymbolicLink()) continue;
5609
+ if (stats.isDirectory()) {
5610
+ await scanDriveFiles(root, {
5611
+ ...options,
5612
+ startDrivePath: dirtyPath,
5613
+ onCacheUpdate: (path, entry) => {
5614
+ kept[path] = entry;
5615
+ }
5616
+ });
5617
+ continue;
5618
+ }
5619
+ if (!stats.isFile()) continue;
5620
+ try {
5621
+ const cached = options.cache?.[dirtyPath];
5622
+ if (cached !== void 0 && cached.mtime_ms === stats.mtimeMs && cached.size_bytes === stats.size) {
5623
+ kept[dirtyPath] = cached;
5624
+ continue;
5625
+ }
5626
+ const digest = await hashDriveFile(absPath);
5627
+ if (!digest) continue;
5628
+ kept[dirtyPath] = { mtime_ms: stats.mtimeMs, size_bytes: digest.sizeBytes, sha256: digest.sha256 };
5629
+ } catch (error) {
5630
+ if (!isTransientScanError(error) || !options.onPathError) throw error;
5631
+ await options.onPathError(dirtyPath, error);
5632
+ }
5633
+ }
5634
+ const files = {};
5635
+ for (const [path, entry] of Object.entries(kept)) {
5636
+ options.onCacheUpdate?.(path, entry);
5637
+ files[path] = { sha256: entry.sha256, size_bytes: entry.size_bytes };
5638
+ }
5639
+ return files;
5640
+ }
5641
+ function removePathAndChildren(view, path) {
5642
+ delete view[path];
5643
+ const prefix = `${path}/`;
5644
+ for (const key of Object.keys(view)) {
5645
+ if (key.startsWith(prefix)) delete view[key];
5646
+ }
5647
+ }
5648
+ function isNotFoundError(error) {
5649
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
5650
+ }
5651
+ var TRANSIENT_SCAN_ERROR_CODES = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EBUSY"]);
5652
+ function isTransientScanError(error) {
5653
+ return error instanceof Error && "code" in error && typeof error.code === "string" && TRANSIENT_SCAN_ERROR_CODES.has(error.code);
5654
+ }
5529
5655
  function isInternalSyncArtifactName(name) {
5530
5656
  if (!name.startsWith(".") || !name.endsWith(".tmp")) return false;
5531
5657
  return name.includes(".wspc-download-") || name.includes(".wspc-backup-") || name.includes(".wspc-conflict-") || name.includes(".wspc-merge-");
@@ -5580,7 +5706,7 @@ import { Readable as Readable2, Transform } from "stream";
5580
5706
  import { pipeline as pipeline2 } from "stream/promises";
5581
5707
  async function assertLocalStillScanned(localPath, scanned) {
5582
5708
  const snapshot = await hashDriveFile(localPath).catch((error) => {
5583
- if (isNotFoundError(error)) return void 0;
5709
+ if (isNotFoundError2(error)) return void 0;
5584
5710
  throw error;
5585
5711
  });
5586
5712
  if (!snapshot || snapshot.sha256 !== scanned.sha256 || snapshot.sizeBytes !== scanned.size_bytes) {
@@ -5608,7 +5734,7 @@ async function installMergedLocalFile(root, path, tmp, scanned, mergedSha256, me
5608
5734
  await rename2(target, backup);
5609
5735
  onLocalMutation();
5610
5736
  } catch (error) {
5611
- if (isNotFoundError(error)) {
5737
+ if (isNotFoundError2(error)) {
5612
5738
  throw new Error("local file changed after scan");
5613
5739
  }
5614
5740
  throw error;
@@ -5646,10 +5772,10 @@ async function restoreMergedLocalFile(target, backup, backupSha256, backupSizeBy
5646
5772
  try {
5647
5773
  await rename2(target, quarantine);
5648
5774
  } catch (error) {
5649
- if (!isNotFoundError(error)) throw error;
5775
+ if (!isNotFoundError2(error)) throw error;
5650
5776
  }
5651
5777
  const quarantineDigest = await hashDriveFile(quarantine).catch((error) => {
5652
- if (isNotFoundError(error)) return void 0;
5778
+ if (isNotFoundError2(error)) return void 0;
5653
5779
  throw error;
5654
5780
  });
5655
5781
  if (quarantineDigest !== void 0 && (quarantineDigest.sha256 !== mergedSha256 || quarantineDigest.sizeBytes !== mergedSizeBytes)) {
@@ -5711,7 +5837,7 @@ async function installDownloadedFile(root, path, tmp, entry, onLocalMutation) {
5711
5837
  await rename2(target, backup);
5712
5838
  onLocalMutation();
5713
5839
  } catch (error) {
5714
- if (!isNotFoundError(error)) throw error;
5840
+ if (!isNotFoundError2(error)) throw error;
5715
5841
  await installNoOverwrite(tmp, target, onLocalMutation);
5716
5842
  return;
5717
5843
  }
@@ -5756,7 +5882,7 @@ async function removeLocalIfStillBase(root, path, entry, onLocalMutation) {
5756
5882
  await rename2(target, backup);
5757
5883
  onLocalMutation();
5758
5884
  } catch (error) {
5759
- if (isNotFoundError(error)) {
5885
+ if (isNotFoundError2(error)) {
5760
5886
  throw new Error("local file changed before delete");
5761
5887
  }
5762
5888
  throw error;
@@ -5794,13 +5920,13 @@ async function restoreBackupWhenPossible(backup, target) {
5794
5920
  return true;
5795
5921
  } catch (error) {
5796
5922
  if (isAlreadyExistsError(error)) return false;
5797
- if (isNotFoundError(error)) return true;
5923
+ if (isNotFoundError2(error)) return true;
5798
5924
  return false;
5799
5925
  }
5800
5926
  }
5801
5927
  async function localFileExists(path) {
5802
5928
  const digest = await hashDriveFile(path).catch((error) => {
5803
- if (isNotFoundError(error)) return void 0;
5929
+ if (isNotFoundError2(error)) return void 0;
5804
5930
  throw error;
5805
5931
  });
5806
5932
  return digest !== void 0;
@@ -5816,14 +5942,14 @@ async function readStableUploadBody(localPath, scanned) {
5816
5942
  throw new Error("local file missing from scan");
5817
5943
  }
5818
5944
  const snapshot = await hashDriveFile(localPath).catch((error) => {
5819
- if (isNotFoundError(error)) return void 0;
5945
+ if (isNotFoundError2(error)) return void 0;
5820
5946
  throw error;
5821
5947
  });
5822
5948
  if (!snapshot || snapshot.sha256 !== scanned.sha256 || snapshot.sizeBytes !== scanned.size_bytes) {
5823
5949
  throw new Error("local file changed after scan");
5824
5950
  }
5825
5951
  const body = await readFile3(localPath).catch((error) => {
5826
- if (isNotFoundError(error)) return void 0;
5952
+ if (isNotFoundError2(error)) return void 0;
5827
5953
  throw error;
5828
5954
  });
5829
5955
  if (!body) {
@@ -5840,7 +5966,7 @@ async function readStableUploadBody(localPath, scanned) {
5840
5966
  async function assertLocalSafeForDownload(root, path, entry) {
5841
5967
  const target = resolveInsideRoot(root, path);
5842
5968
  const digest = await hashDriveFile(target).catch((error) => {
5843
- if (isNotFoundError(error)) return void 0;
5969
+ if (isNotFoundError2(error)) return void 0;
5844
5970
  throw error;
5845
5971
  });
5846
5972
  if (!digest) return;
@@ -5853,14 +5979,14 @@ async function assertLocalSafeForDownload(root, path, entry) {
5853
5979
  }
5854
5980
  async function assertLocalAbsentBeforeRemoteDelete(root, path) {
5855
5981
  const digest = await hashDriveFile(resolveInsideRoot(root, path)).catch((error) => {
5856
- if (isNotFoundError(error)) return void 0;
5982
+ if (isNotFoundError2(error)) return void 0;
5857
5983
  throw error;
5858
5984
  });
5859
5985
  if (digest) {
5860
5986
  throw new Error("local file appeared before remote delete");
5861
5987
  }
5862
5988
  }
5863
- function isNotFoundError(error) {
5989
+ function isNotFoundError2(error) {
5864
5990
  return error instanceof Error && "code" in error && error.code === "ENOENT";
5865
5991
  }
5866
5992
  function isAlreadyExistsError(error) {
@@ -5884,32 +6010,63 @@ function emptySummary() {
5884
6010
  function isActionableAction(action) {
5885
6011
  return action.type !== "unchanged" && action.type !== "state_only" && action.type !== "remove_state";
5886
6012
  }
5887
- async function runDriveSyncOnce(root, api, clock = systemDriveClock, onProgress, debug = noopDriveDebugLogger) {
6013
+ async function runDriveSyncOnce(root, api, clock = systemDriveClock, onProgress, debug = noopDriveDebugLogger, options = {}) {
5888
6014
  return withDriveLock(root, async () => {
5889
6015
  let state = await readDriveState(root);
5890
- const syncApi = api ?? await createDriveApi();
6016
+ const syncApi = api ?? await createDriveApi({ clientId: state.realtime?.client_id });
5891
6017
  const summary = emptySummary();
5892
6018
  const blockedPaths = /* @__PURE__ */ new Set();
5893
6019
  const scanStartedMs = Date.now();
5894
- const localFiles = await scanDriveFiles(root, {
6020
+ const nextScanCache = {};
6021
+ const scanOptions = {
6022
+ cache: state.scan_cache,
6023
+ onCacheUpdate: (path, entry) => {
6024
+ nextScanCache[path] = entry;
6025
+ },
5895
6026
  onPathError: async (path, error) => {
5896
- await recordPathError(summary, blockedPaths, path, error);
6027
+ await recordPathError(summary, blockedPaths, path, error, { debug, op: "scan" });
5897
6028
  }
5898
- });
6029
+ };
6030
+ const useIncrementalScan = options.dirtyPaths !== void 0 && state.scan_cache !== void 0;
6031
+ const localFiles = useIncrementalScan ? await rescanDriveFiles(root, options.dirtyPaths, scanOptions) : await scanDriveFiles(root, scanOptions);
6032
+ if (JSON.stringify(state.scan_cache ?? {}) !== JSON.stringify(nextScanCache)) {
6033
+ state = { ...state, scan_cache: nextScanCache };
6034
+ await writeDriveState(root, state, clock);
6035
+ }
5899
6036
  const scanMs = Date.now() - scanStartedMs;
5900
6037
  const manifestStartedMs = Date.now();
5901
- const remoteFiles = await fetchRemoteManifest(root, state, syncApi, summary, blockedPaths);
6038
+ const manifest = await fetchRemoteManifest(root, state, syncApi, summary, blockedPaths, debug);
6039
+ const remoteFiles = manifest.remoteFiles;
6040
+ if (manifest.manifestCursor !== void 0 && manifest.manifestCursor !== state.manifest_cursor) {
6041
+ state = { ...state, manifest_cursor: manifest.manifestCursor };
6042
+ await writeDriveState(root, state, clock);
6043
+ }
5902
6044
  const manifestMs = Date.now() - manifestStartedMs;
5903
6045
  const paths = Array.from(
5904
6046
  /* @__PURE__ */ new Set([...Object.keys(localFiles), ...Object.keys(remoteFiles), ...Object.keys(state.entries)])
5905
6047
  ).filter((path) => !blockedPaths.has(path)).sort((left, right) => left.localeCompare(right));
6048
+ const movedPaths = await applyRenamesAsMoves({
6049
+ root,
6050
+ state,
6051
+ api: syncApi,
6052
+ paths,
6053
+ localFiles,
6054
+ remoteFiles,
6055
+ summary,
6056
+ clock,
6057
+ debug,
6058
+ onStateChange: (nextState) => {
6059
+ state = nextState;
6060
+ }
6061
+ });
5906
6062
  const total = paths.filter(
5907
- (path) => isActionableAction(decideDriveAction(state.entries[path], localFiles[path], remoteFiles[path]))
6063
+ (path) => !movedPaths.has(path) && isActionableAction(decideDriveAction(state.entries[path], localFiles[path], remoteFiles[path]))
5908
6064
  ).length;
5909
6065
  let processed = 0;
5910
6066
  onProgress?.(processed, total);
5911
6067
  const processStartedMs = Date.now();
5912
6068
  for (const path of paths) {
6069
+ if (movedPaths.has(path)) continue;
5913
6070
  const remote = remoteFiles[path];
5914
6071
  const local = localFiles[path];
5915
6072
  const action = decideDriveAction(state.entries[path], local, remote);
@@ -5917,7 +6074,7 @@ async function runDriveSyncOnce(root, api, clock = systemDriveClock, onProgress,
5917
6074
  debug.log("decision", decisionFields(path, action, state.entries[path], local, remote));
5918
6075
  }
5919
6076
  const previousConflict = state.conflicts[path];
5920
- const result = await processPath({ root, state, api: syncApi, path, action, remote, local, summary, clock });
6077
+ const result = await processPath({ root, state, api: syncApi, path, action, remote, local, summary, clock, debug });
5921
6078
  state = result.state;
5922
6079
  const recordedConflict = state.conflicts[path];
5923
6080
  if (recordedConflict !== void 0 && recordedConflict !== previousConflict) {
@@ -5961,24 +6118,64 @@ function driveSyncCommand(api) {
5961
6118
  });
5962
6119
  return sync;
5963
6120
  }
5964
- async function fetchRemoteManifest(root, state, api, summary, blockedPaths) {
6121
+ async function fetchRemoteManifest(root, state, api, summary, blockedPaths, debug) {
6122
+ if (state.manifest_cursor !== void 0) {
6123
+ const delta = await api.getManifest(state.library_id, void 0, state.manifest_cursor);
6124
+ if (delta.resync_required !== true) {
6125
+ const remoteFiles = remoteViewFromState(state);
6126
+ const changed = delta.entries.filter((entry) => entry.deleted_at === void 0);
6127
+ const normalized2 = normalizeRemoteManifest(root, changed);
6128
+ for (const pathError of normalized2.pathErrors) {
6129
+ await recordPathError(summary, blockedPaths, pathError.path, pathError.error, {
6130
+ appendPathResult: pathError.appendPathResult,
6131
+ debug,
6132
+ op: "manifest"
6133
+ });
6134
+ }
6135
+ for (const entry of delta.entries) {
6136
+ if (entry.deleted_at !== void 0) delete remoteFiles[entry.path];
6137
+ }
6138
+ Object.assign(remoteFiles, normalized2.remoteFiles);
6139
+ return { remoteFiles, manifestCursor: delta.latest_cursor ?? state.manifest_cursor };
6140
+ }
6141
+ }
5965
6142
  const entries = [];
5966
6143
  let cursor;
6144
+ let latestCursor;
5967
6145
  do {
5968
6146
  const page = await api.getManifest(state.library_id, cursor);
5969
6147
  entries.push(...page.entries);
6148
+ if (page.latest_cursor !== void 0) latestCursor = page.latest_cursor;
5970
6149
  cursor = page.next_cursor ?? void 0;
5971
6150
  } while (cursor !== void 0);
5972
6151
  const normalized = normalizeRemoteManifest(root, entries);
5973
6152
  for (const pathError of normalized.pathErrors) {
5974
6153
  await recordPathError(summary, blockedPaths, pathError.path, pathError.error, {
5975
- appendPathResult: pathError.appendPathResult
6154
+ appendPathResult: pathError.appendPathResult,
6155
+ debug,
6156
+ op: "manifest"
5976
6157
  });
5977
6158
  }
5978
- return normalized.remoteFiles;
6159
+ return { remoteFiles: normalized.remoteFiles, manifestCursor: latestCursor };
6160
+ }
6161
+ function remoteViewFromState(state) {
6162
+ const remoteFiles = {};
6163
+ for (const [path, entry] of Object.entries(state.entries)) {
6164
+ remoteFiles[path] = {
6165
+ id: entry.entry_id,
6166
+ path,
6167
+ kind: "file",
6168
+ entry_version: entry.entry_version,
6169
+ size_bytes: entry.size_bytes,
6170
+ updated_at: entry.last_synced_at,
6171
+ ...entry.current_version_id === void 0 ? {} : { current_version_id: entry.current_version_id },
6172
+ ...entry.content_sha256 === void 0 ? {} : { content_sha256: entry.content_sha256 }
6173
+ };
6174
+ }
6175
+ return remoteFiles;
5979
6176
  }
5980
6177
  async function processPath(args) {
5981
- const { root, state, api, path, action, remote, local, summary, clock } = args;
6178
+ const { root, state, api, path, action, remote, local, summary, clock, debug } = args;
5982
6179
  summary.paths.push({ path, action: action.type });
5983
6180
  let durableStateRequired = false;
5984
6181
  try {
@@ -6079,35 +6276,17 @@ async function processPath(args) {
6079
6276
  summary.conflicts += 1;
6080
6277
  return { state: nextState2, stop: false };
6081
6278
  }
6082
- const conflictCopyState = await recordRemoteConflictCopy({
6083
- root,
6084
- state,
6085
- api,
6086
- path,
6087
- reason: action.reason,
6088
- type: "edit_edit",
6089
- remote,
6090
- clock
6091
- });
6092
- if (conflictCopyState) {
6093
- summary.conflicts += 1;
6094
- return { state: conflictCopyState, stop: false };
6279
+ const resolved = await resolveConflictWithLocalAsMain({ root, state, api, path, remote, local, clock });
6280
+ if (resolved) {
6281
+ recordResolvedConflict(summary, debug, path, resolved.copyPath, action.reason, "edit_edit");
6282
+ return { state: resolved.state, stop: false };
6095
6283
  }
6096
6284
  }
6097
6285
  if (action.reason === "local_and_remote_without_base") {
6098
- const conflictCopyState = await recordRemoteConflictCopy({
6099
- root,
6100
- state,
6101
- api,
6102
- path,
6103
- reason: action.reason,
6104
- type: "create_create",
6105
- remote,
6106
- clock
6107
- });
6108
- if (conflictCopyState) {
6109
- summary.conflicts += 1;
6110
- return { state: conflictCopyState, stop: false };
6286
+ const resolved = await resolveConflictWithLocalAsMain({ root, state, api, path, remote, local, clock });
6287
+ if (resolved) {
6288
+ recordResolvedConflict(summary, debug, path, resolved.copyPath, action.reason, "create_create");
6289
+ return { state: resolved.state, stop: false };
6111
6290
  }
6112
6291
  }
6113
6292
  if (action.reason === "local_changed_remote_deleted") {
@@ -6147,11 +6326,11 @@ async function processPath(args) {
6147
6326
  summary.paths[summary.paths.length - 1] = { path, action: "conflict" };
6148
6327
  return { state: nextState, stop: false };
6149
6328
  } catch (writeError) {
6150
- await recordPathError(summary, void 0, path, writeError);
6329
+ await recordPathError(summary, void 0, path, writeError, { debug, op: "process" });
6151
6330
  return { state, stop: durableStateRequired };
6152
6331
  }
6153
6332
  }
6154
- await recordPathError(summary, void 0, path, error);
6333
+ await recordPathError(summary, void 0, path, error, { debug, op: "process" });
6155
6334
  return { state, stop: durableStateRequired };
6156
6335
  }
6157
6336
  return { state, stop: false };
@@ -6213,6 +6392,84 @@ async function downloadBytes(api, libraryId, path, versionId) {
6213
6392
  const response = await api.downloadFile(libraryId, path, versionId);
6214
6393
  return new Uint8Array(await response.arrayBuffer());
6215
6394
  }
6395
+ async function applyRenamesAsMoves(args) {
6396
+ const { root, api, paths, localFiles, remoteFiles, summary, clock, debug, onStateChange } = args;
6397
+ const movedPaths = /* @__PURE__ */ new Set();
6398
+ if (api.moveFile === void 0) return movedPaths;
6399
+ let state = args.state;
6400
+ const deletesBySha = /* @__PURE__ */ new Map();
6401
+ const createsBySha = /* @__PURE__ */ new Map();
6402
+ for (const path of paths) {
6403
+ const action = decideDriveAction(state.entries[path], localFiles[path], remoteFiles[path]);
6404
+ if (action.type === "delete_remote") {
6405
+ const sha = state.entries[path]?.last_local_sha256 ?? state.entries[path]?.content_sha256;
6406
+ if (sha !== void 0) deletesBySha.set(sha, [...deletesBySha.get(sha) ?? [], path]);
6407
+ }
6408
+ if (action.type === "upload_create") {
6409
+ const sha = localFiles[path]?.sha256;
6410
+ if (sha !== void 0) createsBySha.set(sha, [...createsBySha.get(sha) ?? [], path]);
6411
+ }
6412
+ }
6413
+ for (const [sha, fromPaths] of deletesBySha) {
6414
+ const toPaths = createsBySha.get(sha);
6415
+ if (fromPaths.length !== 1 || toPaths === void 0 || toPaths.length !== 1) continue;
6416
+ const fromPath = fromPaths[0];
6417
+ const toPath = toPaths[0];
6418
+ const entry = state.entries[fromPath];
6419
+ const local = localFiles[toPath];
6420
+ if (entry === void 0 || local === void 0) continue;
6421
+ try {
6422
+ const moved = await api.moveFile(state.library_id, fromPath, toPath, entry.entry_version);
6423
+ const nextState = cloneDriveState(state);
6424
+ delete nextState.entries[fromPath];
6425
+ delete nextState.conflicts[fromPath];
6426
+ nextState.entries[toPath] = stateEntryFromRemote(moved.entry, local.sha256, clock);
6427
+ delete nextState.conflicts[toPath];
6428
+ await writeDriveState(root, nextState, clock);
6429
+ state = nextState;
6430
+ onStateChange(nextState);
6431
+ movedPaths.add(fromPath);
6432
+ movedPaths.add(toPath);
6433
+ summary.paths.push({ path: toPath, action: "move" });
6434
+ debug.log("decision", { path: toPath, action: "move", from_path: fromPath });
6435
+ } catch (error) {
6436
+ debug.log("error", { path: toPath, op: "move", message: errorMessage(error) });
6437
+ }
6438
+ }
6439
+ return movedPaths;
6440
+ }
6441
+ function recordResolvedConflict(summary, debug, path, copyPath, reason, type) {
6442
+ summary.conflicts += 1;
6443
+ summary.conflict_paths.push(copyPath);
6444
+ const lastPath = summary.paths.at(-1);
6445
+ if (lastPath?.path === path) {
6446
+ lastPath.conflict_paths = [copyPath];
6447
+ }
6448
+ debug.log("conflict", { path, reason, type, strategy: "conflict_copy", conflict_paths: [copyPath] });
6449
+ }
6450
+ async function resolveConflictWithLocalAsMain(args) {
6451
+ const { root, state, api, path, remote, local, clock } = args;
6452
+ const remoteVersionId = remote?.current_version_id;
6453
+ if (!remote || remoteVersionId === void 0 || !local) {
6454
+ return void 0;
6455
+ }
6456
+ const previous = state.conflicts[path];
6457
+ let copyPath;
6458
+ if (previous !== void 0 && await canReuseConflictCopy(root, previous, remoteVersionId)) {
6459
+ copyPath = previous.conflict_paths[0];
6460
+ } else {
6461
+ const remoteBytes = await downloadBytes(api, state.library_id, path, remoteVersionId);
6462
+ copyPath = await writeConflictCopy(root, path, "remote", remoteVersionId, remoteBytes, clock);
6463
+ }
6464
+ const localPath = resolveInsideRoot(root, path);
6465
+ const { body, digest } = await readStableUploadBody(localPath, local);
6466
+ const uploaded = await api.uploadFile(state.library_id, path, body, digest, remote.entry_version);
6467
+ const nextState = cloneDriveState(state);
6468
+ nextState.entries[path] = stateEntryFromRemote(uploaded.entry, digest, clock);
6469
+ delete nextState.conflicts[path];
6470
+ await writeDriveState(root, nextState, clock);
6471
+ return { state: nextState, copyPath };
6472
+ }
6216
6473
  async function recordRemoteConflictCopy(args) {
6217
6474
  const { root, state, api, path, reason, type, remote, clock } = args;
6218
6475
  const entry = state.entries[path];
@@ -6288,12 +6545,12 @@ function conflictCopyPathWithSuffix(path, suffix) {
6288
6545
  if (suffix === 1) {
6289
6546
  return path;
6290
6547
  }
6291
- const parsed = pathPosix2.parse(path);
6548
+ const parsed = pathPosix3.parse(path);
6292
6549
  const fileName = `${parsed.name}-${suffix}${parsed.ext}`;
6293
6550
  if (parsed.dir === "") {
6294
6551
  return fileName;
6295
6552
  }
6296
- return pathPosix2.join(parsed.dir, fileName);
6553
+ return pathPosix3.join(parsed.dir, fileName);
6297
6554
  }
6298
6555
  function isExpectedVersionDownloadMissing(error) {
6299
6556
  const structured = error;
@@ -6371,7 +6628,13 @@ async function recordPathError(summary, blockedPaths, path, error, options = {})
6371
6628
  } else {
6372
6629
  summary.paths.push({ path, action: "error" });
6373
6630
  }
6374
- void errorMessage(error);
6631
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
6632
+ options.debug?.log("error", {
6633
+ path,
6634
+ ...options.op === void 0 ? {} : { op: options.op },
6635
+ ...code === void 0 ? {} : { code },
6636
+ message: errorMessage(error)
6637
+ });
6375
6638
  summary.errors += 1;
6376
6639
  }
6377
6640
  function conflict(reason, remote, clock) {
@@ -6407,7 +6670,7 @@ function containsVersionConflict(value) {
6407
6670
  import { Command as Command81 } from "commander";
6408
6671
  import chokidar from "chokidar";
6409
6672
  import { watch as fsWatch } from "fs";
6410
- import { relative as relative2, resolve as resolve4 } from "path";
6673
+ import { basename as basename4, relative as relative2, resolve as resolve4 } from "path";
6411
6674
 
6412
6675
  // src/handwritten/commands/drive/realtime.ts
6413
6676
  function createDriveRealtimeSource(args) {
@@ -6504,12 +6767,15 @@ function createDriveRealtimeSource(args) {
6504
6767
  return;
6505
6768
  }
6506
6769
  if (message.type === "library_changed") {
6507
- handlers?.onEvent({
6508
- debounce_ms: 2e3,
6509
- reason: "library_changed",
6510
- ...message.cursor === void 0 ? {} : { cursor: message.cursor },
6511
- ...message.path === void 0 ? {} : { path: message.path }
6512
- });
6770
+ const ownEcho = message.origin_client_id !== void 0 && message.origin_client_id === currentRealtime.client_id;
6771
+ if (!ownEcho) {
6772
+ handlers?.onEvent({
6773
+ debounce_ms: 2e3,
6774
+ reason: "library_changed",
6775
+ ...message.cursor === void 0 ? {} : { cursor: message.cursor },
6776
+ ...message.path === void 0 ? {} : { path: message.path }
6777
+ });
6778
+ }
6513
6779
  if (message.cursor !== void 0) {
6514
6780
  await persistBestEffort({ ...currentRealtime, last_cursor: message.cursor, last_event_at: driveIsoTimestamp(clock) });
6515
6781
  }
@@ -6596,7 +6862,8 @@ function parseDriveRealtimeMessage(raw) {
6596
6862
  return {
6597
6863
  type: "library_changed",
6598
6864
  ...cursor === void 0 ? {} : { cursor },
6599
- ...typeof value.path === "string" ? { path: value.path } : {}
6865
+ ...typeof value.path === "string" ? { path: value.path } : {},
6866
+ ...typeof value.origin_client_id === "string" ? { origin_client_id: value.origin_client_id } : {}
6600
6867
  };
6601
6868
  }
6602
6869
  if (messageType === "resync_required") {
@@ -6706,7 +6973,7 @@ function webSocketError(event) {
6706
6973
  // src/handwritten/commands/drive/watch.ts
6707
6974
  async function runDriveWatch(root, options = {}) {
6708
6975
  const dbg = options.debugLogger ?? (options.debug ? createDriveDebugLogger(root) : noopDriveDebugLogger);
6709
- const runSync = options.runSync ?? ((syncRoot, onProgress) => runDriveSyncOnce(syncRoot, void 0, void 0, onProgress, dbg));
6976
+ const runSync = options.runSync ?? ((syncRoot, onProgress, dirtyPaths2) => runDriveSyncOnce(syncRoot, void 0, void 0, onProgress, dbg, dirtyPaths2 === void 0 ? {} : { dirtyPaths: dirtyPaths2 }));
6710
6977
  const debounceMs = options.debounceMs ?? 500;
6711
6978
  const remoteDebounceMs = options.remoteDebounceMs ?? 2e3;
6712
6979
  const emit = options.onEvent ?? ((event) => {
@@ -6718,6 +6985,7 @@ async function runDriveWatch(root, options = {}) {
6718
6985
  render({ kind: "drive_watch", display: { shape: "object" } }, event);
6719
6986
  });
6720
6987
  let nextTrigger = "initial";
6988
+ const dirtyPaths = /* @__PURE__ */ new Set();
6721
6989
  let debounceTimer;
6722
6990
  let debounceDeadlineMs;
6723
6991
  let retryTimer;
@@ -6744,9 +7012,15 @@ async function runDriveWatch(root, options = {}) {
6744
7012
  emit({ kind: "drive_sync_start" });
6745
7013
  dbg.log("sync_start", { trigger: nextTrigger });
6746
7014
  const startedAtMs = Date.now();
6747
- const summary = await runSync(root, (processed, total) => {
6748
- emit({ kind: "drive_sync_progress", processed, total });
6749
- });
7015
+ const dirtySnapshot = nextTrigger === "local" ? [...dirtyPaths] : void 0;
7016
+ dirtyPaths.clear();
7017
+ const summary = await runSync(
7018
+ root,
7019
+ (processed, total) => {
7020
+ emit({ kind: "drive_sync_progress", processed, total });
7021
+ },
7022
+ dirtySnapshot
7023
+ );
6750
7024
  emit({ kind: "drive_sync_once", ...summary });
6751
7025
  dbg.log("sync_end", {
6752
7026
  duration_ms: Date.now() - startedAtMs,
@@ -6853,7 +7127,10 @@ async function runDriveWatch(root, options = {}) {
6853
7127
  source = options.source ?? createDefaultWatchSource(root);
6854
7128
  source.onChange((path) => {
6855
7129
  if (isDriveInternalPath(root, path)) return;
6856
- dbg.log("fs_event", { path: relative2(root, path) });
7130
+ if (isInternalSyncArtifactName(basename4(path))) return;
7131
+ const drivePath = relative2(root, path).replace(/\\/g, "/");
7132
+ dirtyPaths.add(drivePath);
7133
+ dbg.log("fs_event", { path: drivePath });
6857
7134
  scheduleSync(debounceMs, "local");
6858
7135
  });
6859
7136
  emit({ kind: "drive_watch_started", root, library_id: state.library_id });