ccsini 0.1.44 → 0.1.46

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.
Files changed (2) hide show
  1. package/dist/index.js +121 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28020,7 +28020,7 @@ var {
28020
28020
  } = import__.default;
28021
28021
 
28022
28022
  // src/version.ts
28023
- var VERSION = "0.1.44";
28023
+ var VERSION = "0.1.46";
28024
28024
 
28025
28025
  // src/commands/init.ts
28026
28026
  init_source();
@@ -29355,10 +29355,11 @@ function extractProjects(manifest) {
29355
29355
  lastSyncedAt: manifest.timestamp
29356
29356
  }));
29357
29357
  }
29358
- async function pushSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
29358
+ async function pushSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions, pushOptions) {
29359
29359
  const start = Date.now();
29360
29360
  const claudeDir = getClaudeDir();
29361
29361
  const errors2 = [];
29362
+ const conflicts = [];
29362
29363
  let bytesTransferred = 0;
29363
29364
  const progress = onProgress ?? (() => {});
29364
29365
  const localManifest = await generateManifest(claudeDir, deviceName, progress, sessionOptions);
@@ -29379,7 +29380,7 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
29379
29380
  const diffs = diffManifests(localManifest, remoteManifest);
29380
29381
  const toPush = diffs.filter((d) => d.action === "push" || d.action === "merge");
29381
29382
  progress(`${toPush.length} files to push`);
29382
- if (remoteManifest) {
29383
+ if (remoteManifest && !pushOptions?.skipConflictBackup) {
29383
29384
  const merges = toPush.filter((d) => d.action === "merge" && d.remoteHash);
29384
29385
  for (const diff of merges) {
29385
29386
  try {
@@ -29393,6 +29394,7 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
29393
29394
  const conflictPath = join6(claudeDir, `${diff.path}.conflict`);
29394
29395
  await mkdir2(dirname(conflictPath), { recursive: true });
29395
29396
  await writeFile5(conflictPath, decrypted);
29397
+ conflicts.push(diff.path);
29396
29398
  progress(`Saved conflict backup: ${diff.path}.conflict`);
29397
29399
  } catch {}
29398
29400
  }
@@ -29453,13 +29455,15 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
29453
29455
  filesChanged: toPush.length,
29454
29456
  bytesTransferred,
29455
29457
  durationMs,
29456
- errors: errors2
29458
+ errors: errors2,
29459
+ conflicts
29457
29460
  };
29458
29461
  }
29459
29462
  async function pullSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
29460
29463
  const start = Date.now();
29461
29464
  const claudeDir = getClaudeDir();
29462
29465
  const errors2 = [];
29466
+ const conflicts = [];
29463
29467
  let bytesTransferred = 0;
29464
29468
  const progress = onProgress ?? (() => {});
29465
29469
  progress("Fetching remote manifest...");
@@ -29470,7 +29474,8 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress, se
29470
29474
  filesChanged: 0,
29471
29475
  bytesTransferred: 0,
29472
29476
  durationMs: Date.now() - start,
29473
- errors: []
29477
+ errors: [],
29478
+ conflicts: []
29474
29479
  };
29475
29480
  }
29476
29481
  let remoteManifest;
@@ -29485,7 +29490,8 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress, se
29485
29490
  durationMs: Date.now() - start,
29486
29491
  errors: [
29487
29492
  "Failed to decrypt remote manifest - wrong encryption password?"
29488
- ]
29493
+ ],
29494
+ conflicts: []
29489
29495
  };
29490
29496
  }
29491
29497
  progress("Loading local manifest...");
@@ -29525,12 +29531,14 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress, se
29525
29531
  const result = mergeLastWriteWins(localContent, remoteContent, localEntry.modified, entry.modified);
29526
29532
  await writeFile5(localPath2, result.content);
29527
29533
  await writeFile5(`${localPath2}.conflict`, result.backupContent);
29534
+ conflicts.push(diff.path);
29528
29535
  downloaded++;
