ccsini 0.1.48 → 0.1.50

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 +72 -26
  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.48";
28023
+ var VERSION = "0.1.50";
28024
28024
 
28025
28025
  // src/commands/init.ts
28026
28026
  init_source();
@@ -29098,7 +29098,7 @@ class CcsiniClient {
29098
29098
  // src/core/sync.ts
29099
29099
  import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
29100
29100
  import { join as join6, dirname } from "path";
29101
- import { homedir as homedir2 } from "os";
29101
+ import { homedir as homedir3 } from "os";
29102
29102
  import { createHash as createHash2 } from "crypto";
29103
29103
 
29104
29104
  // src/core/manifest.ts
@@ -29107,8 +29107,9 @@ import { join as join5 } from "path";
29107
29107
 
29108
29108
  // src/core/scanner.ts
29109
29109
  init_src();
29110
- import { readdir, readFile as readFile4, stat } from "fs/promises";
29110
+ import { readdir, readFile as readFile4, stat, appendFile } from "fs/promises";
29111
29111
  import { join as join4, relative } from "path";
29112
+ import { homedir as homedir2 } from "os";
29112
29113
  import { createHash } from "crypto";
29113
29114
  function categorizeFile(relativePath) {
29114
29115
  for (const [category, patterns] of Object.entries(DEFAULT_CATEGORY_PATTERNS)) {
@@ -29163,13 +29164,35 @@ function filterSessionFiles(files, options) {
29163
29164
  }
29164
29165
  return [...nonSessions, ...kept];
29165
29166
  }
29166
- async function hashFile(filePath) {
29167
+ var cacheHits = 0;
29168
+ var cacheMisses = 0;
29169
+ async function logDebug(message2) {
29170
+ const logPath = join4(homedir2(), ".ccsini", "cache-debug.log");
29171
+ const timestamp = new Date().toISOString();
29172
+ await appendFile(logPath, `[${timestamp}] ${message2}
29173
+ `).catch(() => {});
29174
+ }
29175
+ async function hashFileIfNeeded(filePath, relativePath, currentMtime, cachedManifest) {
29176
+ if (cachedManifest) {
29177
+ const cached = cachedManifest.files[relativePath];
29178
+ if (cached && Math.abs(cached.modified - currentMtime) < 100) {
29179
+ cacheHits++;
29180
+ return cached.hash;
29181
+ }
29182
+ if (cached) {
29183
+ const diff = Math.abs(cached.modified - currentMtime);
29184
+ if (diff >= 100) {
29185
+ await logDebug(`Cache MISS: ${relativePath} (\u0394${diff.toFixed(2)}ms)`);
29186
+ }
29187
+ }
29188
+ }
29189
+ cacheMisses++;
29167
29190
  const content = await readFile4(filePath);
29168
29191
  return createHash("sha256").update(content).digest("hex");
29169
29192
  }
29170
29193
  var EXCLUDED_DIRS = NEVER_SYNC_PATTERNS.filter((p) => p.endsWith("/**") && !p.startsWith("**/")).map((p) => p.replace("/**", ""));
29171
29194
  var EXCLUDED_DIR_NAMES = new Set(NEVER_SYNC_PATTERNS.filter((p) => p.startsWith("**/") && p.endsWith("/**")).map((p) => p.replace("**/", "").replace("/**", "")));
29172
- async function scanDir(baseDir, currentDir, results, onProgress) {
29195
+ async function scanDir(baseDir, currentDir, results, onProgress, cachedManifest) {
29173
29196
  const entries = await readdir(currentDir, { withFileTypes: true });
29174
29197
  for (const entry of entries) {
29175
29198
  const fullPath = join4(currentDir, entry.name);
@@ -29179,12 +29202,12 @@ async function scanDir(baseDir, currentDir, results, onProgress) {
29179
29202
  continue;
29180
29203
  if (EXCLUDED_DIRS.includes(relativePath))
29181
29204
  continue;
29182
- await scanDir(baseDir, fullPath, results, onProgress);
29205
+ await scanDir(baseDir, fullPath, results, onProgress, cachedManifest);
29183
29206
  } else if (entry.isFile()) {
29184
29207
  if (isExcluded(relativePath))
29185
29208
  continue;
29186
29209
  const fileStat = await stat(fullPath);
29187
- const hash = await hashFile(fullPath);
29210
+ const hash = await hashFileIfNeeded(fullPath, relativePath, fileStat.mtimeMs, cachedManifest);
29188
29211
  results.push({
29189
29212
  relativePath,
29190
29213
  absolutePath: fullPath,
@@ -29197,12 +29220,19 @@ async function scanDir(baseDir, currentDir, results, onProgress) {
29197
29220
  }
29198
29221
  }
29199
29222
  }
29200
- async function scanClaudeDir(claudeDir, onProgress, sessionOptions) {
29223
+ async function scanClaudeDir(claudeDir, onProgress, sessionOptions, cachedManifest) {
29201
29224
  const progress = onProgress ?? (() => {});
29202
29225
  const results = [];
29226
+ cacheHits = 0;
29227
+ cacheMisses = 0;
29203
29228
  progress("Scanning local files...");
29204
- await scanDir(claudeDir, claudeDir, results, progress);
29229
+ await scanDir(claudeDir, claudeDir, results, progress, cachedManifest);
29205
29230
  progress(`Scan complete: ${results.length} files found`);
29231
+ if (cachedManifest && cacheHits + cacheMisses > 0) {
29232
+ const total = cacheHits + cacheMisses;
29233
+ const hitRate = (cacheHits / total * 100).toFixed(1);
29234
+ progress(`Cache: ${cacheHits} hits, ${cacheMisses} misses (${hitRate}% hit rate)`);
29235
+ }
29206
29236
  const sessionOpts = sessionOptions ?? {
29207
29237
  enabled: SESSION_SYNC_DEFAULTS.enabled,
29208
29238
  maxPerProject: SESSION_SYNC_DEFAULTS.maxPerProject,
@@ -29213,14 +29243,21 @@ async function scanClaudeDir(claudeDir, onProgress, sessionOptions) {
29213
29243
  const diff = results.length - filtered.length;
29214
29244
  progress(`Filtered ${diff} session files (${filtered.length} files to sync)`);
29215
29245
  }
29216
- return filtered;
29246
+ const stats = cacheHits + cacheMisses > 0 ? {
29247
+ hits: cacheHits,
29248
+ misses: cacheMisses,
29249
+ hitRate: cacheHits / (cacheHits + cacheMisses) * 100
29250
+ } : undefined;
29251
+ return { files: filtered, cacheStats: stats };
29217
29252
  }
29218
29253
 
29219
29254
  // src/core/manifest.ts
29220
29255
  async function generateManifest(claudeDir, deviceName, onProgress, sessionOptions) {
29221
- const files = await scanClaudeDir(claudeDir, onProgress, sessionOptions);
29256
+ const configDir = getConfigDir();
29257
+ const cachedManifest = await loadManifest(configDir);
29258
+ const scanResult = await scanClaudeDir(claudeDir, onProgress, sessionOptions, cachedManifest ?? undefined);
29222
29259
  const entries = {};
29223
- for (const file of files) {
29260
+ for (const file of scanResult.files) {
29224
29261
  entries[file.relativePath] = {
29225
29262
  hash: file.hash,
29226
29263
  size: file.size,
@@ -29229,10 +29266,13 @@ async function generateManifest(claudeDir, deviceName, onProgress, sessionOption
29229
29266
  };
29230
29267
  }
29231
29268
  return {
29232
- version: 1,
29233
- device: deviceName,
29234
- timestamp: Date.now(),
29235
- files: entries
29269
+ manifest: {
29270
+ version: 1,
29271
+ device: deviceName,
29272
+ timestamp: Date.now(),
29273
+ files: entries
29274
+ },
29275
+ cacheStats: scanResult.cacheStats
29236
29276
  };
29237
29277
  }
29238
29278
  async function saveManifest(configDir, manifest) {
@@ -29327,7 +29367,7 @@ function blobKey(filePath, contentHash) {
29327
29367
  return createHash2("sha256").update(`${filePath}:${contentHash}`).digest("hex");
29328
29368
  }
29329
29369
  function getClaudeDir() {
29330
- return join6(homedir2(), ".claude");
29370
+ return join6(homedir3(), ".claude");
29331
29371
  }
29332
29372
  function extractProjects(manifest) {
29333
29373
  const projectMap = new Map;
@@ -29362,7 +29402,7 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
29362
29402
  const conflicts = [];
29363
29403
  let bytesTransferred = 0;
29364
29404
  const progress = onProgress ?? (() => {});
29365
- const localManifest = await generateManifest(claudeDir, deviceName, progress, sessionOptions);
29405
+ const { manifest: localManifest, cacheStats } = await generateManifest(claudeDir, deviceName, progress, sessionOptions);
29366
29406
  const fileCount = Object.keys(localManifest.files).length;
29367
29407
  progress(`Scanned ${fileCount} files`);
29368
29408
  progress("Fetching remote manifest...");
@@ -29456,7 +29496,8 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress, se
29456
29496
  bytesTransferred,
29457
29497
  durationMs,
29458
29498
  errors: errors2,
29459
- conflicts
29499
+ conflicts,
29500
+ cacheStats
29460
29501
  };
29461
29502
  }
29462
29503
  async function pullSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
@@ -29603,7 +29644,7 @@ import { execSync } from "child_process";
29603
29644
  import { platform } from "os";
29604
29645
  import { writeFile as writeFile6, unlink } from "fs/promises";
29605
29646
  import { join as join7 } from "path";
29606
- import { homedir as homedir3 } from "os";
29647
+ import { homedir as homedir4 } from "os";
29607
29648
  var TASK_NAME = "ccsini-heartbeat";
29608
29649
  var INTERVAL_MINUTES = 3;
29609
29650
  async function installHeartbeatScheduler() {
@@ -29638,7 +29679,7 @@ async function uninstallWindows() {
29638
29679
  } catch {}
29639
29680
  }
29640
29681
  function getLaunchAgentPath() {
29641
- return join7(homedir3(), "Library", "LaunchAgents", `com.ccsini.heartbeat.plist`);
29682
+ return join7(homedir4(), "Library", "LaunchAgents", `com.ccsini.heartbeat.plist`);
29642
29683
  }
29643
29684
  async function installMacOS() {
29644
29685
  const plistPath = getLaunchAgentPath();
@@ -29977,7 +30018,11 @@ function registerAutoCommands(program2) {
29977
30018
  };
29978
30019
  const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions, { skipConflictBackup: true });
29979
30020
  if (result.filesChanged > 0) {
29980
- console.log(`[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`);
30021
+ let msg = `[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`;
30022
+ if (result.cacheStats && result.cacheStats.hits > 0) {
30023
+ msg += ` [cache: ${result.cacheStats.hitRate.toFixed(0)}% hits]`;
30024
+ }
30025
+ console.log(msg);
29981
30026
  }
29982
30027
  if (result.conflicts.length > 0) {
29983
30028
  console.log(`[ccsini] \u26A0 ${result.conflicts.length} conflict(s) \u2014 check .conflict files in ~/.claude/`);
@@ -30397,7 +30442,7 @@ function registerSyncCommands(program2) {
30397
30442
  const spinner = ora2("Checking status...").start();
30398
30443
  const localManifest = await loadManifest(configDir);
30399
30444
  const claudeDir = getClaudeDir();
30400
- const currentManifest = await generateManifest(claudeDir, config.deviceName, (msg) => {
30445
+ const { manifest: currentManifest } = await generateManifest(claudeDir, config.deviceName, (msg) => {
30401
30446
  spinner.text = msg;
30402
30447
  });
30403
30448
  let remoteManifest = null;
@@ -30579,14 +30624,15 @@ function registerSessionsCommand(program2) {
30579
30624
  maxPerProject: sessionConfig.maxPerProject,
30580
30625
  maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
30581
30626
  };
30582
- const withSessions = await scanClaudeDir(claudeDir, undefined, enabledOpts);
30583
- const sessionFiles = withSessions.filter((f) => isSessionFile(f.relativePath));
30627
+ const withSessionsResult = await scanClaudeDir(claudeDir, undefined, enabledOpts);
30628
+ const sessionFiles = withSessionsResult.files.filter((f) => isSessionFile(f.relativePath));
30584
30629
  const disabledOpts = {
30585
30630
  enabled: false,
30586
30631
  maxPerProject: sessionConfig.maxPerProject,
30587
30632
  maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
30588
30633
  };
30589
- const withoutSessions = await scanClaudeDir(claudeDir, undefined, disabledOpts);
30634
+ const withoutSessionsResult = await scanClaudeDir(claudeDir, undefined, disabledOpts);
30635
+ const withoutSessions = withoutSessionsResult.files;
30590
30636
  spinner.stop();
30591
30637
  const totalSessionSize = sessionFiles.reduce((sum, f) => sum + f.size, 0);
30592
30638
  const projects = new Set(sessionFiles.map((f) => f.relativePath.split("/").slice(0, 2).join("/")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsini",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "Claude Code seamless sync across devices",
5
5
  "type": "module",
6
6
  "bin": {