proton-drive-mcp 1.3.2 → 1.3.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.3 — 2026-10-01
4
+
5
+ ### Fixed
6
+ - `drive_find_duplicates` with `verify`: on a real 1,125-file drive about half of the verification downloads failed with `database is locked` (the CLI's SQLite cache, hit by 4 parallel downloads) and left groups unverified. Verification now retries that error in a fresh temp dir (max 4 attempts), runs 2 downloads at a time by default, and failure notes are a single line instead of a SQLite dump. Re-run on the same drive: 30 of 30 groups verified, no lock errors.
7
+
3
8
  ## 1.3.2 — 2026-10-01
4
9
 
5
10
  ### Changed
@@ -163,14 +163,14 @@ export async function verifyGroups(groups, hash, o) {
163
163
  totalMembers += g.members.length;
164
164
  const digests = new Map();
165
165
  const failed = [];
166
- await pool(g.members, o.concurrency ?? 4, async (m) => {
166
+ await pool(g.members, o.concurrency ?? 2, async (m) => {
167
167
  if (o.signal?.aborted)
168
168
  return;
169
169
  try {
170
170
  digests.set(m, await hash(m));
171
171
  }
172
172
  catch (e) {
173
- failed.push(`${m.path}: ${e instanceof Error ? e.message : String(e)}`);
173
+ failed.push(`${m.path}: ${oneLine(e instanceof Error ? e.message : String(e))}`);
174
174
  }
175
175
  });
176
176
  stop(); // the hasher's own finally already removed any temp dir
@@ -187,9 +187,16 @@ export async function verifyGroups(groups, hash, o) {
187
187
  }
188
188
  return out.filter((g) => g.members.length > 1).sort((a, b) => wasted(b) - wasted(a) || (a.members[0].path < b.members[0].path ? -1 : 1));
189
189
  }
190
- /** Downloads one file into its own private temp dir, sha256s it, and always removes the dir. */
190
+ const LOCKED_RE = /database is locked|SQLITE_BUSY/i;
191
+ const oneLine = (m) => (m.split("\n")[0] ?? m).slice(0, 160);
192
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
193
+ /**
194
+ * Downloads one file into its own private temp dir, sha256s it, and always removes the dir.
195
+ * The CLI's SQLite cache can report "database is locked" at startup when several downloads run
196
+ * at once; nothing has been transferred then, so a fresh attempt is safe (max 4 tries).
197
+ */
191
198
  export function makeDriveHasher(drive) {
192
- return async (node) => {
199
+ const once = async (node) => {
193
200
  const dir = await mkdtemp(join(tmpdir(), "pdmcp-dup-"));
194
201
  try {
195
202
  validateLocalPath(dir); // same local-path rules as drive_download
@@ -208,6 +215,20 @@ export function makeDriveHasher(drive) {
208
215
  await rm(dir, { recursive: true, force: true });
209
216
  }
210
217
  };
218
+ return async (node) => {
219
+ const base = Number(process.env["PROTON_DRIVE_RETRY_BASE_MS"] ?? 250);
220
+ for (let attempt = 1;; attempt++) {
221
+ try {
222
+ return await once(node);
223
+ }
224
+ catch (e) {
225
+ const msg = e instanceof Error ? e.message : String(e);
226
+ if (attempt >= 4 || !LOCKED_RE.test(msg))
227
+ throw e;
228
+ await sleep(base * attempt + Math.random() * base);
229
+ }
230
+ }
231
+ };
211
232
  }
212
233
  function reportGroup(g) {
213
234
  const keeper = keeperOf(g.members);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proton-drive-mcp",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "MCP server and CLI that gives Claude full access to Proton Drive — upload, download, share, and manage your end-to-end encrypted files without leaving the conversation.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",