ccsini 0.1.33 → 0.1.35
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 +279 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27865,8 +27865,18 @@ var init_webapi = __esm(() => {
|
|
|
27865
27865
|
init_verify4();
|
|
27866
27866
|
init_sign4();
|
|
27867
27867
|
});
|
|
27868
|
+
|
|
27869
|
+
// ../shared/src/types.ts
|
|
27870
|
+
function decodeProjectPath(encoded) {
|
|
27871
|
+
const driveMatch = encoded.match(/^([A-Za-z])--(.*)$/);
|
|
27872
|
+
if (driveMatch) {
|
|
27873
|
+
return `${driveMatch[1].toUpperCase()}:/${driveMatch[2].replace(/-/g, "/")}`;
|
|
27874
|
+
}
|
|
27875
|
+
return "/" + encoded.replace(/-/g, "/");
|
|
27876
|
+
}
|
|
27877
|
+
|
|
27868
27878
|
// ../shared/src/constants.ts
|
|
27869
|
-
var DEFAULT_CATEGORY_PATTERNS, MERGE_STRATEGIES, NEVER_SYNC_PATTERNS, JWT_EXPIRY_SECONDS = 900, MAX_CONCURRENT_TRANSFERS = 50;
|
|
27879
|
+
var DEFAULT_CATEGORY_PATTERNS, MERGE_STRATEGIES, NEVER_SYNC_PATTERNS, SESSION_SYNC_DEFAULTS, JWT_EXPIRY_SECONDS = 900, MAX_CONCURRENT_TRANSFERS = 50;
|
|
27870
27880
|
var init_constants = __esm(() => {
|
|
27871
27881
|
DEFAULT_CATEGORY_PATTERNS = {
|
|
27872
27882
|
session: ["projects/**/*.jsonl"],
|
|
@@ -27910,7 +27920,6 @@ var init_constants = __esm(() => {
|
|
|
27910
27920
|
"**/node_modules/**",
|
|
27911
27921
|
"**/cache/**",
|
|
27912
27922
|
"**/tool-results/**",
|
|
27913
|
-
"projects/**/*.jsonl",
|
|
27914
27923
|
"plugins/**",
|
|
27915
27924
|
"plans/**",
|
|
27916
27925
|
"todos/**",
|
|
@@ -27920,8 +27929,14 @@ var init_constants = __esm(() => {
|
|
|
27920
27929
|
"**/dist/**",
|
|
27921
27930
|
"**/.git/**",
|
|
27922
27931
|
"**/*.bak",
|
|
27923
|
-
"settings.*.json"
|
|
27932
|
+
"settings.*.json",
|
|
27933
|
+
"history.jsonl"
|
|
27924
27934
|
];
|
|
27935
|
+
SESSION_SYNC_DEFAULTS = {
|
|
27936
|
+
enabled: false,
|
|
27937
|
+
maxPerProject: 3,
|
|
27938
|
+
maxFileSize: 50000000
|
|
27939
|
+
};
|
|
27925
27940
|
});
|
|
27926
27941
|
|
|
27927
27942
|
// ../shared/src/index.ts
|
|
@@ -28003,7 +28018,7 @@ var {
|
|
|
28003
28018
|
} = import__.default;
|
|
28004
28019
|
|
|
28005
28020
|
// src/version.ts
|
|
28006
|
-
var VERSION = "0.1.
|
|
28021
|
+
var VERSION = "0.1.35";
|
|
28007
28022
|
|
|
28008
28023
|
// src/commands/init.ts
|
|
28009
28024
|
init_source();
|
|
@@ -28674,6 +28689,7 @@ function decryptFile(masterKey, filePath, ciphertext) {
|
|
|
28674
28689
|
}
|
|
28675
28690
|
|
|
28676
28691
|
// src/core/keystore.ts
|
|
28692
|
+
init_src();
|
|
28677
28693
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
28678
28694
|
import { join } from "path";
|
|
28679
28695
|
import { homedir } from "os";
|
|
@@ -28719,6 +28735,31 @@ async function loadKeys(configDir) {
|
|
|
28719
28735
|
apiUrl: config.apiUrl
|
|
28720
28736
|
};
|
|
28721
28737
|
}
|
|
28738
|
+
async function loadSessionConfig(configDir) {
|
|
28739
|
+
const dir = configDir ?? getConfigDir();
|
|
28740
|
+
try {
|
|
28741
|
+
const configRaw = await readFile(join(dir, "config.json"), "utf-8");
|
|
28742
|
+
const config = JSON.parse(configRaw);
|
|
28743
|
+
return {
|
|
28744
|
+
enabled: config.syncSessions ?? SESSION_SYNC_DEFAULTS.enabled,
|
|
28745
|
+
maxPerProject: config.sessionsPerProject ?? SESSION_SYNC_DEFAULTS.maxPerProject
|
|
28746
|
+
};
|
|
28747
|
+
} catch {
|
|
28748
|
+
return {
|
|
28749
|
+
enabled: SESSION_SYNC_DEFAULTS.enabled,
|
|
28750
|
+
maxPerProject: SESSION_SYNC_DEFAULTS.maxPerProject
|
|
28751
|
+
};
|
|
28752
|
+
}
|
|
28753
|
+
}
|
|
28754
|
+
async function saveSessionConfig(configDir, sessionConfig) {
|
|
28755
|
+
const dir = configDir ?? getConfigDir();
|
|
28756
|
+
const configPath = join(dir, "config.json");
|
|
28757
|
+
const configRaw = await readFile(configPath, "utf-8");
|
|
28758
|
+
const config = JSON.parse(configRaw);
|
|
28759
|
+
config.syncSessions = sessionConfig.enabled;
|
|
28760
|
+
config.sessionsPerProject = sessionConfig.maxPerProject;
|
|
28761
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
28762
|
+
}
|
|
28722
28763
|
|
|
28723
28764
|
// src/core/schema.ts
|
|
28724
28765
|
init_src();
|
|
@@ -29017,6 +29058,17 @@ class CcsiniClient {
|
|
|
29017
29058
|
if (!res.ok)
|
|
29018
29059
|
throw new Error("Failed to wipe account data");
|
|
29019
29060
|
}
|
|
29061
|
+
async putProjects(projects, device) {
|
|
29062
|
+
const res = await fetch(`${this.apiUrl}/api/sync/projects`, {
|
|
29063
|
+
method: "PUT",
|
|
29064
|
+
headers: this.getHeaders(),
|
|
29065
|
+
body: JSON.stringify({ projects, device, timestamp: Date.now() })
|
|
29066
|
+
});
|
|
29067
|
+
if (!res.ok) {
|
|
29068
|
+
const text = await res.text().catch(() => "");
|
|
29069
|
+
throw new Error(`Failed to send projects (${res.status}): ${text}`);
|
|
29070
|
+
}
|
|
29071
|
+
}
|
|
29020
29072
|
async resetAll() {
|
|
29021
29073
|
const res = await fetch(`${this.apiUrl}/api/sync/reset`, {
|
|
29022
29074
|
method: "DELETE",
|
|
@@ -29061,12 +29113,48 @@ function categorizeFile(relativePath) {
|
|
|
29061
29113
|
return "unknown";
|
|
29062
29114
|
}
|
|
29063
29115
|
function matchGlobPattern(path2, pattern) {
|
|
29064
|
-
const regex2 = pattern.replace(
|
|
29116
|
+
const regex2 = pattern.replace(/\*\*\//g, "\x00DIR\x00").replace(/\*\*/g, "\x00STAR\x00").replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replace(/\0DIR\0/g, "(.+/)?").replace(/\0STAR\0/g, ".*");
|
|
29065
29117
|
return new RegExp(`^${regex2}$`).test(path2);
|
|
29066
29118
|
}
|
|
29067
29119
|
function isExcluded(relativePath) {
|
|
29068
29120
|
return NEVER_SYNC_PATTERNS.some((pattern) => matchGlobPattern(relativePath, pattern));
|
|
29069
29121
|
}
|
|
29122
|
+
function isSessionFile(relativePath) {
|
|
29123
|
+
return matchGlobPattern(relativePath, "projects/**/*.jsonl");
|
|
29124
|
+
}
|
|
29125
|
+
function filterSessionFiles(files, options) {
|
|
29126
|
+
const sessions = [];
|
|
29127
|
+
const nonSessions = [];
|
|
29128
|
+
for (const file of files) {
|
|
29129
|
+
if (isSessionFile(file.relativePath)) {
|
|
29130
|
+
sessions.push(file);
|
|
29131
|
+
} else {
|
|
29132
|
+
nonSessions.push(file);
|
|
29133
|
+
}
|
|
29134
|
+
}
|
|
29135
|
+
if (!options.enabled) {
|
|
29136
|
+
return nonSessions;
|
|
29137
|
+
}
|
|
29138
|
+
const byProject = new Map;
|
|
29139
|
+
for (const file of sessions) {
|
|
29140
|
+
if (file.size > options.maxFileSize)
|
|
29141
|
+
continue;
|
|
29142
|
+
const parts = file.relativePath.split("/");
|
|
29143
|
+
const projectKey = parts.slice(0, 2).join("/");
|
|
29144
|
+
let group = byProject.get(projectKey);
|
|
29145
|
+
if (!group) {
|
|
29146
|
+
group = [];
|
|
29147
|
+
byProject.set(projectKey, group);
|
|
29148
|
+
}
|
|
29149
|
+
group.push(file);
|
|
29150
|
+
}
|
|
29151
|
+
const kept = [];
|
|
29152
|
+
for (const group of byProject.values()) {
|
|
29153
|
+
group.sort((a, b) => b.modified - a.modified);
|
|
29154
|
+
kept.push(...group.slice(0, options.maxPerProject));
|
|
29155
|
+
}
|
|
29156
|
+
return [...nonSessions, ...kept];
|
|
29157
|
+
}
|
|
29070
29158
|
async function hashFile(filePath) {
|
|
29071
29159
|
const content = await readFile4(filePath);
|
|
29072
29160
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -29101,18 +29189,28 @@ async function scanDir(baseDir, currentDir, results, onProgress) {
|
|
|
29101
29189
|
}
|
|
29102
29190
|
}
|
|
29103
29191
|
}
|
|
29104
|
-
async function scanClaudeDir(claudeDir, onProgress) {
|
|
29192
|
+
async function scanClaudeDir(claudeDir, onProgress, sessionOptions) {
|
|
29105
29193
|
const progress = onProgress ?? (() => {});
|
|
29106
29194
|
const results = [];
|
|
29107
29195
|
progress("Scanning local files...");
|
|
29108
29196
|
await scanDir(claudeDir, claudeDir, results, progress);
|
|
29109
29197
|
progress(`Scan complete: ${results.length} files found`);
|
|
29110
|
-
|
|
29198
|
+
const sessionOpts = sessionOptions ?? {
|
|
29199
|
+
enabled: SESSION_SYNC_DEFAULTS.enabled,
|
|
29200
|
+
maxPerProject: SESSION_SYNC_DEFAULTS.maxPerProject,
|
|
29201
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
29202
|
+
};
|
|
29203
|
+
const filtered = filterSessionFiles(results, sessionOpts);
|
|
29204
|
+
if (filtered.length !== results.length) {
|
|
29205
|
+
const diff = results.length - filtered.length;
|
|
29206
|
+
progress(`Filtered ${diff} session files (${filtered.length} files to sync)`);
|
|
29207
|
+
}
|
|
29208
|
+
return filtered;
|
|
29111
29209
|
}
|
|
29112
29210
|
|
|
29113
29211
|
// src/core/manifest.ts
|
|
29114
|
-
async function generateManifest(claudeDir, deviceName, onProgress) {
|
|
29115
|
-
const files = await scanClaudeDir(claudeDir, onProgress);
|
|
29212
|
+
async function generateManifest(claudeDir, deviceName, onProgress, sessionOptions) {
|
|
29213
|
+
const files = await scanClaudeDir(claudeDir, onProgress, sessionOptions);
|
|
29116
29214
|
const entries = {};
|
|
29117
29215
|
for (const file of files) {
|
|
29118
29216
|
entries[file.relativePath] = {
|
|
@@ -29216,19 +29314,46 @@ function mergeLastWriteWins(localContent, remoteContent, localModified, remoteMo
|
|
|
29216
29314
|
|
|
29217
29315
|
// src/core/sync.ts
|
|
29218
29316
|
init_src();
|
|
29317
|
+
init_src();
|
|
29219
29318
|
function blobKey(filePath, contentHash) {
|
|
29220
29319
|
return createHash2("sha256").update(`${filePath}:${contentHash}`).digest("hex");
|
|
29221
29320
|
}
|
|
29222
29321
|
function getClaudeDir() {
|
|
29223
29322
|
return join6(homedir2(), ".claude");
|
|
29224
29323
|
}
|
|
29225
|
-
|
|
29324
|
+
function extractProjects(manifest) {
|
|
29325
|
+
const projectMap = new Map;
|
|
29326
|
+
for (const [path2, entry] of Object.entries(manifest.files)) {
|
|
29327
|
+
const match = path2.match(/^projects\/([^/]+)\//);
|
|
29328
|
+
if (!match)
|
|
29329
|
+
continue;
|
|
29330
|
+
const encoded = match[1];
|
|
29331
|
+
let proj = projectMap.get(encoded);
|
|
29332
|
+
if (!proj) {
|
|
29333
|
+
proj = { fileCount: 0, totalSize: 0, categories: new Set };
|
|
29334
|
+
projectMap.set(encoded, proj);
|
|
29335
|
+
}
|
|
29336
|
+
proj.fileCount++;
|
|
29337
|
+
proj.totalSize += entry.size;
|
|
29338
|
+
proj.categories.add(entry.category);
|
|
29339
|
+
}
|
|
29340
|
+
return Array.from(projectMap.entries()).map(([encoded, data]) => ({
|
|
29341
|
+
encodedPath: encoded,
|
|
29342
|
+
decodedPath: decodeProjectPath(encoded),
|
|
29343
|
+
name: encoded.split("-").pop() || encoded,
|
|
29344
|
+
fileCount: data.fileCount,
|
|
29345
|
+
totalSize: data.totalSize,
|
|
29346
|
+
categories: Array.from(data.categories),
|
|
29347
|
+
lastSyncedAt: manifest.timestamp
|
|
29348
|
+
}));
|
|
29349
|
+
}
|
|
29350
|
+
async function pushSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
|
|
29226
29351
|
const start = Date.now();
|
|
29227
29352
|
const claudeDir = getClaudeDir();
|
|
29228
29353
|
const errors2 = [];
|
|
29229
29354
|
let bytesTransferred = 0;
|
|
29230
29355
|
const progress = onProgress ?? (() => {});
|
|
29231
|
-
const localManifest = await generateManifest(claudeDir, deviceName, progress);
|
|
29356
|
+
const localManifest = await generateManifest(claudeDir, deviceName, progress, sessionOptions);
|
|
29232
29357
|
const fileCount = Object.keys(localManifest.files).length;
|
|
29233
29358
|
progress(`Scanned ${fileCount} files`);
|
|
29234
29359
|
progress("Fetching remote manifest...");
|
|
@@ -29291,6 +29416,12 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29291
29416
|
bytesTransferred,
|
|
29292
29417
|
durationMs
|
|
29293
29418
|
});
|
|
29419
|
+
try {
|
|
29420
|
+
const projects = extractProjects(localManifest);
|
|
29421
|
+
if (projects.length > 0) {
|
|
29422
|
+
await client.putProjects(projects, deviceName);
|
|
29423
|
+
}
|
|
29424
|
+
} catch {}
|
|
29294
29425
|
return {
|
|
29295
29426
|
action: "push",
|
|
29296
29427
|
filesChanged: toPush.length,
|
|
@@ -29299,7 +29430,7 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29299
29430
|
errors: errors2
|
|
29300
29431
|
};
|
|
29301
29432
|
}
|
|
29302
|
-
async function pullSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
29433
|
+
async function pullSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
|
|
29303
29434
|
const start = Date.now();
|
|
29304
29435
|
const claudeDir = getClaudeDir();
|
|
29305
29436
|
const errors2 = [];
|
|
@@ -29335,14 +29466,20 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29335
29466
|
const localManifest = await loadManifest(configDir);
|
|
29336
29467
|
progress("Comparing manifests...");
|
|
29337
29468
|
const diffs = diffManifests(localManifest, remoteManifest);
|
|
29338
|
-
const
|
|
29469
|
+
const sessionsEnabled = sessionOptions?.enabled ?? SESSION_SYNC_DEFAULTS.enabled;
|
|
29470
|
+
const toPull = diffs.filter((d) => (d.action === "pull" || d.action === "merge") && !isExcluded(d.path) && (sessionsEnabled || !isSessionFile(d.path)));
|
|
29339
29471
|
progress(`${toPull.length} files to pull`);
|
|
29340
29472
|
let downloaded = 0;
|
|
29341
29473
|
const chunks = chunkArray(toPull, MAX_CONCURRENT_TRANSFERS);
|
|
29342
29474
|
for (const chunk of chunks) {
|
|
29343
29475
|
await Promise.all(chunk.map(async (diff) => {
|
|
29344
29476
|
try {
|
|
29345
|
-
|
|
29477
|
+
let encrypted;
|
|
29478
|
+
try {
|
|
29479
|
+
encrypted = await client.downloadBlob(blobKey(diff.path, diff.remoteHash));
|
|
29480
|
+
} catch {
|
|
29481
|
+
encrypted = await client.downloadBlob(diff.remoteHash);
|
|
29482
|
+
}
|
|
29346
29483
|
bytesTransferred += encrypted.length;
|
|
29347
29484
|
const decrypted = decryptFile(masterKey, diff.path, encrypted);
|
|
29348
29485
|
if (diff.action === "merge" && diff.localHash) {
|
|
@@ -29608,6 +29745,7 @@ function detectPlatform() {
|
|
|
29608
29745
|
// src/commands/auto.ts
|
|
29609
29746
|
init_auth();
|
|
29610
29747
|
init_auth();
|
|
29748
|
+
init_src();
|
|
29611
29749
|
import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
29612
29750
|
import { join as join8 } from "path";
|
|
29613
29751
|
async function getMasterKey(configDir) {
|
|
@@ -29634,7 +29772,13 @@ function registerAutoCommands(program2) {
|
|
|
29634
29772
|
const masterKey = await getMasterKey(configDir);
|
|
29635
29773
|
const client = await getAuthenticatedClient(configDir);
|
|
29636
29774
|
const config = await loadKeys(configDir);
|
|
29637
|
-
const
|
|
29775
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
29776
|
+
const sessionOptions = {
|
|
29777
|
+
enabled: storedSession.enabled,
|
|
29778
|
+
maxPerProject: storedSession.maxPerProject,
|
|
29779
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
29780
|
+
};
|
|
29781
|
+
const result = await pullSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
|
|
29638
29782
|
if (result.filesChanged > 0) {
|
|
29639
29783
|
console.log(`[ccsini] Pulled ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
29640
29784
|
}
|
|
@@ -29648,7 +29792,13 @@ function registerAutoCommands(program2) {
|
|
|
29648
29792
|
const masterKey = await getMasterKey(configDir);
|
|
29649
29793
|
const client = await getAuthenticatedClient(configDir);
|
|
29650
29794
|
const config = await loadKeys(configDir);
|
|
29651
|
-
const
|
|
29795
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
29796
|
+
const sessionOptions = {
|
|
29797
|
+
enabled: storedSession.enabled,
|
|
29798
|
+
maxPerProject: storedSession.maxPerProject,
|
|
29799
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
29800
|
+
};
|
|
29801
|
+
const result = await pushSync(client, masterKey, config.deviceName, configDir, undefined, sessionOptions);
|
|
29652
29802
|
if (result.filesChanged > 0) {
|
|
29653
29803
|
console.log(`[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
29654
29804
|
}
|
|
@@ -29840,6 +29990,7 @@ Uninstall failed. Try manually:`);
|
|
|
29840
29990
|
|
|
29841
29991
|
// src/commands/sync.ts
|
|
29842
29992
|
init_auth();
|
|
29993
|
+
init_src();
|
|
29843
29994
|
import { readFile as readFile8 } from "fs/promises";
|
|
29844
29995
|
import { join as join10 } from "path";
|
|
29845
29996
|
async function getMasterKey2(configDir) {
|
|
@@ -29879,7 +30030,7 @@ function formatBytes(bytes) {
|
|
|
29879
30030
|
}
|
|
29880
30031
|
function registerSyncCommands(program2) {
|
|
29881
30032
|
const syncCmd = program2.command("sync").description("Sync management commands");
|
|
29882
|
-
syncCmd.command("push").description("Push local changes to cloud").action(async () => {
|
|
30033
|
+
syncCmd.command("push").description("Push local changes to cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
29883
30034
|
const configDir = getConfigDir();
|
|
29884
30035
|
if (!await configExists(configDir)) {
|
|
29885
30036
|
console.error("Not initialized. Run 'ccsini init' first.");
|
|
@@ -29891,10 +30042,16 @@ function registerSyncCommands(program2) {
|
|
|
29891
30042
|
const masterKey = await getMasterKey2(configDir);
|
|
29892
30043
|
const client = await getAuthenticatedClient2(configDir);
|
|
29893
30044
|
const config = await loadKeys(configDir);
|
|
30045
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
30046
|
+
const sessionOptions = {
|
|
30047
|
+
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
30048
|
+
maxPerProject: storedSession.maxPerProject,
|
|
30049
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30050
|
+
};
|
|
29894
30051
|
const spinner = ora2("Scanning files...").start();
|
|
29895
30052
|
const result = await pushSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
29896
30053
|
spinner.text = msg;
|
|
29897
|
-
});
|
|
30054
|
+
}, sessionOptions);
|
|
29898
30055
|
if (result.errors.length > 0) {
|
|
29899
30056
|
spinner.warn("Push completed with errors");
|
|
29900
30057
|
for (const err of result.errors) {
|
|
@@ -29904,12 +30061,15 @@ function registerSyncCommands(program2) {
|
|
|
29904
30061
|
spinner.succeed("Push complete");
|
|
29905
30062
|
}
|
|
29906
30063
|
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
30064
|
+
if (sessionOptions.enabled) {
|
|
30065
|
+
console.log(chalk2.dim(" Sessions: enabled"));
|
|
30066
|
+
}
|
|
29907
30067
|
} catch (e) {
|
|
29908
30068
|
console.error(`Push failed: ${e.message}`);
|
|
29909
30069
|
process.exit(1);
|
|
29910
30070
|
}
|
|
29911
30071
|
});
|
|
29912
|
-
syncCmd.command("pull").description("Pull changes from cloud").action(async () => {
|
|
30072
|
+
syncCmd.command("pull").description("Pull changes from cloud").option("--with-sessions", "Include session files in sync").option("--no-sessions", "Exclude session files from sync").action(async (opts) => {
|
|
29913
30073
|
const configDir = getConfigDir();
|
|
29914
30074
|
if (!await configExists(configDir)) {
|
|
29915
30075
|
console.error("Not initialized. Run 'ccsini init' first.");
|
|
@@ -29921,10 +30081,16 @@ function registerSyncCommands(program2) {
|
|
|
29921
30081
|
const masterKey = await getMasterKey2(configDir);
|
|
29922
30082
|
const client = await getAuthenticatedClient2(configDir);
|
|
29923
30083
|
const config = await loadKeys(configDir);
|
|
30084
|
+
const storedSession = await loadSessionConfig(configDir);
|
|
30085
|
+
const sessionOptions = {
|
|
30086
|
+
enabled: opts.withSessions ? true : opts.sessions === false ? false : storedSession.enabled,
|
|
30087
|
+
maxPerProject: storedSession.maxPerProject,
|
|
30088
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30089
|
+
};
|
|
29924
30090
|
const spinner = ora2("Pulling from cloud...").start();
|
|
29925
30091
|
const result = await pullSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
29926
30092
|
spinner.text = msg;
|
|
29927
|
-
});
|
|
30093
|
+
}, sessionOptions);
|
|
29928
30094
|
if (result.errors.length > 0) {
|
|
29929
30095
|
spinner.warn("Pull completed with errors");
|
|
29930
30096
|
for (const err of result.errors) {
|
|
@@ -29934,6 +30100,9 @@ function registerSyncCommands(program2) {
|
|
|
29934
30100
|
spinner.succeed("Pull complete");
|
|
29935
30101
|
}
|
|
29936
30102
|
console.log(chalk2.dim(` ${result.filesChanged} files changed, ${formatBytes(result.bytesTransferred)} transferred in ${result.durationMs}ms`));
|
|
30103
|
+
if (sessionOptions.enabled) {
|
|
30104
|
+
console.log(chalk2.dim(" Sessions: enabled"));
|
|
30105
|
+
}
|
|
29937
30106
|
} catch (e) {
|
|
29938
30107
|
console.error(`Pull failed: ${e.message}`);
|
|
29939
30108
|
process.exit(1);
|
|
@@ -30151,6 +30320,95 @@ Account fully reset. Run 'ccsini init --token <token>' to start fresh.`));
|
|
|
30151
30320
|
});
|
|
30152
30321
|
}
|
|
30153
30322
|
|
|
30323
|
+
// src/commands/sessions.ts
|
|
30324
|
+
init_src();
|
|
30325
|
+
function formatBytes2(bytes) {
|
|
30326
|
+
if (bytes < 1024)
|
|
30327
|
+
return `${bytes} B`;
|
|
30328
|
+
if (bytes < 1024 * 1024)
|
|
30329
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
30330
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
30331
|
+
}
|
|
30332
|
+
function registerSessionsCommand(program2) {
|
|
30333
|
+
const sessionsCmd = program2.command("sessions").description("Configure session sync (continue conversations across devices)");
|
|
30334
|
+
sessionsCmd.command("enable").description("Enable session sync").option("-n, --per-project <count>", "Max sessions per project", String(SESSION_SYNC_DEFAULTS.maxPerProject)).action(async (opts) => {
|
|
30335
|
+
const configDir = getConfigDir();
|
|
30336
|
+
if (!await configExists(configDir)) {
|
|
30337
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30338
|
+
process.exit(1);
|
|
30339
|
+
}
|
|
30340
|
+
const maxPerProject = parseInt(opts.perProject, 10);
|
|
30341
|
+
if (isNaN(maxPerProject) || maxPerProject < 1) {
|
|
30342
|
+
console.error("--per-project must be a positive integer");
|
|
30343
|
+
process.exit(1);
|
|
30344
|
+
}
|
|
30345
|
+
await saveSessionConfig(configDir, {
|
|
30346
|
+
enabled: true,
|
|
30347
|
+
maxPerProject
|
|
30348
|
+
});
|
|
30349
|
+
console.log(`Session sync enabled (${maxPerProject} sessions per project)`);
|
|
30350
|
+
});
|
|
30351
|
+
sessionsCmd.command("disable").description("Disable session sync").action(async () => {
|
|
30352
|
+
const configDir = getConfigDir();
|
|
30353
|
+
if (!await configExists(configDir)) {
|
|
30354
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30355
|
+
process.exit(1);
|
|
30356
|
+
}
|
|
30357
|
+
const current = await loadSessionConfig(configDir);
|
|
30358
|
+
await saveSessionConfig(configDir, {
|
|
30359
|
+
enabled: false,
|
|
30360
|
+
maxPerProject: current.maxPerProject
|
|
30361
|
+
});
|
|
30362
|
+
console.log("Session sync disabled");
|
|
30363
|
+
});
|
|
30364
|
+
sessionsCmd.command("status").description("Show session sync status and preview").action(async () => {
|
|
30365
|
+
const configDir = getConfigDir();
|
|
30366
|
+
if (!await configExists(configDir)) {
|
|
30367
|
+
console.error("Not initialized. Run 'ccsini init' first.");
|
|
30368
|
+
process.exit(1);
|
|
30369
|
+
}
|
|
30370
|
+
const chalk2 = (await Promise.resolve().then(() => (init_source(), exports_source))).default;
|
|
30371
|
+
const ora2 = (await Promise.resolve().then(() => (init_ora(), exports_ora))).default;
|
|
30372
|
+
const sessionConfig = await loadSessionConfig(configDir);
|
|
30373
|
+
console.log(chalk2.bold("Session Sync"));
|
|
30374
|
+
console.log(` Status: ${sessionConfig.enabled ? chalk2.green("enabled") : chalk2.yellow("disabled")}`);
|
|
30375
|
+
console.log(` Per project: ${sessionConfig.maxPerProject}`);
|
|
30376
|
+
console.log(` Max file size: ${formatBytes2(SESSION_SYNC_DEFAULTS.maxFileSize)}`);
|
|
30377
|
+
const spinner = ora2("Scanning session files...").start();
|
|
30378
|
+
try {
|
|
30379
|
+
const claudeDir = getClaudeDir();
|
|
30380
|
+
const enabledOpts = {
|
|
30381
|
+
enabled: true,
|
|
30382
|
+
maxPerProject: sessionConfig.maxPerProject,
|
|
30383
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30384
|
+
};
|
|
30385
|
+
const withSessions = await scanClaudeDir(claudeDir, undefined, enabledOpts);
|
|
30386
|
+
const sessionFiles = withSessions.filter((f) => isSessionFile(f.relativePath));
|
|
30387
|
+
const disabledOpts = {
|
|
30388
|
+
enabled: false,
|
|
30389
|
+
maxPerProject: sessionConfig.maxPerProject,
|
|
30390
|
+
maxFileSize: SESSION_SYNC_DEFAULTS.maxFileSize
|
|
30391
|
+
};
|
|
30392
|
+
const withoutSessions = await scanClaudeDir(claudeDir, undefined, disabledOpts);
|
|
30393
|
+
spinner.stop();
|
|
30394
|
+
const totalSessionSize = sessionFiles.reduce((sum, f) => sum + f.size, 0);
|
|
30395
|
+
const projects = new Set(sessionFiles.map((f) => f.relativePath.split("/").slice(0, 2).join("/")));
|
|
30396
|
+
console.log(chalk2.bold(`
|
|
30397
|
+
Preview`));
|
|
30398
|
+
console.log(` Projects: ${projects.size}`);
|
|
30399
|
+
console.log(` Sessions: ${sessionFiles.length} files`);
|
|
30400
|
+
console.log(` Session size: ${formatBytes2(totalSessionSize)}`);
|
|
30401
|
+
console.log(` Other files: ${withoutSessions.length}`);
|
|
30402
|
+
if (!sessionConfig.enabled) {
|
|
30403
|
+
console.log(chalk2.dim(`
|
|
30404
|
+
Run 'ccsini sessions enable' to start syncing sessions`));
|
|
30405
|
+
}
|
|
30406
|
+
} catch (e) {
|
|
30407
|
+
spinner.fail(`Scan failed: ${e.message}`);
|
|
30408
|
+
}
|
|
30409
|
+
});
|
|
30410
|
+
}
|
|
30411
|
+
|
|
30154
30412
|
// src/index.ts
|
|
30155
30413
|
var program2 = new Command;
|
|
30156
30414
|
program2.name("ccsini").description("Claude Code seamless sync across devices").version(VERSION);
|
|
@@ -30161,6 +30419,7 @@ registerSelfCommands(program2);
|
|
|
30161
30419
|
registerSyncCommands(program2);
|
|
30162
30420
|
registerHooksCommands(program2);
|
|
30163
30421
|
registerResetCommand(program2);
|
|
30422
|
+
registerSessionsCommand(program2);
|
|
30164
30423
|
program2.command("version").description("Show current version").action(() => {
|
|
30165
30424
|
console.log(VERSION);
|
|
30166
30425
|
});
|