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.
- package/dist/index.js +72 -26
- 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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
29233
|
-
|
|
29234
|
-
|
|
29235
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
30583
|
-
const sessionFiles =
|
|
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
|
|
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("/")));
|