@claude-sync/cli 0.1.14 → 0.1.16
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/bin/claude-sync.js +273 -178
- package/dist/bin/claude-sync.js.map +1 -1
- package/dist/src/index.js +273 -178
- package/dist/src/index.js.map +1 -1
- package/package.json +6 -4
package/dist/src/index.js
CHANGED
|
@@ -4345,7 +4345,7 @@ var require_validation = __commonJS({
|
|
|
4345
4345
|
"use strict";
|
|
4346
4346
|
init_esm_shims();
|
|
4347
4347
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4348
|
-
exports.createFeatureRequestCommentSchema = exports.updateFeatureRequestStatusSchema = exports.updateFeatureRequestSchema = exports.createFeatureRequestSchema = exports.featureRequestStatusSchema = exports.featureRequestTypeSchema = exports.updateProfileSchema = exports.oauthExchangeSchema = exports.refreshTokenSchema = exports.syncCompleteSchema = exports.downloadUrlSchema = exports.uploadUrlSchema = exports.pullRequestSchema = exports.pushManifestSchema = exports.updateDeviceSchema = exports.createDeviceSchema = exports.registerSchema = exports.loginSchema = void 0;
|
|
4348
|
+
exports.createFeatureRequestCommentSchema = exports.updateFeatureRequestStatusSchema = exports.updateFeatureRequestSchema = exports.createFeatureRequestSchema = exports.featureRequestStatusSchema = exports.featureRequestTypeSchema = exports.updateProfileSchema = exports.resetPasswordSchema = exports.forgotPasswordSchema = exports.oauthExchangeSchema = exports.refreshTokenSchema = exports.syncCompleteSchema = exports.downloadUrlSchema = exports.uploadUrlSchema = exports.pullRequestSchema = exports.pushManifestSchema = exports.updateDeviceSchema = exports.createDeviceSchema = exports.registerSchema = exports.loginSchema = void 0;
|
|
4349
4349
|
var zod_1 = require_zod();
|
|
4350
4350
|
exports.loginSchema = zod_1.z.object({
|
|
4351
4351
|
email: zod_1.z.string().email(),
|
|
@@ -4411,6 +4411,13 @@ var require_validation = __commonJS({
|
|
|
4411
4411
|
provider: zod_1.z.enum(["github", "google"]),
|
|
4412
4412
|
accessToken: zod_1.z.string().min(1)
|
|
4413
4413
|
});
|
|
4414
|
+
exports.forgotPasswordSchema = zod_1.z.object({
|
|
4415
|
+
email: zod_1.z.string().email()
|
|
4416
|
+
});
|
|
4417
|
+
exports.resetPasswordSchema = zod_1.z.object({
|
|
4418
|
+
token: zod_1.z.string().min(1),
|
|
4419
|
+
password: zod_1.z.string().min(8)
|
|
4420
|
+
});
|
|
4414
4421
|
exports.updateProfileSchema = zod_1.z.object({
|
|
4415
4422
|
name: zod_1.z.string().min(1).max(100).optional(),
|
|
4416
4423
|
avatarUrl: zod_1.z.string().url().nullable().optional()
|
|
@@ -4696,16 +4703,49 @@ function formatBytes(bytes) {
|
|
|
4696
4703
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4697
4704
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
4698
4705
|
}
|
|
4699
|
-
function
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4706
|
+
function createBar(percent, width = 20) {
|
|
4707
|
+
const filled = Math.round(percent / 100 * width);
|
|
4708
|
+
const empty = width - filled;
|
|
4709
|
+
return dimColor("[") + successColor("\u2588".repeat(filled)) + dimColor("\u2591".repeat(empty) + "]");
|
|
4710
|
+
}
|
|
4711
|
+
function formatSpeed(bytesPerSecond) {
|
|
4712
|
+
return `${formatBytes(bytesPerSecond)}/s`;
|
|
4713
|
+
}
|
|
4714
|
+
function createTransferProgress(initialLabel, totalBytes, spinner) {
|
|
4715
|
+
const startTime = Date.now();
|
|
4716
|
+
let lastUpdate = 0;
|
|
4717
|
+
let currentLabel = initialLabel;
|
|
4718
|
+
return {
|
|
4719
|
+
update(bytesTransferred) {
|
|
4720
|
+
const now = Date.now();
|
|
4721
|
+
if (now - lastUpdate < 50) return;
|
|
4722
|
+
lastUpdate = now;
|
|
4723
|
+
const percent = Math.round(bytesTransferred / totalBytes * 100);
|
|
4724
|
+
const elapsed = (now - startTime) / 1e3;
|
|
4725
|
+
const speed = elapsed > 0 ? bytesTransferred / elapsed : 0;
|
|
4726
|
+
const bar = createBar(percent);
|
|
4727
|
+
spinner.text = `${currentLabel} ${bar} ${dimColor(`${percent}%`)} ${dimColor("\u2022")} ${formatBytes(bytesTransferred)}/${formatBytes(totalBytes)} ${dimColor("\u2022")} ${formatSpeed(speed)}`;
|
|
4728
|
+
},
|
|
4729
|
+
setLabel(label) {
|
|
4730
|
+
currentLabel = label;
|
|
4731
|
+
},
|
|
4732
|
+
finish(message) {
|
|
4733
|
+
if (message) {
|
|
4734
|
+
spinner.succeed(message);
|
|
4735
|
+
} else {
|
|
4736
|
+
spinner.stop();
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
};
|
|
4702
4740
|
}
|
|
4703
|
-
var brandColor;
|
|
4741
|
+
var brandColor, dimColor, successColor;
|
|
4704
4742
|
var init_progress = __esm({
|
|
4705
4743
|
"src/lib/progress.ts"() {
|
|
4706
4744
|
"use strict";
|
|
4707
4745
|
init_esm_shims();
|
|
4708
4746
|
brandColor = chalk.hex("#8a8cdd");
|
|
4747
|
+
dimColor = chalk.dim;
|
|
4748
|
+
successColor = chalk.hex("#22c55e");
|
|
4709
4749
|
}
|
|
4710
4750
|
});
|
|
4711
4751
|
|
|
@@ -5126,32 +5166,105 @@ var init_sync_engine = __esm({
|
|
|
5126
5166
|
}
|
|
5127
5167
|
});
|
|
5128
5168
|
|
|
5129
|
-
// src/lib/
|
|
5169
|
+
// src/lib/bundle.ts
|
|
5130
5170
|
import { createGzip, createGunzip } from "zlib";
|
|
5131
5171
|
import { createReadStream, createWriteStream } from "fs";
|
|
5172
|
+
import { readFile as readFile2, stat as stat2, mkdir as mkdir2 } from "fs/promises";
|
|
5173
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
5132
5174
|
import { pipeline } from "stream/promises";
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5175
|
+
import { pack, extract } from "tar-stream";
|
|
5176
|
+
async function createBundle(sourceDir, files, outputPath, onProgress) {
|
|
5177
|
+
const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
|
|
5178
|
+
let processedBytes = 0;
|
|
5179
|
+
const archive = pack();
|
|
5180
|
+
const gzip = createGzip({ level: 6 });
|
|
5181
|
+
const output = createWriteStream(outputPath);
|
|
5182
|
+
const pipelinePromise = pipeline(archive, gzip, output);
|
|
5183
|
+
for (const file of files) {
|
|
5184
|
+
const filePath = join3(sourceDir, file.path);
|
|
5185
|
+
const content = await readFile2(filePath);
|
|
5186
|
+
archive.entry({ name: file.path, size: content.length }, content);
|
|
5187
|
+
processedBytes += file.size;
|
|
5188
|
+
onProgress?.(processedBytes, totalBytes, "compressing");
|
|
5189
|
+
}
|
|
5190
|
+
archive.finalize();
|
|
5191
|
+
await pipelinePromise;
|
|
5192
|
+
const outputStat = await stat2(outputPath);
|
|
5193
|
+
return {
|
|
5194
|
+
size: outputStat.size,
|
|
5195
|
+
fileCount: files.length,
|
|
5196
|
+
originalSize: totalBytes
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
async function extractBundle(bundlePath, targetDir, onProgress) {
|
|
5200
|
+
await mkdir2(targetDir, { recursive: true });
|
|
5201
|
+
const extractor = extract();
|
|
5202
|
+
const gunzip = createGunzip();
|
|
5203
|
+
const input = createReadStream(bundlePath);
|
|
5204
|
+
let count = 0;
|
|
5205
|
+
extractor.on("entry", (header, stream, next) => {
|
|
5206
|
+
count++;
|
|
5207
|
+
onProgress?.(count, header.name);
|
|
5208
|
+
const outputPath = join3(targetDir, header.name);
|
|
5209
|
+
const dir = dirname2(outputPath);
|
|
5210
|
+
mkdir2(dir, { recursive: true }).then(() => {
|
|
5211
|
+
const fileOutput = createWriteStream(outputPath);
|
|
5212
|
+
stream.pipe(fileOutput);
|
|
5213
|
+
fileOutput.on("finish", next);
|
|
5214
|
+
fileOutput.on("error", next);
|
|
5215
|
+
}).catch(next);
|
|
5141
5216
|
});
|
|
5217
|
+
await pipeline(input, gunzip, extractor);
|
|
5218
|
+
return count;
|
|
5142
5219
|
}
|
|
5143
|
-
function
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5220
|
+
async function streamUpload(url, filePath, onProgress) {
|
|
5221
|
+
const fileStat = await stat2(filePath);
|
|
5222
|
+
const totalSize = fileStat.size;
|
|
5223
|
+
const fileStream = createReadStream(filePath);
|
|
5224
|
+
let uploaded = 0;
|
|
5225
|
+
const chunks = [];
|
|
5226
|
+
for await (const chunk of fileStream) {
|
|
5227
|
+
chunks.push(chunk);
|
|
5228
|
+
uploaded += chunk.length;
|
|
5229
|
+
onProgress?.(uploaded, totalSize);
|
|
5230
|
+
}
|
|
5231
|
+
const body = Buffer.concat(chunks);
|
|
5232
|
+
const response = await fetch(url, {
|
|
5233
|
+
method: "PUT",
|
|
5234
|
+
body,
|
|
5235
|
+
headers: {
|
|
5236
|
+
"Content-Type": "application/gzip",
|
|
5237
|
+
"Content-Length": String(totalSize)
|
|
5238
|
+
}
|
|
5151
5239
|
});
|
|
5240
|
+
if (!response.ok) {
|
|
5241
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
5242
|
+
}
|
|
5152
5243
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5244
|
+
async function streamDownload(url, outputPath, expectedSize, onProgress) {
|
|
5245
|
+
const response = await fetch(url);
|
|
5246
|
+
if (!response.ok) {
|
|
5247
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
5248
|
+
}
|
|
5249
|
+
const reader = response.body?.getReader();
|
|
5250
|
+
if (!reader) throw new Error("No response body");
|
|
5251
|
+
const output = createWriteStream(outputPath);
|
|
5252
|
+
let downloaded = 0;
|
|
5253
|
+
while (true) {
|
|
5254
|
+
const { done, value } = await reader.read();
|
|
5255
|
+
if (done) break;
|
|
5256
|
+
output.write(value);
|
|
5257
|
+
downloaded += value.length;
|
|
5258
|
+
onProgress?.(downloaded, expectedSize);
|
|
5259
|
+
}
|
|
5260
|
+
await new Promise((resolve, reject) => {
|
|
5261
|
+
output.end();
|
|
5262
|
+
output.on("finish", resolve);
|
|
5263
|
+
output.on("error", reject);
|
|
5264
|
+
});
|
|
5265
|
+
}
|
|
5266
|
+
var init_bundle = __esm({
|
|
5267
|
+
"src/lib/bundle.ts"() {
|
|
5155
5268
|
"use strict";
|
|
5156
5269
|
init_esm_shims();
|
|
5157
5270
|
}
|
|
@@ -5165,9 +5278,9 @@ __export(sync_exports, {
|
|
|
5165
5278
|
});
|
|
5166
5279
|
import { Command as Command4 } from "commander";
|
|
5167
5280
|
import { select as select2, isCancel as isCancel3 } from "@clack/prompts";
|
|
5168
|
-
import {
|
|
5169
|
-
import { join as
|
|
5170
|
-
import { homedir as homedir4 } from "os";
|
|
5281
|
+
import { mkdir as mkdir3, lstat as lstat2, symlink, unlink, rename, rm } from "fs/promises";
|
|
5282
|
+
import { join as join4 } from "path";
|
|
5283
|
+
import { homedir as homedir4, tmpdir } from "os";
|
|
5171
5284
|
async function runSync(options) {
|
|
5172
5285
|
printIntro("Sync");
|
|
5173
5286
|
const config = await loadConfig();
|
|
@@ -5182,11 +5295,11 @@ async function runSync(options) {
|
|
|
5182
5295
|
const cwd = process.cwd();
|
|
5183
5296
|
const ctx = await resolveProjectState(cwd);
|
|
5184
5297
|
const client = new ApiClient(config.apiUrl);
|
|
5185
|
-
const projectsDir =
|
|
5298
|
+
const projectsDir = join4(homedir4(), import_utils3.CLAUDE_DIR, import_utils3.PROJECTS_DIR);
|
|
5186
5299
|
const projectLink = await loadProjectLink(cwd);
|
|
5187
5300
|
let projectDir = ctx.projectDir;
|
|
5188
5301
|
if (ctx.state === "symlink" && ctx.symlinkTarget) {
|
|
5189
|
-
projectDir =
|
|
5302
|
+
projectDir = join4(projectsDir, ctx.symlinkTarget);
|
|
5190
5303
|
}
|
|
5191
5304
|
printInfo(`Project: ${brand(cwd)}`);
|
|
5192
5305
|
if (ctx.state === "symlink") {
|
|
@@ -5202,7 +5315,6 @@ async function runSync(options) {
|
|
|
5202
5315
|
if (result === "pulled") {
|
|
5203
5316
|
const finalManifest2 = await buildProjectManifest(cwd);
|
|
5204
5317
|
await saveProjectManifest(cwd, finalManifest2);
|
|
5205
|
-
printSuccess("Sessions synced from remote device.");
|
|
5206
5318
|
printOutro("Done");
|
|
5207
5319
|
return;
|
|
5208
5320
|
}
|
|
@@ -5215,13 +5327,13 @@ async function runSync(options) {
|
|
|
5215
5327
|
printOutro("Done");
|
|
5216
5328
|
return;
|
|
5217
5329
|
}
|
|
5218
|
-
let pushResult = {
|
|
5219
|
-
let pullResult = {
|
|
5330
|
+
let pushResult = { fileCount: 0, projectId: projectLink.projectId, failed: false };
|
|
5331
|
+
let pullResult = { fileCount: 0, failed: false };
|
|
5220
5332
|
if (manifest.length > 0) {
|
|
5221
|
-
pushResult = await
|
|
5333
|
+
pushResult = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, projectLink.projectId, options);
|
|
5222
5334
|
}
|
|
5223
5335
|
if (!pushResult.failed) {
|
|
5224
|
-
pullResult = await
|
|
5336
|
+
pullResult = await bundlePullPhase(client, config.deviceId, cwd, projectDir, projectLink.projectId, options);
|
|
5225
5337
|
}
|
|
5226
5338
|
if (pushResult.failed && pullResult.failed) {
|
|
5227
5339
|
printOutro("Sync failed.");
|
|
@@ -5229,11 +5341,11 @@ async function runSync(options) {
|
|
|
5229
5341
|
}
|
|
5230
5342
|
const finalManifest = await buildProjectManifest(cwd);
|
|
5231
5343
|
await saveProjectManifest(cwd, finalManifest);
|
|
5232
|
-
if (pushResult.
|
|
5344
|
+
if (pushResult.fileCount > 0 || pullResult.fileCount > 0) {
|
|
5233
5345
|
const parts = [];
|
|
5234
|
-
if (pushResult.
|
|
5235
|
-
if (pullResult.
|
|
5236
|
-
printSuccess(`Synced: ${parts.join(", ")}`);
|
|
5346
|
+
if (pushResult.fileCount > 0) parts.push(`${success(`${pushResult.fileCount}`)} pushed`);
|
|
5347
|
+
if (pullResult.fileCount > 0) parts.push(`${success(`${pullResult.fileCount}`)} pulled`);
|
|
5348
|
+
printSuccess(`Synced: ${parts.join(", ")} files`);
|
|
5237
5349
|
} else if (!pushResult.failed && !pullResult.failed) {
|
|
5238
5350
|
printSuccess("Everything is up to date.");
|
|
5239
5351
|
}
|
|
@@ -5281,15 +5393,12 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5281
5393
|
});
|
|
5282
5394
|
if (isCancel3(choice)) return "cancelled";
|
|
5283
5395
|
if (choice === "push") {
|
|
5284
|
-
const
|
|
5285
|
-
if (
|
|
5396
|
+
const result2 = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, void 0, options);
|
|
5397
|
+
if (result2.failed) {
|
|
5286
5398
|
return "cancelled";
|
|
5287
5399
|
}
|
|
5288
|
-
if (result.uploaded > 0) {
|
|
5289
|
-
printSuccess(`${success(`+${result.uploaded}`)} files pushed`);
|
|
5290
|
-
}
|
|
5291
5400
|
await saveProjectLink(cwd, {
|
|
5292
|
-
projectId:
|
|
5401
|
+
projectId: result2.projectId,
|
|
5293
5402
|
foreignEncodedDir: ctx.encodedPath,
|
|
5294
5403
|
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5295
5404
|
});
|
|
@@ -5333,7 +5442,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5333
5442
|
if (existingCheck === "cancelled") {
|
|
5334
5443
|
return "cancelled";
|
|
5335
5444
|
}
|
|
5336
|
-
const
|
|
5445
|
+
const result = await crossMachineBundlePull(
|
|
5337
5446
|
client,
|
|
5338
5447
|
config.deviceId,
|
|
5339
5448
|
cwd,
|
|
@@ -5343,7 +5452,7 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5343
5452
|
projectsDir,
|
|
5344
5453
|
options
|
|
5345
5454
|
);
|
|
5346
|
-
if (
|
|
5455
|
+
if (result.fileCount === 0 && !options.dryRun) {
|
|
5347
5456
|
printInfo("No files to download.");
|
|
5348
5457
|
return "cancelled";
|
|
5349
5458
|
}
|
|
@@ -5354,13 +5463,12 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5354
5463
|
foreignEncodedDir: selectedProject.encodedDir,
|
|
5355
5464
|
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5356
5465
|
});
|
|
5357
|
-
printSuccess(`${success(`+${downloaded}`)} files pulled`);
|
|
5358
5466
|
printInfo(`Symlink: ${dim(ctx.encodedPath)} \u2192 ${dim(selectedProject.encodedDir)}`);
|
|
5359
5467
|
}
|
|
5360
5468
|
return "pulled";
|
|
5361
5469
|
}
|
|
5362
5470
|
async function handleExistingSessionDir(projectsDir, localEncoded) {
|
|
5363
|
-
const symlinkPath =
|
|
5471
|
+
const symlinkPath = join4(projectsDir, localEncoded);
|
|
5364
5472
|
try {
|
|
5365
5473
|
const stats = await lstat2(symlinkPath);
|
|
5366
5474
|
if (stats.isSymbolicLink()) {
|
|
@@ -5397,8 +5505,8 @@ async function handleExistingSessionDir(projectsDir, localEncoded) {
|
|
|
5397
5505
|
return "continue";
|
|
5398
5506
|
}
|
|
5399
5507
|
async function createProjectSymlink(projectsDir, localEncoded, foreignEncoded) {
|
|
5400
|
-
const symlinkPath =
|
|
5401
|
-
await
|
|
5508
|
+
const symlinkPath = join4(projectsDir, localEncoded);
|
|
5509
|
+
await mkdir3(projectsDir, { recursive: true });
|
|
5402
5510
|
try {
|
|
5403
5511
|
const stats = await lstat2(symlinkPath);
|
|
5404
5512
|
if (stats.isSymbolicLink()) {
|
|
@@ -5408,190 +5516,177 @@ async function createProjectSymlink(projectsDir, localEncoded, foreignEncoded) {
|
|
|
5408
5516
|
}
|
|
5409
5517
|
await symlink(foreignEncoded, symlinkPath);
|
|
5410
5518
|
}
|
|
5411
|
-
async function
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
try {
|
|
5415
|
-
prepareResponse = await client.post("/api/sync/pull/prepare", {
|
|
5416
|
-
deviceId,
|
|
5417
|
-
projectPath,
|
|
5418
|
-
fromDeviceId,
|
|
5419
|
-
projectId
|
|
5420
|
-
});
|
|
5421
|
-
} catch (err) {
|
|
5422
|
-
spinner.stop();
|
|
5423
|
-
if (err instanceof AuthExpiredError) throw err;
|
|
5424
|
-
printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5425
|
-
return 0;
|
|
5519
|
+
async function bundlePushPhase(client, deviceId, projectPath, manifest, projectDir, projectId, options) {
|
|
5520
|
+
if (manifest.length === 0) {
|
|
5521
|
+
return { fileCount: 0, projectId: projectId || "", failed: false };
|
|
5426
5522
|
}
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
if (filesToDownload.length === 0) return 0;
|
|
5430
|
-
printInfo(`${brand(String(filesToDownload.length))} files to pull`);
|
|
5523
|
+
const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
|
|
5524
|
+
printInfo(`${brand(String(manifest.length))} files ${dim(`(${formatBytes(totalBytes)})`)} to push`);
|
|
5431
5525
|
if (options.dryRun) {
|
|
5432
5526
|
if (options.verbose) {
|
|
5433
|
-
for (const entry of
|
|
5434
|
-
console.log(` ${
|
|
5527
|
+
for (const entry of manifest) {
|
|
5528
|
+
console.log(` ${success("+")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
|
|
5435
5529
|
}
|
|
5436
5530
|
}
|
|
5437
|
-
return
|
|
5531
|
+
return { fileCount: manifest.length, projectId: projectId || "", failed: false };
|
|
5438
5532
|
}
|
|
5439
|
-
const
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
const
|
|
5443
|
-
|
|
5444
|
-
|
|
5533
|
+
const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5534
|
+
try {
|
|
5535
|
+
const spinner = createSpinner("Syncing...").start();
|
|
5536
|
+
const bundleResult = await createBundle(projectDir, manifest, tempBundle, (bytes, total, phase) => {
|
|
5537
|
+
if (phase === "compressing") {
|
|
5538
|
+
const percent = Math.round(bytes / total * 100);
|
|
5539
|
+
spinner.text = `Compressing files... ${dim(`${percent}%`)}`;
|
|
5540
|
+
}
|
|
5541
|
+
});
|
|
5542
|
+
const progress = createTransferProgress("Uploading", bundleResult.size, spinner);
|
|
5543
|
+
let prepareResponse;
|
|
5544
|
+
try {
|
|
5545
|
+
prepareResponse = await client.post("/api/sync/push/bundle/prepare", {
|
|
5546
|
+
deviceId,
|
|
5547
|
+
projectPath,
|
|
5548
|
+
projectId,
|
|
5549
|
+
manifest,
|
|
5550
|
+
bundleSize: bundleResult.size
|
|
5551
|
+
});
|
|
5552
|
+
} catch (err) {
|
|
5553
|
+
spinner.stop();
|
|
5554
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5555
|
+
printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5556
|
+
return { fileCount: 0, projectId: projectId || "", failed: true };
|
|
5557
|
+
}
|
|
5558
|
+
await streamUpload(prepareResponse.uploadUrl, tempBundle, (bytes, total) => {
|
|
5559
|
+
progress.update(bytes);
|
|
5560
|
+
});
|
|
5445
5561
|
try {
|
|
5446
|
-
|
|
5562
|
+
await client.post("/api/sync/push/bundle/complete", {
|
|
5447
5563
|
syncEventId: prepareResponse.syncEventId,
|
|
5448
|
-
|
|
5564
|
+
manifest
|
|
5449
5565
|
});
|
|
5450
|
-
const response = await fetch(urlResponse.url);
|
|
5451
|
-
let data = Buffer.from(await response.arrayBuffer());
|
|
5452
|
-
if (entry.isCompressed) {
|
|
5453
|
-
data = await decompressBuffer(data);
|
|
5454
|
-
}
|
|
5455
|
-
const outputPath = join3(targetDir, entry.path);
|
|
5456
|
-
await mkdir2(dirname2(outputPath), { recursive: true });
|
|
5457
|
-
await writeFile2(outputPath, data);
|
|
5458
|
-
downloaded++;
|
|
5459
5566
|
} catch {
|
|
5460
5567
|
}
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
}
|
|
5469
|
-
} catch {
|
|
5568
|
+
const compressionRatio = Math.round((1 - bundleResult.size / bundleResult.originalSize) * 100);
|
|
5569
|
+
progress.finish(`Pushed ${manifest.length} files ${dim(`(${formatBytes(bundleResult.size)}, ${compressionRatio}% smaller)`)}`);
|
|
5570
|
+
return { fileCount: manifest.length, projectId: prepareResponse.projectId, failed: false };
|
|
5571
|
+
} finally {
|
|
5572
|
+
try {
|
|
5573
|
+
await rm(tempBundle, { force: true });
|
|
5574
|
+
} catch {
|
|
5575
|
+
}
|
|
5470
5576
|
}
|
|
5471
|
-
return downloaded;
|
|
5472
5577
|
}
|
|
5473
|
-
async function
|
|
5474
|
-
const spinner = createSpinner("
|
|
5578
|
+
async function bundlePullPhase(client, deviceId, projectPath, projectDir, projectId, options) {
|
|
5579
|
+
const spinner = createSpinner("Checking for updates...").start();
|
|
5475
5580
|
let prepareResponse;
|
|
5476
5581
|
try {
|
|
5477
|
-
prepareResponse = await client.post("/api/sync/
|
|
5582
|
+
prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
|
|
5478
5583
|
deviceId,
|
|
5479
5584
|
projectPath,
|
|
5480
|
-
projectId
|
|
5481
|
-
manifest
|
|
5585
|
+
projectId
|
|
5482
5586
|
});
|
|
5483
5587
|
} catch (err) {
|
|
5484
5588
|
spinner.stop();
|
|
5485
5589
|
if (err instanceof AuthExpiredError) throw err;
|
|
5486
|
-
printError(`
|
|
5487
|
-
return {
|
|
5590
|
+
printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5591
|
+
return { fileCount: 0, failed: true };
|
|
5488
5592
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5593
|
+
if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
|
|
5594
|
+
spinner.stop();
|
|
5595
|
+
return { fileCount: 0, failed: false };
|
|
5596
|
+
}
|
|
5597
|
+
const fileCount = prepareResponse.manifest.length;
|
|
5494
5598
|
if (options.dryRun) {
|
|
5599
|
+
spinner.stop();
|
|
5600
|
+
printInfo(`${brand(String(fileCount))} files to pull`);
|
|
5495
5601
|
if (options.verbose) {
|
|
5496
|
-
for (const entry of
|
|
5497
|
-
console.log(` ${
|
|
5602
|
+
for (const entry of prepareResponse.manifest) {
|
|
5603
|
+
console.log(` ${brand("\u25CB")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
|
|
5498
5604
|
}
|
|
5499
5605
|
}
|
|
5500
|
-
return {
|
|
5606
|
+
return { fileCount, failed: false };
|
|
5501
5607
|
}
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
const
|
|
5505
|
-
|
|
5506
|
-
|
|
5608
|
+
const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5609
|
+
try {
|
|
5610
|
+
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5611
|
+
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
5612
|
+
progress.update(bytes);
|
|
5613
|
+
});
|
|
5614
|
+
spinner.text = "Extracting files...";
|
|
5615
|
+
const extractedCount = await extractBundle(tempBundle, projectDir);
|
|
5507
5616
|
try {
|
|
5508
|
-
const
|
|
5617
|
+
const completeManifest = await walkDirectory(projectDir);
|
|
5618
|
+
await client.post("/api/sync/pull/bundle/complete", {
|
|
5509
5619
|
syncEventId: prepareResponse.syncEventId,
|
|
5510
|
-
|
|
5620
|
+
manifest: completeManifest
|
|
5511
5621
|
});
|
|
5512
|
-
const filePath = join3(projectDir, entry.path);
|
|
5513
|
-
let body = await readFile2(filePath);
|
|
5514
|
-
if (entry.isCompressed) {
|
|
5515
|
-
body = await compressBuffer(body);
|
|
5516
|
-
}
|
|
5517
|
-
await fetch(urlResponse.url, {
|
|
5518
|
-
method: "PUT",
|
|
5519
|
-
body,
|
|
5520
|
-
headers: { "Content-Type": "application/octet-stream" }
|
|
5521
|
-
});
|
|
5522
|
-
uploaded++;
|
|
5523
5622
|
} catch {
|
|
5524
5623
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
}
|
|
5532
|
-
} catch {
|
|
5624
|
+
progress.finish(`Pulled ${extractedCount} files ${dim(`(${formatBytes(prepareResponse.bundleSize)})`)}`);
|
|
5625
|
+
return { fileCount: extractedCount, failed: false };
|
|
5626
|
+
} finally {
|
|
5627
|
+
try {
|
|
5628
|
+
await rm(tempBundle, { force: true });
|
|
5629
|
+
} catch {
|
|
5630
|
+
}
|
|
5533
5631
|
}
|
|
5534
|
-
return { uploaded, projectId: resolvedProjectId, failed: false };
|
|
5535
5632
|
}
|
|
5536
|
-
async function
|
|
5537
|
-
const spinner = createSpinner("Preparing
|
|
5633
|
+
async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceId, projectId, foreignEncodedDir, projectsDir, options) {
|
|
5634
|
+
const spinner = createSpinner("Preparing download...").start();
|
|
5538
5635
|
let prepareResponse;
|
|
5539
5636
|
try {
|
|
5540
|
-
prepareResponse = await client.post("/api/sync/pull/prepare", {
|
|
5637
|
+
prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
|
|
5541
5638
|
deviceId,
|
|
5542
5639
|
projectPath,
|
|
5640
|
+
fromDeviceId,
|
|
5543
5641
|
projectId
|
|
5544
5642
|
});
|
|
5545
5643
|
} catch (err) {
|
|
5546
5644
|
spinner.stop();
|
|
5547
5645
|
if (err instanceof AuthExpiredError) throw err;
|
|
5548
5646
|
printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5549
|
-
return {
|
|
5647
|
+
return { fileCount: 0, failed: true };
|
|
5550
5648
|
}
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5649
|
+
if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
|
|
5650
|
+
spinner.stop();
|
|
5651
|
+
return { fileCount: 0, failed: false };
|
|
5652
|
+
}
|
|
5653
|
+
const fileCount = prepareResponse.manifest.length;
|
|
5555
5654
|
if (options.dryRun) {
|
|
5655
|
+
spinner.stop();
|
|
5656
|
+
printInfo(`${brand(String(fileCount))} files to pull`);
|
|
5556
5657
|
if (options.verbose) {
|
|
5557
|
-
for (const entry of
|
|
5658
|
+
for (const entry of prepareResponse.manifest) {
|
|
5558
5659
|
console.log(` ${brand("\u25CB")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
|
|
5559
5660
|
}
|
|
5560
5661
|
}
|
|
5561
|
-
return {
|
|
5662
|
+
return { fileCount, failed: false };
|
|
5562
5663
|
}
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5664
|
+
const targetDir = join4(projectsDir, foreignEncodedDir);
|
|
5665
|
+
const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5666
|
+
try {
|
|
5667
|
+
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5668
|
+
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
5669
|
+
progress.update(bytes);
|
|
5670
|
+
});
|
|
5671
|
+
spinner.text = "Extracting files...";
|
|
5672
|
+
await mkdir3(targetDir, { recursive: true });
|
|
5673
|
+
const extractedCount = await extractBundle(tempBundle, targetDir);
|
|
5568
5674
|
try {
|
|
5569
|
-
const
|
|
5675
|
+
const completeManifest = await walkDirectory(targetDir);
|
|
5676
|
+
await client.post("/api/sync/pull/bundle/complete", {
|
|
5570
5677
|
syncEventId: prepareResponse.syncEventId,
|
|
5571
|
-
|
|
5678
|
+
manifest: completeManifest
|
|
5572
5679
|
});
|
|
5573
|
-
const response = await fetch(urlResponse.url);
|
|
5574
|
-
let data = Buffer.from(await response.arrayBuffer());
|
|
5575
|
-
if (entry.isCompressed) {
|
|
5576
|
-
data = await decompressBuffer(data);
|
|
5577
|
-
}
|
|
5578
|
-
const outputPath = join3(projectDir, entry.path);
|
|
5579
|
-
await mkdir2(dirname2(outputPath), { recursive: true });
|
|
5580
|
-
await writeFile2(outputPath, data);
|
|
5581
|
-
downloaded++;
|
|
5582
5680
|
} catch {
|
|
5583
5681
|
}
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
});
|
|
5592
|
-
} catch {
|
|
5682
|
+
progress.finish(`Pulled ${extractedCount} files ${dim(`(${formatBytes(prepareResponse.bundleSize)})`)}`);
|
|
5683
|
+
return { fileCount: extractedCount, failed: false };
|
|
5684
|
+
} finally {
|
|
5685
|
+
try {
|
|
5686
|
+
await rm(tempBundle, { force: true });
|
|
5687
|
+
} catch {
|
|
5688
|
+
}
|
|
5593
5689
|
}
|
|
5594
|
-
return { downloaded, failed: false };
|
|
5595
5690
|
}
|
|
5596
5691
|
function syncCommand() {
|
|
5597
5692
|
return new Command4("sync").description("Sync current project sessions with cloud").option("--dry-run", "Show what would be synced without syncing").option("--verbose", "Show detailed output").action(async (options) => {
|
|
@@ -5607,7 +5702,7 @@ var init_sync = __esm({
|
|
|
5607
5702
|
init_api_client();
|
|
5608
5703
|
init_sync_engine();
|
|
5609
5704
|
init_progress();
|
|
5610
|
-
|
|
5705
|
+
init_bundle();
|
|
5611
5706
|
import_utils3 = __toESM(require_dist(), 1);
|
|
5612
5707
|
init_theme();
|
|
5613
5708
|
}
|