@claude-sync/cli 0.1.16 → 0.1.17
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 +155 -1
- package/dist/bin/claude-sync.js.map +1 -1
- package/dist/src/index.js +155 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
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.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;
|
|
4348
|
+
exports.updateCollaboratorRoleSchema = exports.inviteCollaboratorSchema = exports.editableRoleSchema = exports.projectRoleSchema = 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(),
|
|
@@ -4439,6 +4439,15 @@ var require_validation = __commonJS({
|
|
|
4439
4439
|
exports.createFeatureRequestCommentSchema = zod_1.z.object({
|
|
4440
4440
|
body: zod_1.z.string().min(1).max(2e3)
|
|
4441
4441
|
});
|
|
4442
|
+
exports.projectRoleSchema = zod_1.z.enum(["owner", "editor", "viewer"]);
|
|
4443
|
+
exports.editableRoleSchema = zod_1.z.enum(["editor", "viewer"]);
|
|
4444
|
+
exports.inviteCollaboratorSchema = zod_1.z.object({
|
|
4445
|
+
email: zod_1.z.string().email(),
|
|
4446
|
+
role: exports.editableRoleSchema
|
|
4447
|
+
});
|
|
4448
|
+
exports.updateCollaboratorRoleSchema = zod_1.z.object({
|
|
4449
|
+
role: exports.editableRoleSchema
|
|
4450
|
+
});
|
|
4442
4451
|
}
|
|
4443
4452
|
});
|
|
4444
4453
|
|
|
@@ -5364,6 +5373,11 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5364
5373
|
}
|
|
5365
5374
|
devicesSpinner.stop();
|
|
5366
5375
|
const otherDevices = devices.filter((d) => d.id !== config.deviceId);
|
|
5376
|
+
let sharedProjects = [];
|
|
5377
|
+
try {
|
|
5378
|
+
sharedProjects = await client.get("/api/projects/shared");
|
|
5379
|
+
} catch {
|
|
5380
|
+
}
|
|
5367
5381
|
const menuOptions = [];
|
|
5368
5382
|
if (hasLocalFiles) {
|
|
5369
5383
|
menuOptions.push({
|
|
@@ -5379,6 +5393,13 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5379
5393
|
hint: dim("Pull sessions from a device")
|
|
5380
5394
|
});
|
|
5381
5395
|
}
|
|
5396
|
+
if (sharedProjects.length > 0) {
|
|
5397
|
+
menuOptions.push({
|
|
5398
|
+
value: "collaborator",
|
|
5399
|
+
label: "Sync from collaborator",
|
|
5400
|
+
hint: dim(`${sharedProjects.length} shared project(s) available`)
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5382
5403
|
if (menuOptions.length === 0) {
|
|
5383
5404
|
printInfo("No local sessions and no other devices found.");
|
|
5384
5405
|
printInfo("Use Claude Code in this directory first, then run sync.");
|
|
@@ -5404,6 +5425,65 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
|
|
|
5404
5425
|
});
|
|
5405
5426
|
return "pushed";
|
|
5406
5427
|
}
|
|
5428
|
+
if (choice === "collaborator") {
|
|
5429
|
+
const sharedChoice = await select2({
|
|
5430
|
+
message: brand("Select shared project"),
|
|
5431
|
+
options: sharedProjects.map((p) => ({
|
|
5432
|
+
value: p.id,
|
|
5433
|
+
label: p.displayName || p.originalPath,
|
|
5434
|
+
hint: dim(`${p.owner.name} \xB7 ${p.role}`)
|
|
5435
|
+
}))
|
|
5436
|
+
});
|
|
5437
|
+
if (isCancel3(sharedChoice)) return "cancelled";
|
|
5438
|
+
const selectedShared = sharedProjects.find((p) => p.id === sharedChoice);
|
|
5439
|
+
const syncStatusSpinner = createSpinner("Checking sync status...").start();
|
|
5440
|
+
let syncStatus = null;
|
|
5441
|
+
try {
|
|
5442
|
+
syncStatus = await client.get(`/api/projects/${selectedShared.id}/sync-status`);
|
|
5443
|
+
} catch {
|
|
5444
|
+
}
|
|
5445
|
+
syncStatusSpinner.stop();
|
|
5446
|
+
if (syncStatus?.hasRecentSync && syncStatus.lastSyncedBy) {
|
|
5447
|
+
printInfo(brand("Recent sync detected"));
|
|
5448
|
+
printInfo(dim(`${syncStatus.lastSyncedBy.name} synced from ${syncStatus.lastSyncedBy.deviceName} recently`));
|
|
5449
|
+
const confirmChoice = await select2({
|
|
5450
|
+
message: brand("Continue anyway?"),
|
|
5451
|
+
options: [
|
|
5452
|
+
{ value: "yes", label: "Yes, continue", hint: dim("Pull the latest version") },
|
|
5453
|
+
{ value: "no", label: "Cancel", hint: dim("Wait for them to finish") }
|
|
5454
|
+
]
|
|
5455
|
+
});
|
|
5456
|
+
if (isCancel3(confirmChoice) || confirmChoice === "no") return "cancelled";
|
|
5457
|
+
}
|
|
5458
|
+
const existingCheck2 = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
|
|
5459
|
+
if (existingCheck2 === "cancelled") {
|
|
5460
|
+
return "cancelled";
|
|
5461
|
+
}
|
|
5462
|
+
const result2 = await collaboratorBundlePull(
|
|
5463
|
+
client,
|
|
5464
|
+
config.deviceId,
|
|
5465
|
+
cwd,
|
|
5466
|
+
selectedShared.id,
|
|
5467
|
+
selectedShared.canonicalPath,
|
|
5468
|
+
projectsDir,
|
|
5469
|
+
options
|
|
5470
|
+
);
|
|
5471
|
+
if (result2.fileCount === 0 && !options.dryRun) {
|
|
5472
|
+
printInfo("No files to download.");
|
|
5473
|
+
return "cancelled";
|
|
5474
|
+
}
|
|
5475
|
+
if (!options.dryRun) {
|
|
5476
|
+
const foreignEncoded = selectedShared.canonicalPath.replace(/\//g, "%2F");
|
|
5477
|
+
await createProjectSymlink(projectsDir, ctx.encodedPath, foreignEncoded);
|
|
5478
|
+
await saveProjectLink(cwd, {
|
|
5479
|
+
projectId: selectedShared.id,
|
|
5480
|
+
foreignEncodedDir: foreignEncoded,
|
|
5481
|
+
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5482
|
+
});
|
|
5483
|
+
printInfo(`Synced from: ${dim(`${selectedShared.owner.name}'s ${selectedShared.displayName}`)}`);
|
|
5484
|
+
}
|
|
5485
|
+
return "pulled";
|
|
5486
|
+
}
|
|
5407
5487
|
const deviceChoice = await select2({
|
|
5408
5488
|
message: brand("Select device"),
|
|
5409
5489
|
options: otherDevices.map((d) => ({
|
|
@@ -5520,6 +5600,14 @@ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectD
|
|
|
5520
5600
|
if (manifest.length === 0) {
|
|
5521
5601
|
return { fileCount: 0, projectId: projectId || "", failed: false };
|
|
5522
5602
|
}
|
|
5603
|
+
if (projectId) {
|
|
5604
|
+
const permission = await checkPushPermission(client, projectId);
|
|
5605
|
+
if (!permission.allowed) {
|
|
5606
|
+
printError(`Cannot push: You have ${brand(permission.role)} access to this project.`);
|
|
5607
|
+
printInfo("Only project editors and owners can push changes.");
|
|
5608
|
+
return { fileCount: 0, projectId, failed: true };
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5523
5611
|
const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
|
|
5524
5612
|
printInfo(`${brand(String(manifest.length))} files ${dim(`(${formatBytes(totalBytes)})`)} to push`);
|
|
5525
5613
|
if (options.dryRun) {
|
|
@@ -5688,6 +5776,72 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
|
|
|
5688
5776
|
}
|
|
5689
5777
|
}
|
|
5690
5778
|
}
|
|
5779
|
+
async function collaboratorBundlePull(client, deviceId, projectPath, projectId, canonicalPath, projectsDir, options) {
|
|
5780
|
+
const spinner = createSpinner("Preparing download...").start();
|
|
5781
|
+
let prepareResponse;
|
|
5782
|
+
try {
|
|
5783
|
+
prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
|
|
5784
|
+
deviceId,
|
|
5785
|
+
projectPath,
|
|
5786
|
+
projectId
|
|
5787
|
+
});
|
|
5788
|
+
} catch (err) {
|
|
5789
|
+
spinner.stop();
|
|
5790
|
+
if (err instanceof AuthExpiredError) throw err;
|
|
5791
|
+
printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
5792
|
+
return { fileCount: 0, failed: true };
|
|
5793
|
+
}
|
|
5794
|
+
if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
|
|
5795
|
+
spinner.stop();
|
|
5796
|
+
return { fileCount: 0, failed: false };
|
|
5797
|
+
}
|
|
5798
|
+
const fileCount = prepareResponse.manifest.length;
|
|
5799
|
+
if (options.dryRun) {
|
|
5800
|
+
spinner.stop();
|
|
5801
|
+
printInfo(`${brand(String(fileCount))} files to pull`);
|
|
5802
|
+
if (options.verbose) {
|
|
5803
|
+
for (const entry of prepareResponse.manifest) {
|
|
5804
|
+
console.log(` ${brand("\u25CB")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
return { fileCount, failed: false };
|
|
5808
|
+
}
|
|
5809
|
+
const foreignEncoded = canonicalPath.replace(/\//g, "%2F");
|
|
5810
|
+
const targetDir = join4(projectsDir, foreignEncoded);
|
|
5811
|
+
const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
|
|
5812
|
+
try {
|
|
5813
|
+
const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
|
|
5814
|
+
await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
|
|
5815
|
+
progress.update(bytes);
|
|
5816
|
+
});
|
|
5817
|
+
spinner.text = "Extracting files...";
|
|
5818
|
+
await mkdir3(targetDir, { recursive: true });
|
|
5819
|
+
const extractedCount = await extractBundle(tempBundle, targetDir);
|
|
5820
|
+
try {
|
|
5821
|
+
const completeManifest = await walkDirectory(targetDir);
|
|
5822
|
+
await client.post("/api/sync/pull/bundle/complete", {
|
|
5823
|
+
syncEventId: prepareResponse.syncEventId,
|
|
5824
|
+
manifest: completeManifest
|
|
5825
|
+
});
|
|
5826
|
+
} catch {
|
|
5827
|
+
}
|
|
5828
|
+
progress.finish(`Pulled ${extractedCount} files ${dim(`(${formatBytes(prepareResponse.bundleSize)})`)}`);
|
|
5829
|
+
return { fileCount: extractedCount, failed: false };
|
|
5830
|
+
} finally {
|
|
5831
|
+
try {
|
|
5832
|
+
await rm(tempBundle, { force: true });
|
|
5833
|
+
} catch {
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
async function checkPushPermission(client, projectId) {
|
|
5838
|
+
try {
|
|
5839
|
+
const myRole = await client.get(`/api/projects/${projectId}/my-role`);
|
|
5840
|
+
return { allowed: myRole.canPush, role: myRole.role };
|
|
5841
|
+
} catch {
|
|
5842
|
+
return { allowed: true, role: "owner" };
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5691
5845
|
function syncCommand() {
|
|
5692
5846
|
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) => {
|
|
5693
5847
|
await runSync(options);
|