29529
29536
  progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path}`);
29530
29537
  return;
29531
29538
  }
29532
29539
  await writeFile5(`${localPath2}.conflict`, localContent);
29533
29540
  await writeFile5(localPath2, remoteContent);
29541
+ conflicts.push(diff.path);
29534
29542
  downloaded++;
29535
29543
  progress(`Downloading ${downloaded}/${toPull.length}: ${diff.path} (conflict saved)`);
29536
29544
  return;
@@ -29560,7 +29568,8 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress, se
29560
29568
  filesChanged: toPull.length,
29561
29569
  bytesTransferred,
29562
29570
  durationMs,
29563
- errors: errors2
29571
+ errors: errors2,
29572
+ conflicts
29564
29573
  };
29565
29574
  }
29566
29575
  function chunkArray(arr, size) {
@@ -29947,6 +29956,9 @@ function registerAutoCommands(program2) {
29947
29956
  if (result.filesChanged > 0) {
29948
29957
  console.log(`[ccsini] Pulled ${result.filesChanged} files (${result.durationMs}ms)`);
29949
29958
  }
29959
+ if (result.conflicts.length > 0) {
29960
+ console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
29961
+ }
29950
29962
  } catch {}
29951
29963
  });
29952
29964
  program2.command("auto-push").description("Auto-push changes (called by Claude Code hooks)").action(async () => {
@@ -29963,10 +29975,13 @@ function registerAutoCommands(program2) {
29963
29975
  maxPerProject: storedSession.maxPerProject,
29964
29976
  maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
29965
29977
  };
29966
- const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
29978
+ const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
29967
29979
  if (result.filesChanged > 0) {
29968
29980
  console.log(`[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`);
29969
29981
  }
29982
+ if (result.conflicts.length > 0) {
29983
+ console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
29984
+ }
29970
29985
  } catch {}
29971
29986
  });
29972
29987
  program2.command("unlock").description("Enter encryption password to enable auto-sync").action(async () => {
@@ -30231,6 +30246,9 @@ function registerSyncCommands(program2) {
30231
30246
  if (sessionOptions.enabled) {
30232
30247
  console.log(chalk2.dim(" Sessions: enabled"));
30233
30248
  }
30249
+ if (result.conflicts.length > 0) {
30250
+ console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
30251
+ }
30234
30252
  } catch (e) {
30235
30253
  console.error(`Push failed: ${e.message}`);
30236
30254
  process.exit(1);
@@ -30270,6 +30288,9 @@ function registerSyncCommands(program2) {
30270
30288
  if (sessionOptions.enabled) {
30271
30289
  console.log(chalk2.dim(" Sessions: enabled"));
30272
30290
  }
30291
+ if (result.conflicts.length > 0) {
30292
+ console.log(chalk2.yellow(` \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`));
30293
+ }
30273
30294
  } catch (e) {
30274
30295
  console.error(`Pull failed: ${e.message}`);
30275
30296
  process.exit(1);
@@ -30576,6 +30597,97 @@ function registerSessionsCommand(program2) {
30576
30597
  });
30577
30598
  }
30578
30599
 
30600
+ // src/commands/conflicts.ts
30601
+ import { readdir as readdir2, readFile as readFile9, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
30602
+ import { join as join12, relative as relative2 } from "path";
30603
+ async function findConflictFiles(dir, base) {
30604
+ base = base ?? dir;
30605
+ const results = [];
30606
+ const entries = await readdir2(dir, { withFileTypes: true });
30607
+ for (const entry of entries) {
30608
+ const full = join12(dir, entry.name);
30609
+ if (entry.isDirectory()) {
30610
+ results.push(...await findConflictFiles(full, base));
30611
+ } else if (entry.name.endsWith(".conflict")) {
30612
+ results.push(relative2(base, full).split("\\").join("/"));
30613
+ }
30614
+ }
30615
+ return results;
30616
+ }
30617
+ function registerConflictsCommand(program2) {
30618
+ program2.command("conflicts").description("List and resolve sync conflicts").option("--keep <side>", "Resolve all: 'local' keeps .conflict, 'remote' discards .conflict").action(async (opts) => {
30619
+ const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
30620
+ const claudeDir = getClaudeDir();
30621
+ const conflictFiles = await findConflictFiles(claudeDir);
30622
+ if (conflictFiles.length === 0) {
30623
+ console.log(chalk2.green("No conflicts found."));
30624
+ return;
30625
+ }
30626
+ console.log(chalk2.bold(`
30627
+ ${conflictFiles.length} conflict(s) found:
30628
+ `));
30629
+ for (const file of conflictFiles) {
30630
+ const original = file.replace(/\.conflict$/, "");
30631
+ console.log(` ${chalk2.yellow("\u26A0")} ${original}`);
30632
+ console.log(chalk2.dim(` conflict: ~/.claude/${file}`));
30633
+ }
30634
+ console.log();
30635
+ if (opts.keep === "remote") {
30636
+ for (const file of conflictFiles) {
30637
+ await unlink2(join12(claudeDir, file));
30638
+ }
30639
+ console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept remote versions.`));
30640
+ return;
30641
+ }
30642
+ if (opts.keep === "local") {
30643
+ for (const file of conflictFiles) {
30644
+ const conflictPath = join12(claudeDir, file);
30645
+ const originalPath = conflictPath.replace(/\.conflict$/, "");
30646
+ const conflictContent = await readFile9(conflictPath);
30647
+ await writeFile9(originalPath, conflictContent);
30648
+ await unlink2(conflictPath);
30649
+ }
30650
+ console.log(chalk2.green(`Resolved ${conflictFiles.length} conflict(s) \u2014 kept local versions.`));
30651
+ return;
30652
+ }
30653
+ const inquirer2 = await Promise.resolve().then(() => (init_dist16(), exports_dist));
30654
+ for (const file of conflictFiles) {
30655
+ const original = file.replace(/\.conflict$/, "");
30656
+ const conflictPath = join12(claudeDir, file);
30657
+ const originalPath = join12(claudeDir, original);
30658
+ const { action } = await inquirer2.default.prompt([
30659
+ {
30660
+ type: "list",
30661
+ name: "action",
30662
+ message: `${original}:`,
30663
+ choices: [
30664
+ { name: "Keep remote (current file)", value: "remote" },
30665
+ { name: "Keep local (restore from .conflict)", value: "local" },
30666
+ { name: "Skip", value: "skip" }
30667
+ ]
30668
+ }
30669
+ ]);
30670
+ if (action === "remote") {
30671
+ await unlink2(conflictPath);
30672
+ console.log(chalk2.dim(` Kept remote \u2014 deleted ${file}`));
30673
+ } else if (action === "local") {
30674
+ const conflictContent = await readFile9(conflictPath);
30675
+ await writeFile9(originalPath, conflictContent);
30676
+ await unlink2(conflictPath);
30677
+ console.log(chalk2.dim(` Restored local version`));
30678
+ }
30679
+ }
30680
+ const remaining = await findConflictFiles(claudeDir);
30681
+ if (remaining.length === 0) {
30682
+ console.log(chalk2.green(`
30683
+ All conflicts resolved.`));
30684
+ } else {
30685
+ console.log(chalk2.yellow(`
30686
+ ${remaining.length} conflict(s) remaining.`));
30687
+ }
30688
+ });
30689
+ }
30690
+
30579
30691
  // src/commands/menu.ts
30580
30692
  init_source();
30581
30693
  init_dist16();
@@ -30642,6 +30754,7 @@ registerSyncCommands(program2);
30642
30754
  registerHooksCommands(program2);
30643
30755
  registerResetCommand(program2);
30644
30756
  registerSessionsCommand(program2);
30757
+ registerConflictsCommand(program2);
30645
30758
  program2.command("version").description("Show current version").action(() => {
30646
30759
  console.log(VERSION);
30647
30760
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsini",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "Claude Code seamless sync across devices",
5
5
  "type": "module",
6
6
  "bin": {