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