@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/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.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(),
@@ -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()
@@ -4432,6 +4439,15 @@ var require_validation = __commonJS({
4432
4439
  exports.createFeatureRequestCommentSchema = zod_1.z.object({
4433
4440
  body: zod_1.z.string().min(1).max(2e3)
4434
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
+ });
4435
4451
  }
4436
4452
  });
4437
4453
 
@@ -5357,6 +5373,11 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
5357
5373
  }
5358
5374
  devicesSpinner.stop();
5359
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
+ }
5360
5381
  const menuOptions = [];
5361
5382
  if (hasLocalFiles) {
5362
5383
  menuOptions.push({
@@ -5372,6 +5393,13 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
5372
5393
  hint: dim("Pull sessions from a device")
5373
5394
  });
5374
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
+ }
5375
5403
  if (menuOptions.length === 0) {
5376
5404
  printInfo("No local sessions and no other devices found.");
5377
5405
  printInfo("Use Claude Code in this directory first, then run sync.");
@@ -5397,6 +5425,65 @@ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, pr
5397
5425
  });
5398
5426
  return "pushed";
5399
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
+ }
5400
5487
  const deviceChoice = await select2({
5401
5488
  message: brand("Select device"),
5402
5489
  options: otherDevices.map((d) => ({
@@ -5513,6 +5600,14 @@ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectD
5513
5600
  if (manifest.length === 0) {
5514
5601
  return { fileCount: 0, projectId: projectId || "", failed: false };
5515
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
+ }
5516
5611
  const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
5517
5612
  printInfo(`${brand(String(manifest.length))} files ${dim(`(${formatBytes(totalBytes)})`)} to push`);
5518
5613
  if (options.dryRun) {
@@ -5681,6 +5776,72 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
5681
5776
  }
5682
5777
  }
5683
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
+ }
5684
5845
  function syncCommand() {
5685
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) => {
5686
5847
  await runSync(options);