ccsini 0.1.34 → 0.1.36
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 +327 -17
- 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/**",
|
|
@@ -27923,6 +27932,11 @@ var init_constants = __esm(() => {
|
|
|
27923
27932
|
"settings.*.json",
|
|
27924
27933
|
"history.jsonl"
|
|
27925
27934
|
];
|
|
27935
|
+
SESSION_SYNC_DEFAULTS = {
|
|
27936
|
+
enabled: false,
|
|
27937
|
+
maxPerProject: 3,
|
|
27938
|
+
maxFileSize: 50000000
|
|
27939
|
+
};
|
|
27926
27940
|
});
|
|
27927
27941
|
|
|
27928
27942
|
// ../shared/src/index.ts
|
|
@@ -28004,7 +28018,7 @@ var {
|
|
|
28004
28018
|
} = import__.default;
|
|
28005
28019
|
|
|
28006
28020
|
// src/version.ts
|
|
28007
|
-
var VERSION = "0.1.
|
|
28021
|
+
var VERSION = "0.1.36";
|
|
28008
28022
|
|
|
28009
28023
|
// src/commands/init.ts
|
|
28010
28024
|
init_source();
|
|
@@ -28675,6 +28689,7 @@ function decryptFile(masterKey, filePath, ciphertext) {
|
|
|
28675
28689
|
}
|
|
28676
28690
|
|
|
28677
28691
|
// src/core/keystore.ts
|
|
28692
|
+
init_src();
|
|
28678
28693
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
28679
28694
|
import { join } from "path";
|
|
28680
28695
|
import { homedir } from "os";
|
|
@@ -28720,6 +28735,31 @@ async function loadKeys(configDir) {
|
|
|
28720
28735
|
apiUrl: config.apiUrl
|
|
28721
28736
|
};
|
|
28722
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
|
+
}
|
|
28723
28763
|
|
|
28724
28764
|
// src/core/schema.ts
|
|
28725
28765
|
init_src();
|
|
@@ -29018,6 +29058,17 @@ class CcsiniClient {
|
|
|
29018
29058
|
if (!res.ok)
|
|
29019
29059
|
throw new Error("Failed to wipe account data");
|
|
29020
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
|
+
}
|
|
29021
29072
|
async resetAll() {
|
|
29022
29073
|
const res = await fetch(`${this.apiUrl}/api/sync/reset`, {
|
|
29023
29074
|
method: "DELETE",
|
|
@@ -29068,6 +29119,42 @@ function matchGlobPattern(path2, pattern) {
|
|
|
29068
29119
|
function isExcluded(relativePath) {
|
|
29069
29120
|
return NEVER_SYNC_PATTERNS.some((pattern) => matchGlobPattern(relativePath, pattern));
|
|
29070
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
|
+
}
|
|
29071
29158
|
async function hashFile(filePath) {
|
|
29072
29159
|
const content = await readFile4(filePath);
|
|
29073
29160
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -29102,18 +29189,28 @@ async function scanDir(baseDir, currentDir, results, onProgress) {
|
|
|
29102
29189
|
}
|
|
29103
29190
|
}
|
|
29104
29191
|
}
|
|
29105
|
-
async function scanClaudeDir(claudeDir, onProgress) {
|
|
29192
|
+
async function scanClaudeDir(claudeDir, onProgress, sessionOptions) {
|
|
29106
29193
|
const progress = onProgress ?? (() => {});
|
|
29107
29194
|
const results = [];
|
|
29108
29195
|
progress("Scanning local files...");
|
|
29109
29196
|
await scanDir(claudeDir, claudeDir, results, progress);
|
|
29110
29197
|
progress(`Scan complete: ${results.length} files found`);
|
|
29111
|
-
|
|
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;
|
|
29112
29209
|
}
|
|
29113
29210
|
|
|
29114
29211
|
// src/core/manifest.ts
|
|
29115
|
-
async function generateManifest(claudeDir, deviceName, onProgress) {
|
|
29116
|
-
const files = await scanClaudeDir(claudeDir, onProgress);
|
|
29212
|
+
async function generateManifest(claudeDir, deviceName, onProgress, sessionOptions) {
|
|
29213
|
+
const files = await scanClaudeDir(claudeDir, onProgress, sessionOptions);
|
|
29117
29214
|
const entries = {};
|
|
29118
29215
|
for (const file of files) {
|
|
29119
29216
|
entries[file.relativePath] = {
|
|
@@ -29217,19 +29314,46 @@ function mergeLastWriteWins(localContent, remoteContent, localModified, remoteMo
|
|
|
29217
29314
|
|
|
29218
29315
|
// src/core/sync.ts
|
|
29219
29316
|
init_src();
|
|
29317
|
+
init_src();
|
|
29220
29318
|
function blobKey(filePath, contentHash) {
|
|
29221
29319
|
return createHash2("sha256").update(`${filePath}:${contentHash}`).digest("hex");
|
|
29222
29320
|
}
|
|
29223
29321
|
function getClaudeDir() {
|
|
29224
29322
|
return join6(homedir2(), ".claude");
|
|
29225
29323
|
}
|
|
29226
|
-
|
|
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) {
|
|
29227
29351
|
const start = Date.now();
|
|
29228
29352
|
const claudeDir = getClaudeDir();
|
|
29229
29353
|
const errors2 = [];
|
|
29230
29354
|
let bytesTransferred = 0;
|
|
29231
29355
|
const progress = onProgress ?? (() => {});
|
|
29232
|
-
const localManifest = await generateManifest(claudeDir, deviceName, progress);
|
|
29356
|
+
const localManifest = await generateManifest(claudeDir, deviceName, progress, sessionOptions);
|
|
29233
29357
|
const fileCount = Object.keys(localManifest.files).length;
|
|
29234
29358
|
progress(`Scanned ${fileCount} files`);
|
|
29235
29359
|
progress("Fetching remote manifest...");
|
|
@@ -29292,6 +29416,12 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29292
29416
|
bytesTransferred,
|
|
29293
29417
|
durationMs
|
|
29294
29418
|
});
|
|
29419
|
+
try {
|
|
29420
|
+
const projects = extractProjects(localManifest);
|
|
29421
|
+
if (projects.length > 0) {
|
|
29422
|
+
await client.putProjects(projects, deviceName);
|
|
29423
|
+
}
|
|
29424
|
+
} catch {}
|
|
29295
29425
|
return {
|
|
29296
29426
|
action: "push",
|
|
29297
29427
|
filesChanged: toPush.length,
|
|
@@ -29300,7 +29430,7 @@ async function pushSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29300
29430
|
errors: errors2
|
|
29301
29431
|
};
|
|
29302
29432
|
}
|
|
29303
|
-
async function pullSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
29433
|
+
async function pullSync(client, masterKey, deviceName, configDir, onProgress, sessionOptions) {
|
|
29304
29434
|
const start = Date.now();
|
|
29305
29435
|
const claudeDir = getClaudeDir();
|
|
29306
29436
|
const errors2 = [];
|
|
@@ -29336,7 +29466,8 @@ async function pullSync(client, masterKey, deviceName, configDir, onProgress) {
|
|
|
29336
29466
|
const localManifest = await loadManifest(configDir);
|
|
29337
29467
|
progress("Comparing manifests...");
|
|
29338
29468
|
const diffs = diffManifests(localManifest, remoteManifest);
|
|
29339
|
-
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)));
|
|
29340
29471
|
progress(`${toPull.length} files to pull`);
|
|
29341
29472
|
let downloaded = 0;
|
|
29342
29473
|
const chunks = chunkArray(toPull, MAX_CONCURRENT_TRANSFERS);
|
|
@@ -29614,6 +29745,7 @@ function detectPlatform() {
|
|
|
29614
29745
|
// src/commands/auto.ts
|
|
29615
29746
|
init_auth();
|
|
29616
29747
|
init_auth();
|
|
29748
|
+
init_src();
|
|
29617
29749
|
import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
29618
29750
|
import { join as join8 } from "path";
|
|
29619
29751
|
async function getMasterKey(configDir) {
|
|
@@ -29640,7 +29772,13 @@ function registerAutoCommands(program2) {
|
|
|
29640
29772
|
const masterKey = await getMasterKey(configDir);
|
|
29641
29773
|
const client = await getAuthenticatedClient(configDir);
|
|
29642
29774
|
const config = await loadKeys(configDir);
|
|
29643
|
-
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);
|
|
29644
29782
|
if (result.filesChanged > 0) {
|
|
29645
29783
|
console.log(`[ccsini] Pulled ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
29646
29784
|
}
|
|
@@ -29654,7 +29792,13 @@ function registerAutoCommands(program2) {
|
|
|
29654
29792
|
const masterKey = await getMasterKey(configDir);
|
|
29655
29793
|
const client = await getAuthenticatedClient(configDir);
|
|
29656
29794
|
const config = await loadKeys(configDir);
|
|
29657
|
-
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);
|
|
29658
29802
|
if (result.filesChanged > 0) {
|
|
29659
29803
|
console.log(`[ccsini] Pushed ${result.filesChanged} files (${result.durationMs}ms)`);
|
|
29660
29804
|
}
|
|
@@ -29846,6 +29990,7 @@ Uninstall failed. Try manually:`);
|
|
|
29846
29990
|
|
|
29847
29991
|
// src/commands/sync.ts
|
|
29848
29992
|
init_auth();
|
|
29993
|
+
init_src();
|
|
29849
29994
|
import { readFile as readFile8 } from "fs/promises";
|
|
29850
29995
|
import { join as join10 } from "path";
|
|
29851
29996
|
async function getMasterKey2(configDir) {
|
|
@@ -29885,7 +30030,7 @@ function formatBytes(bytes) {
|
|
|
29885
30030
|
}
|
|
29886
30031
|
function registerSyncCommands(program2) {
|
|
29887
30032
|
const syncCmd = program2.command("sync").description("Sync management commands");
|
|
29888
|
-
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) => {
|
|
29889
30034
|
const configDir = getConfigDir();
|
|
29890
30035
|
if (!await configExists(configDir)) {
|
|
29891
30036
|
console.error("Not initialized. Run 'ccsini init' first.");
|
|
@@ -29897,10 +30042,16 @@ function registerSyncCommands(program2) {
|
|
|
29897
30042
|
const masterKey = await getMasterKey2(configDir);
|
|
29898
30043
|
const client = await getAuthenticatedClient2(configDir);
|
|
29899
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
|
+
};
|
|
29900
30051
|
const spinner = ora2("Scanning files...").start();
|
|
29901
30052
|
const result = await pushSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
29902
30053
|
spinner.text = msg;
|
|
29903
|
-
});
|
|
30054
|
+
}, sessionOptions);
|
|
29904
30055
|
if (result.errors.length > 0) {
|
|
29905
30056
|
spinner.warn("Push completed with errors");
|
|
29906
30057
|
for (const err of result.errors) {
|
|
@@ -29910,12 +30061,15 @@ function registerSyncCommands(program2) {
|
|
|
29910
30061
|
spinner.succeed("Push complete");
|
|
29911
30062
|
}
|
|
29912
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
|
+
}
|
|
29913
30067
|
} catch (e) {
|
|
29914
30068
|
console.error(`Push failed: ${e.message}`);
|
|
29915
30069
|
process.exit(1);
|
|
29916
30070
|
}
|
|
29917
30071
|
});
|
|
29918
|
-
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) => {
|
|
29919
30073
|
const configDir = getConfigDir();
|
|
29920
30074
|
if (!await configExists(configDir)) {
|
|
29921
30075
|
console.error("Not initialized. Run 'ccsini init' first.");
|
|
@@ -29927,10 +30081,16 @@ function registerSyncCommands(program2) {
|
|
|
29927
30081
|
const masterKey = await getMasterKey2(configDir);
|
|
29928
30082
|
const client = await getAuthenticatedClient2(configDir);
|
|
29929
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
|
+
};
|
|
29930
30090
|
const spinner = ora2("Pulling from cloud...").start();
|
|
29931
30091
|
const result = await pullSync(client, masterKey, config.deviceName, configDir, (msg) => {
|
|
29932
30092
|
spinner.text = msg;
|
|
29933
|
-
});
|
|
30093
|
+
}, sessionOptions);
|
|
29934
30094
|
if (result.errors.length > 0) {
|
|
29935
30095
|
spinner.warn("Pull completed with errors");
|
|
29936
30096
|
for (const err of result.errors) {
|
|
@@ -29940,6 +30100,9 @@ function registerSyncCommands(program2) {
|
|
|
29940
30100
|
spinner.succeed("Pull complete");
|
|
29941
30101
|
}
|
|
29942
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
|
+
}
|
|
29943
30106
|
} catch (e) {
|
|
29944
30107
|
console.error(`Pull failed: ${e.message}`);
|
|
29945
30108
|
process.exit(1);
|
|
@@ -30157,6 +30320,149 @@ Account fully reset. Run 'ccsini init --token <token>' to start fresh.`));
|
|
|
30157
30320
|
});
|
|
30158
30321
|
}
|
|
30159
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
|
+
|
|
30412
|
+
// src/commands/menu.ts
|
|
30413
|
+
init_source();
|
|
30414
|
+
init_dist16();
|
|
30415
|
+
var LOGO = source_default.cyan.bold(`
|
|
30416
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557
|
|
30417
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551
|
|
30418
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551
|
|
30419
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551
|
|
30420
|
+
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551
|
|
30421
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D
|
|
30422
|
+
`);
|
|
30423
|
+
var TAGLINE = ` ${source_default.bold(`ccsini v${VERSION}`)} \u2014 Claude Code seamless sync
|
|
30424
|
+
${source_default.dim("Encrypted sync for your Claude Code settings across devices")}
|
|
30425
|
+
`;
|
|
30426
|
+
async function showInteractiveMenu(program2) {
|
|
30427
|
+
if (!process.stdin.isTTY) {
|
|
30428
|
+
program2.help();
|
|
30429
|
+
return;
|
|
30430
|
+
}
|
|
30431
|
+
console.log(LOGO);
|
|
30432
|
+
console.log(TAGLINE);
|
|
30433
|
+
const configDir = getConfigDir();
|
|
30434
|
+
const initialized = await configExists(configDir);
|
|
30435
|
+
const choices = initialized ? [
|
|
30436
|
+
{ name: "Sync Push Push local changes to cloud", value: ["sync", "push"] },
|
|
30437
|
+
{ name: "Sync Pull Pull changes from cloud", value: ["sync", "pull"] },
|
|
30438
|
+
{ name: "Sync Status Show sync status", value: ["sync", "status"] },
|
|
30439
|
+
{ name: "Sessions Configure session sync", value: ["sessions", "status"] },
|
|
30440
|
+
{ name: "Doctor Check system health", value: ["doctor"] },
|
|
30441
|
+
{ name: "Hooks Fix Repair broken hooks", value: ["hooks", "fix"] },
|
|
30442
|
+
{ name: "Help Show all commands", value: ["--help"] },
|
|
30443
|
+
{ name: "Exit", value: [] }
|
|
30444
|
+
] : [
|
|
30445
|
+
{ name: "Setup Connect this device", value: ["init"] },
|
|
30446
|
+
{ name: "Doctor Check system health", value: ["doctor"] },
|
|
30447
|
+
{ name: "Help Show all commands", value: ["--help"] },
|
|
30448
|
+
{ name: "Exit", value: [] }
|
|
30449
|
+
];
|
|
30450
|
+
try {
|
|
30451
|
+
const { action } = await dist_default12.prompt([
|
|
30452
|
+
{
|
|
30453
|
+
type: "list",
|
|
30454
|
+
name: "action",
|
|
30455
|
+
message: "What would you like to do?",
|
|
30456
|
+
choices
|
|
30457
|
+
}
|
|
30458
|
+
]);
|
|
30459
|
+
if (action.length === 0) {
|
|
30460
|
+
return;
|
|
30461
|
+
}
|
|
30462
|
+
await program2.parseAsync(["node", "ccsini", ...action]);
|
|
30463
|
+
} catch {}
|
|
30464
|
+
}
|
|
30465
|
+
|
|
30160
30466
|
// src/index.ts
|
|
30161
30467
|
var program2 = new Command;
|
|
30162
30468
|
program2.name("ccsini").description("Claude Code seamless sync across devices").version(VERSION);
|
|
@@ -30167,7 +30473,11 @@ registerSelfCommands(program2);
|
|
|
30167
30473
|
registerSyncCommands(program2);
|
|
30168
30474
|
registerHooksCommands(program2);
|
|
30169
30475
|
registerResetCommand(program2);
|
|
30476
|
+
registerSessionsCommand(program2);
|
|
30170
30477
|
program2.command("version").description("Show current version").action(() => {
|
|
30171
30478
|
console.log(VERSION);
|
|
30172
30479
|
});
|
|
30480
|
+
program2.action(async () => {
|
|
30481
|
+
await showInteractiveMenu(program2);
|
|
30482
|
+
});
|
|
30173
30483
|
program2.parse();
|