ccsini 0.1.59 → 0.1.61
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/index.js +239 -102
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27876,7 +27876,7 @@ function decodeProjectPath(encoded) {
|
|
|
27876
27876
|
}
|
|
27877
27877
|
|
|
27878
27878
|
// ../shared/src/constants.ts
|
|
27879
|
-
var DEFAULT_CATEGORY_PATTERNS, MERGE_STRATEGIES, NEVER_SYNC_PATTERNS, SESSION_SYNC_DEFAULTS, JWT_EXPIRY_SECONDS = 900, DAEMON_DEBOUNCE_MS = 5000, DAEMON_INTERVAL_MS = 30000, MAX_CONCURRENT_TRANSFERS = 50;
|
|
27879
|
+
var DEFAULT_CATEGORY_PATTERNS, MERGE_STRATEGIES, NEVER_SYNC_PATTERNS, SESSION_SYNC_DEFAULTS, JWT_EXPIRY_SECONDS = 900, DAEMON_DEBOUNCE_MS = 5000, DAEMON_INTERVAL_MS = 30000, MAX_CONCURRENT_TRANSFERS = 50, BATCH_DOWNLOAD_SIZE = 20;
|
|
27880
27880
|
var init_constants = __esm(() => {
|
|
27881
27881
|
DEFAULT_CATEGORY_PATTERNS = {
|
|
27882
27882
|
session: ["projects/**/*.jsonl"],
|
|
@@ -28005,6 +28005,42 @@ var init_auth = __esm(() => {
|
|
|
28005
28005
|
init_src();
|
|
28006
28006
|
});
|
|
28007
28007
|
|
|
28008
|
+
// src/core/schema.ts
|
|
28009
|
+
var exports_schema = {};
|
|
28010
|
+
__export(exports_schema, {
|
|
28011
|
+
saveDefaultSchema: () => saveDefaultSchema,
|
|
28012
|
+
loadSchema: () => loadSchema,
|
|
28013
|
+
getEnabledCategories: () => getEnabledCategories
|
|
28014
|
+
});
|
|
28015
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
28016
|
+
import { join as join2 } from "path";
|
|
28017
|
+
async function saveDefaultSchema(configDir) {
|
|
28018
|
+
const schema = buildDefaultSchema();
|
|
28019
|
+
await writeFile2(join2(configDir, "schema.yaml"), JSON.stringify(schema, null, 2));
|
|
28020
|
+
}
|
|
28021
|
+
async function loadSchema(configDir) {
|
|
28022
|
+
const raw = await readFile2(join2(configDir, "schema.yaml"), "utf-8");
|
|
28023
|
+
return JSON.parse(raw);
|
|
28024
|
+
}
|
|
28025
|
+
function getEnabledCategories(schema) {
|
|
28026
|
+
return Object.entries(schema.categories).filter(([, config]) => config.enabled).map(([name]) => name);
|
|
28027
|
+
}
|
|
28028
|
+
function buildDefaultSchema() {
|
|
28029
|
+
const categories = {};
|
|
28030
|
+
for (const [cat, patterns] of Object.entries(DEFAULT_CATEGORY_PATTERNS)) {
|
|
28031
|
+
const category = cat;
|
|
28032
|
+
categories[category] = {
|
|
28033
|
+
enabled: true,
|
|
28034
|
+
patterns,
|
|
28035
|
+
merge: MERGE_STRATEGIES[category]
|
|
28036
|
+
};
|
|
28037
|
+
}
|
|
28038
|
+
return { version: 1, categories };
|
|
28039
|
+
}
|
|
28040
|
+
var init_schema = __esm(() => {
|
|
28041
|
+
init_src();
|
|
28042
|
+
});
|
|
28043
|
+
|
|
28008
28044
|
// ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
|
|
28009
28045
|
var import__ = __toESM(require_commander(), 1);
|
|
28010
28046
|
var {
|
|
@@ -28022,7 +28058,7 @@ var {
|
|
|
28022
28058
|
} = import__.default;
|
|
28023
28059
|
|
|
28024
28060
|
// src/version.ts
|
|
28025
|
-
var VERSION = "0.1.
|
|
28061
|
+
var VERSION = "0.1.61";
|
|
28026
28062
|
|
|
28027
28063
|
// src/commands/init.ts
|
|
28028
28064
|
init_source();
|
|
@@ -28765,26 +28801,8 @@ async function saveSessionConfig(configDir, sessionConfig) {
|
|
|
28765
28801
|
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
28766
28802
|
}
|
|
28767
28803
|
|
|
28768
|
-
// src/
|
|
28769
|
-
|
|
28770
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
28771
|
-
import { join as join2 } from "path";
|
|
28772
|
-
async function saveDefaultSchema(configDir) {
|
|
28773
|
-
const schema = buildDefaultSchema();
|
|
28774
|
-
await writeFile2(join2(configDir, "schema.yaml"), JSON.stringify(schema, null, 2));
|
|
28775
|
-
}
|
|
28776
|
-
function buildDefaultSchema() {
|
|
28777
|
-
const categories = {};
|
|
28778
|
-
for (const [cat, patterns] of Object.entries(DEFAULT_CATEGORY_PATTERNS)) {
|
|
28779
|
-
const category = cat;
|
|
28780
|
-
categories[category] = {
|
|
28781
|
-
enabled: true,
|
|
28782
|
-
patterns,
|
|
28783
|
-
merge: MERGE_STRATEGIES[category]
|
|
28784
|
-
};
|
|
28785
|
-
}
|
|
28786
|
-
return { version: 1, categories };
|
|
28787
|
-
}
|
|
28804
|
+
// src/commands/init.ts
|
|
28805
|
+
init_schema();
|
|
28788
28806
|
|
|
28789
28807
|
// src/hooks/installer.ts
|
|
28790
28808
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -28949,6 +28967,23 @@ class CcsiniClient {
|
|
|
28949
28967
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
28950
28968
|
this.token = token;
|
|
28951
28969
|
}
|
|
28970
|
+
async fetchWithRetry(input, init, maxRetries = 3) {
|
|
28971
|
+
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
28972
|
+
try {
|
|
28973
|
+
const res = await fetch(input, init);
|
|
28974
|
+
if (res.status >= 500 && attempt < maxRetries) {
|
|
28975
|
+
await new Promise((r) => setTimeout(r, 500 * Math.pow(2, attempt - 1)));
|
|
28976
|
+
continue;
|
|
28977
|
+
}
|
|
28978
|
+
return res;
|
|
28979
|
+
} catch (e) {
|
|
28980
|
+
if (attempt >= maxRetries)
|
|
28981
|
+
throw e;
|
|
28982
|
+
await new Promise((r) => setTimeout(r, 500 * Math.pow(2, attempt - 1)));
|
|
28983
|
+
}
|
|
28984
|
+
}
|
|
28985
|
+
throw new Error("fetchWithRetry exhausted");
|
|
28986
|
+
}
|
|
28952
28987
|
getHeaders() {
|
|
28953
28988
|
return {
|
|
28954
28989
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -28977,7 +29012,7 @@ class CcsiniClient {
|
|
|
28977
29012
|
throw new Error("Failed to store salt");
|
|
28978
29013
|
}
|
|
28979
29014
|
async getManifest() {
|
|
28980
|
-
const res = await
|
|
29015
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/manifest`, {
|
|
28981
29016
|
headers: this.getHeaders()
|
|
28982
29017
|
});
|
|
28983
29018
|
if (!res.ok)
|
|
@@ -28988,7 +29023,7 @@ class CcsiniClient {
|
|
|
28988
29023
|
return new Uint8Array(Buffer.from(data.manifest, "base64"));
|
|
28989
29024
|
}
|
|
28990
29025
|
async putManifest(encryptedManifest) {
|
|
28991
|
-
const res = await
|
|
29026
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/manifest`, {
|
|
28992
29027
|
method: "PUT",
|
|
28993
29028
|
headers: {
|
|
28994
29029
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -29000,7 +29035,7 @@ class CcsiniClient {
|
|
|
29000
29035
|
throw new Error("Failed to upload manifest");
|
|
29001
29036
|
}
|
|
29002
29037
|
async uploadBlob(hash, encryptedData) {
|
|
29003
|
-
const res = await
|
|
29038
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/blob/${hash}`, {
|
|
29004
29039
|
method: "PUT",
|
|
29005
29040
|
headers: {
|
|
29006
29041
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -29012,7 +29047,7 @@ class CcsiniClient {
|
|
|
29012
29047
|
throw new Error(`Failed to upload blob ${hash}`);
|
|
29013
29048
|
}
|
|
29014
29049
|
async downloadBlob(hash) {
|
|
29015
|
-
const res = await
|
|
29050
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/blob/${hash}`, {
|
|
29016
29051
|
headers: this.getHeaders()
|
|
29017
29052
|
});
|
|
29018
29053
|
if (!res.ok)
|
|
@@ -29021,7 +29056,7 @@ class CcsiniClient {
|
|
|
29021
29056
|
return new Uint8Array(buffer);
|
|
29022
29057
|
}
|
|
29023
29058
|
async batchDownload(hashes) {
|
|
29024
|
-
const res = await
|
|
29059
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/batch-download`, {
|
|
29025
29060
|
method: "POST",
|
|
29026
29061
|
headers: this.getHeaders(),
|
|
29027
29062
|
body: JSON.stringify({ hashes })
|
|
@@ -29036,7 +29071,7 @@ class CcsiniClient {
|
|
|
29036
29071
|
return result;
|
|
29037
29072
|
}
|
|
29038
29073
|
async listBlobs() {
|
|
29039
|
-
const res = await
|
|
29074
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/blobs`, {
|
|
29040
29075
|
headers: this.getHeaders()
|
|
29041
29076
|
});
|
|
29042
29077
|
if (!res.ok)
|
|
@@ -29044,7 +29079,7 @@ class CcsiniClient {
|
|
|
29044
29079
|
return res.json();
|
|
29045
29080
|
}
|
|
29046
29081
|
async deleteBlobs(hashes) {
|
|
29047
|
-
const res = await
|
|
29082
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/blobs/delete`, {
|
|
29048
29083
|
method: "POST",
|
|
29049
29084
|
headers: this.getHeaders(),
|
|
29050
29085
|
body: JSON.stringify({ hashes })
|
|
@@ -29063,7 +29098,7 @@ class CcsiniClient {
|
|
|
29063
29098
|
throw new Error("Failed to wipe account data");
|
|
29064
29099
|
}
|
|
29065
29100
|
async putProjects(projects, device) {
|
|
29066
|
-
const res = await
|
|
29101
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/projects`, {
|
|
29067
29102
|
method: "PUT",
|
|
29068
29103
|
headers: this.getHeaders(),
|
|
29069
29104
|
body: JSON.stringify({ projects, device, timestamp: Date.now() })
|
|
@@ -29073,6 +29108,17 @@ class CcsiniClient {
|
|
|
29073
29108
|
throw new Error(`Failed to send projects (${res.status}): ${text}`);
|
|
29074
29109
|
}
|
|
29075
29110
|
}
|
|
29111
|
+
async checkBlobsExist(hashes) {
|
|
29112
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/blobs/exists`, {
|
|
29113
|
+
method: "POST",
|
|
29114
|
+
headers: this.getHeaders(),
|
|
29115
|
+
body: JSON.stringify({ hashes })
|
|
29116
|
+
});
|
|
29117
|
+
if (!res.ok)
|
|
29118
|
+
throw new Error("Failed to check blob existence");
|
|
29119
|
+
const data = await res.json();
|
|
29120
|
+
return data.exists;
|
|
29121
|
+
}
|
|
29076
29122
|
async resetAll() {
|
|
29077
29123
|
const res = await fetch(`${this.apiUrl}/api/sync/reset`, {
|
|
29078
29124
|
method: "DELETE",
|
|
@@ -29083,7 +29129,7 @@ class CcsiniClient {
|
|
|
29083
29129
|
return res.json();
|
|
29084
29130
|
}
|
|
29085
29131
|
async heartbeat(mode, stats) {
|
|
29086
|
-
await
|
|
29132
|
+
const res = await this.fetchWithRetry(`${this.apiUrl}/api/sync/heartbeat`, {
|
|
29087
29133
|
method: "POST",
|
|
29088
29134
|
headers: this.getHeaders(),
|
|
29089
29135
|
body: JSON.stringify({
|
|
@@ -29092,13 +29138,20 @@ class CcsiniClient {
|
|
|
29092
29138
|
pushCount: stats.pushCount,
|
|
29093
29139
|
pullCount: stats.pullCount,
|
|
29094
29140
|
lastError: stats.lastError,
|
|
29095
|
-
conflicts: stats.conflicts
|
|
29141
|
+
conflicts: stats.conflicts,
|
|
29142
|
+
version: stats.version,
|
|
29143
|
+
categories: stats.categories,
|
|
29144
|
+
conflictFiles: stats.conflictFiles
|
|
29096
29145
|
}
|
|
29097
29146
|
})
|
|
29098
29147
|
});
|
|
29148
|
+
if (res.ok) {
|
|
29149
|
+
return res.json();
|
|
29150
|
+
}
|
|
29151
|
+
return {};
|
|
29099
29152
|
}
|
|
29100
29153
|
async logSyncEvent(event) {
|
|
29101
|
-
await
|
|
29154
|
+
await this.fetchWithRetry(`${this.apiUrl}/api/sync/log`, {
|
|
29102
29155
|
method: "POST",
|
|
29103
29156
|
headers: this.getHeaders(),
|
|
29104
29157
|
body: JSON.stringify(event)
|
|
@@ -29111,6 +29164,7 @@ import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir2 } from
|
|
|
29111
29164
|
import { join as join6, dirname } from "path";
|
|
29112
29165
|
import { homedir as homedir3 } from "os";
|
|
29113
29166
|
import { createHash as createHash2 } from "crypto";
|
|
29167
|
+
import { gzipSync, gunzipSync } from "zlib";
|
|
29114
29168
|
|
|
29115
29169
|
// src/core/manifest.ts
|
|
29116
29170
|
import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -29377,6 +29431,30 @@ init_src();
|
|
|
29377
29431
|
function blobKey(filePath, contentHash) {
|
|
29378
29432
|
return createHash2("sha256").update(`${filePath}:${contentHash}`).digest("hex");
|
|
29379
29433
|
}
|
|
29434
|
+
var COMPRESS_FLAG = 1;
|
|
29435
|
+
var RAW_FLAG = 0;
|
|
29436
|
+
function compressForUpload(plaintext) {
|
|
29437
|
+
const compressed = gzipSync(plaintext);
|
|
29438
|
+
if (compressed.length < plaintext.length) {
|
|
29439
|
+
const result2 = new Uint8Array(1 + compressed.length);
|
|
29440
|
+
result2[0] = COMPRESS_FLAG;
|
|
29441
|
+
result2.set(compressed, 1);
|
|
29442
|
+
return result2;
|
|
29443
|
+
}
|
|
29444
|
+
const result = new Uint8Array(1 + plaintext.length);
|
|
29445
|
+
result[0] = RAW_FLAG;
|
|
29446
|
+
result.set(plaintext, 1);
|
|
29447
|
+
return result;
|
|
29448
|
+
}
|
|
29449
|
+
function decompressFromDownload(data) {
|
|
29450
|
+
if (data.length === 0)
|
|
29451
|
+
return data;
|
|
29452
|
+
if (data[0] === COMPRESS_FLAG)
|
|
29453
|
+
return gunzipSync(data.slice(1));
|
|
29454
|
+
if (data[0] === RAW_FLAG)
|
|
29455
|
+
return data.slice(1);
|
|
29456
|
+
return data;
|
|
29457
|
+
}
|
|
29380
29458
|
function getClaudeDir() {
|
|
29381
29459
|
return join6(homedir3(), ".claude");
|
|
29382
29460
|
}
|
|
@@ -29441,7 +29519,8 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
|
|
|
29441
29519
|
} catch {
|
|
29442
29520
|
encrypted = await client.downloadBlob(diff.remoteHash);
|
|
29443
29521
|
}
|
|
29444
|
-
const
|
|
29522
|
+
const decryptedRaw = decryptFile(masterKey, diff.path, encrypted);
|
|
29523
|
+
const decrypted = decompressFromDownload(decryptedRaw);
|
|
29445
29524
|
const conflictPath = join6(claudeDir, `${diff.path}.conflict`);
|
|
29446
29525
|
await mkdir2(dirname(conflictPath), { recursive: true });
|
|
29447
29526
|
await writeFile5(conflictPath, decrypted);
|
|
@@ -29450,30 +29529,32 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
|
|
|
29450
29529
|
} catch {}
|
|
29451
29530
|
}
|
|
29452
29531
|
}
|
|
29532
|
+
const pushKeys = toPush.map((d) => blobKey(d.path, d.localHash));
|
|
29533
|
+
let existing = {};
|
|
29534
|
+
try {
|
|
29535
|
+
for (const batch of chunkArray(pushKeys, 100)) {
|
|
29536
|
+
Object.assign(existing, await client.checkBlobsExist(batch));
|
|
29537
|
+
}
|
|
29538
|
+
} catch {}
|
|
29539
|
+
const toUpload = toPush.filter((d) => !existing[blobKey(d.path, d.localHash)]);
|
|
29540
|
+
progress(`${toPush.length} changed, ${toUpload.length} to upload (${toPush.length - toUpload.length} skipped)`);
|
|
29453
29541
|
let uploaded = 0;
|
|
29454
29542
|
const failedPaths = new Set;
|
|
29455
|
-
const chunks = chunkArray(
|
|
29543
|
+
const chunks = chunkArray(toUpload, MAX_CONCURRENT_TRANSFERS);
|
|
29456
29544
|
for (const chunk of chunks) {
|
|
29457
29545
|
await Promise.all(chunk.map(async (diff) => {
|
|
29458
|
-
|
|
29459
|
-
|
|
29460
|
-
|
|
29461
|
-
|
|
29462
|
-
|
|
29463
|
-
|
|
29464
|
-
|
|
29465
|
-
|
|
29466
|
-
|
|
29467
|
-
|
|
29468
|
-
|
|
29469
|
-
}
|
|
29470
|
-
if (attempt < MAX_RETRIES) {
|
|
29471
|
-
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
29472
|
-
} else {
|
|
29473
|
-
failedPaths.add(diff.path);
|
|
29474
|
-
errors2.push(`Push ${diff.path}: ${e.message}`);
|
|
29475
|
-
}
|
|
29476
|
-
}
|
|
29546
|
+
try {
|
|
29547
|
+
const filePath = join6(claudeDir, diff.path);
|
|
29548
|
+
const content = await readFile6(filePath);
|
|
29549
|
+
const compressed = compressForUpload(content);
|
|
29550
|
+
const encrypted = encryptFile(masterKey, diff.path, compressed);
|
|
29551
|
+
await client.uploadBlob(blobKey(diff.path, diff.localHash), encrypted);
|
|
29552
|
+
bytesTransferred += encrypted.length;
|
|
29553
|
+
uploaded++;
|
|
29554
|
+
progress(`Uploading ${uploaded}/${toUpload.length}: ${diff.path}`);
|
|
29555
|
+
} catch (e) {
|
|
29556
|
+
failedPaths.add(diff.path);
|
|
29557
|
+
errors2.push(`Push ${diff.path}: ${e.message}`);
|
|
29477
29558
|
}
|
|
29478
29559
|
}));
|
|
29479
29560
|
}
|
|
@@ -29554,56 +29635,84 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress, se
|
|
|
29554
29635
|
const toPull = diffs.filter((d) => (d.action === "pull" || d.action === "merge") && !isExcluded(d.path) && (sessionsEnabled || !isSessionFile(d.path)));
|
|
29555
29636
|
progress(`${toPull.length} files to pull`);
|
|
29556
29637
|
let downloaded = 0;
|
|
29557
|
-
const
|
|
29558
|
-
for (const
|
|
29559
|
-
|
|
29560
|
-
|
|
29561
|
-
|
|
29638
|
+
const keyToDiffs = new Map;
|
|
29639
|
+
for (const diff of toPull) {
|
|
29640
|
+
const key = blobKey(diff.path, diff.remoteHash);
|
|
29641
|
+
const list = keyToDiffs.get(key) ?? [];
|
|
29642
|
+
list.push(diff);
|
|
29643
|
+
keyToDiffs.set(key, list);
|
|
29644
|
+
}
|
|
29645
|
+
const uniqueKeys = Array.from(keyToDiffs.keys());
|
|
29646
|
+
const batches = chunkArray(uniqueKeys, BATCH_DOWNLOAD_SIZE);
|
|
29647
|
+
const downloadedBlobs = new Map;
|
|
29648
|
+
for (const batch of batches) {
|
|
29649
|
+
try {
|
|
29650
|
+
const results = await client.batchDownload(batch);
|
|
29651
|
+
for (const [key, data] of Object.entries(results)) {
|
|
29652
|
+
if (data)
|
|
29653
|
+
downloadedBlobs.set(key, data);
|
|
29654
|
+
}
|
|
29655
|
+
} catch {
|
|
29656
|
+
for (const key of batch) {
|
|
29657
|
+
try {
|
|
29658
|
+
const data = await client.downloadBlob(key);
|
|
29659
|
+
downloadedBlobs.set(key, data);
|
|
29660
|
+
} catch {}
|
|
29661
|
+
}
|
|
29662
|
+
}
|
|
29663
|
+
}
|
|
29664
|
+
for (const diff of toPull) {
|
|
29665
|
+
try {
|
|
29666
|
+
const key = blobKey(diff.path, diff.remoteHash);
|
|
29667
|
+
let encrypted = downloadedBlobs.get(key);
|
|
29668
|
+
if (!encrypted) {
|
|
29562
29669
|
try {
|
|
29563
|
-
encrypted = await client.downloadBlob(blobKey(diff.path, diff.remoteHash));
|
|
29564
|
-
} catch {
|
|
29565
29670
|
encrypted = await client.downloadBlob(diff.remoteHash);
|
|
29671
|
+
} catch {
|
|
29672
|
+
errors2.push(`Pull ${diff.path}: blob not found`);
|
|
29673
|
+
continue;
|
|
29566
29674
|
}
|
|
29567
|
-
|
|
29568
|
-
|
|
29569
|
-
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
}
|
|
29580
|
-
|
|
29581
|
-
|
|
29582
|
-
|
|
29583
|
-
|
|
29584
|
-
|
|
29585
|
-
|
|
29586
|
-
|
|
29587
|
-
|
|
29588
|
-
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
|
|
29589
|
-
return;
|
|
29590
|
-
}
|
|
29591
|
-
await writeFile5(`${localPath2}.conflict`, localContent);
|
|
29592
|
-
await writeFile5(localPath2, remoteContent);
|
|
29675
|
+
}
|
|
29676
|
+
bytesTransferred += encrypted.length;
|
|
29677
|
+
const decryptedRaw = decryptFile(masterKey, diff.path, encrypted);
|
|
29678
|
+
const decrypted = decompressFromDownload(decryptedRaw);
|
|
29679
|
+
if (diff.action === "merge" && diff.localHash) {
|
|
29680
|
+
const localPath2 = join6(claudeDir, diff.path);
|
|
29681
|
+
const localContent = await readFile6(localPath2, "utf-8");
|
|
29682
|
+
const remoteContent = new TextDecoder().decode(decrypted);
|
|
29683
|
+
if (diff.category === "session") {
|
|
29684
|
+
const merged = mergeSessionFiles(localContent, remoteContent);
|
|
29685
|
+
await writeFile5(localPath2, merged);
|
|
29686
|
+
downloaded++;
|
|
29687
|
+
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
|
|
29688
|
+
continue;
|
|
29689
|
+
}
|
|
29690
|
+
const entry = remoteManifest.files[diff.path];
|
|
29691
|
+
const localEntry = localManifest?.files[diff.path];
|
|
29692
|
+
if (localEntry && entry) {
|
|
29693
|
+
const result = mergeLastWriteWins(localContent, remoteContent, localEntry.modified, entry.modified);
|
|
29694
|
+
await writeFile5(localPath2, result.content);
|
|
29695
|
+
await writeFile5(`${localPath2}.conflict`, result.backupContent);
|
|
29593
29696
|
conflicts.push(diff.path);
|
|
29594
29697
|
downloaded++;
|
|
29595
|
-
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}
|
|
29596
|
-
|
|
29698
|
+
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
|
|
29699
|
+
continue;
|
|
29597
29700
|
}
|
|
29598
|
-
|
|
29599
|
-
await
|
|
29600
|
-
|
|
29701
|
+
await writeFile5(`${localPath2}.conflict`, localContent);
|
|
29702
|
+
await writeFile5(localPath2, remoteContent);
|
|
29703
|
+
conflicts.push(diff.path);
|
|
29601
29704
|
downloaded++;
|
|
29602
|
-
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
|
|
29603
|
-
|
|
29604
|
-
errors2.push(`Pull ${diff.path}: ${e.message}`);
|
|
29705
|
+
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path} (conflict saved)`);
|
|
29706
|
+
continue;
|
|
29605
29707
|
}
|
|
29606
|
-
|
|
29708
|
+
const localPath = join6(claudeDir, diff.path);
|
|
29709
|
+
await mkdir2(dirname(localPath), { recursive: true });
|
|
29710
|
+
await writeFile5(localPath, decrypted);
|
|
29711
|
+
downloaded++;
|
|
29712
|
+
progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
|
|
29713
|
+
} catch (e) {
|
|
29714
|
+
errors2.push(`Pull ${diff.path}: ${e.message}`);
|
|
29715
|
+
}
|
|
29607
29716
|
}
|
|
29608
29717
|
progress("Saving manifest...");
|
|
29609
29718
|
await saveManifest(configDir, remoteManifest);
|
|
@@ -31766,25 +31875,53 @@ async function runDaemon() {
|
|
|
31766
31875
|
log(configDir, `Pull error: ${e.message}`);
|
|
31767
31876
|
}
|
|
31768
31877
|
}
|
|
31769
|
-
async function
|
|
31878
|
+
async function listConflictFiles() {
|
|
31770
31879
|
try {
|
|
31771
31880
|
const { readdir: readdir4 } = await import("fs/promises");
|
|
31772
31881
|
const files = await readdir4(configDir);
|
|
31773
|
-
return files.filter((f) => f.endsWith(".conflict"))
|
|
31882
|
+
return files.filter((f) => f.endsWith(".conflict"));
|
|
31774
31883
|
} catch {
|
|
31775
|
-
return
|
|
31884
|
+
return [];
|
|
31776
31885
|
}
|
|
31777
31886
|
}
|
|
31887
|
+
async function getCategories() {
|
|
31888
|
+
try {
|
|
31889
|
+
const { loadSchema: loadSchema2, getEnabledCategories: getEnabledCategories2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
31890
|
+
const schema = await loadSchema2(configDir);
|
|
31891
|
+
return getEnabledCategories2(schema);
|
|
31892
|
+
} catch {
|
|
31893
|
+
return [];
|
|
31894
|
+
}
|
|
31895
|
+
}
|
|
31896
|
+
async function deleteConflictFiles() {
|
|
31897
|
+
try {
|
|
31898
|
+
const { unlink: unlink2 } = await import("fs/promises");
|
|
31899
|
+
const files = await listConflictFiles();
|
|
31900
|
+
for (const f of files) {
|
|
31901
|
+
await unlink2(join11(configDir, f)).catch(() => {});
|
|
31902
|
+
}
|
|
31903
|
+
if (files.length > 0) {
|
|
31904
|
+
log(configDir, `Resolved ${files.length} conflict file(s) via dashboard`);
|
|
31905
|
+
}
|
|
31906
|
+
} catch {}
|
|
31907
|
+
}
|
|
31778
31908
|
async function doHeartbeat() {
|
|
31779
31909
|
try {
|
|
31780
31910
|
const client = await getAuthenticatedClient(configDir);
|
|
31781
|
-
const
|
|
31782
|
-
await
|
|
31911
|
+
const conflictFiles = await listConflictFiles();
|
|
31912
|
+
const categories = await getCategories();
|
|
31913
|
+
const result = await client.heartbeat("daemon", {
|
|
31783
31914
|
pushCount: status.pushCount,
|
|
31784
31915
|
pullCount: status.pullCount,
|
|
31785
31916
|
lastError: status.lastError,
|
|
31786
|
-
conflicts
|
|
31917
|
+
conflicts: conflictFiles.length,
|
|
31918
|
+
version: VERSION,
|
|
31919
|
+
categories,
|
|
31920
|
+
conflictFiles
|
|
31787
31921
|
});
|
|
31922
|
+
if (result.resolveConflicts) {
|
|
31923
|
+
await deleteConflictFiles();
|
|
31924
|
+
}
|
|
31788
31925
|
} catch (e) {
|
|
31789
31926
|
log(configDir, `Heartbeat error: ${e.message}`);
|
|
31790
31927
|
}
|