@claude-sync/cli 0.1.16 → 0.1.18

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.
@@ -55,12 +55,12 @@ var require_path_encoding = __commonJS({
55
55
  init_esm_shims();
56
56
  Object.defineProperty(exports, "__esModule", { value: true });
57
57
  exports.ROOT_PATTERN = void 0;
58
- exports.encodePath = encodePath3;
58
+ exports.encodePath = encodePath5;
59
59
  exports.decodeSuffix = decodeSuffix;
60
60
  exports.decodeRoot = decodeRoot;
61
61
  exports.extractCanonicalPath = extractCanonicalPath;
62
62
  exports.ROOT_PATTERN = /^-(home-[^-]+|Users-[^-]+)/;
63
- function encodePath3(p) {
63
+ function encodePath5(p) {
64
64
  return p.replace(/\//g, "-").replace(/\./g, "-");
65
65
  }
66
66
  function decodeSuffix(suffix, projectPath) {
@@ -80,7 +80,7 @@ var require_path_encoding = __commonJS({
80
80
  }
81
81
  function extractCanonicalPath(fullPath, homeDir) {
82
82
  const relative = fullPath.startsWith(homeDir) ? fullPath.slice(homeDir.length) : fullPath;
83
- return encodePath3(relative.startsWith("/") ? relative : "/" + relative);
83
+ return encodePath5(relative.startsWith("/") ? relative : "/" + relative);
84
84
  }
85
85
  }
86
86
  });
@@ -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.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
+ 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.pushBundleCompleteSchema = exports.pushBundlePrepareSchema = exports.manifestEntrySchema = 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(),
@@ -4405,6 +4405,27 @@ var require_validation = __commonJS({
4405
4405
  isCompressed: zod_1.z.boolean()
4406
4406
  }))
4407
4407
  });
4408
+ exports.manifestEntrySchema = zod_1.z.object({
4409
+ path: zod_1.z.string(),
4410
+ hash: zod_1.z.string(),
4411
+ size: zod_1.z.number(),
4412
+ modifiedAt: zod_1.z.string(),
4413
+ isCompressed: zod_1.z.boolean()
4414
+ });
4415
+ exports.pushBundlePrepareSchema = zod_1.z.object({
4416
+ deviceId: zod_1.z.string().uuid(),
4417
+ projectPath: zod_1.z.string().min(1),
4418
+ projectId: zod_1.z.string().uuid().optional(),
4419
+ manifest: zod_1.z.array(exports.manifestEntrySchema),
4420
+ bundleSize: zod_1.z.number().positive(),
4421
+ localVersion: zod_1.z.number().int().min(0),
4422
+ message: zod_1.z.string().max(500).optional()
4423
+ });
4424
+ exports.pushBundleCompleteSchema = zod_1.z.object({
4425
+ syncEventId: zod_1.z.string().uuid(),
4426
+ manifest: zod_1.z.array(exports.manifestEntrySchema),
4427
+ message: zod_1.z.string().min(1).max(500)
4428
+ });
4408
4429
  exports.refreshTokenSchema = zod_1.z.object({
4409
4430
  refreshToken: zod_1.z.string()
4410
4431
  });
@@ -4440,6 +4461,15 @@ var require_validation = __commonJS({
4440
4461
  exports.createFeatureRequestCommentSchema = zod_1.z.object({
4441
4462
  body: zod_1.z.string().min(1).max(2e3)
4442
4463
  });
4464
+ exports.projectRoleSchema = zod_1.z.enum(["owner", "editor", "viewer"]);
4465
+ exports.editableRoleSchema = zod_1.z.enum(["editor", "viewer"]);
4466
+ exports.inviteCollaboratorSchema = zod_1.z.object({
4467
+ email: zod_1.z.string().email(),
4468
+ role: exports.editableRoleSchema
4469
+ });
4470
+ exports.updateCollaboratorRoleSchema = zod_1.z.object({
4471
+ role: exports.editableRoleSchema
4472
+ });
4443
4473
  }
4444
4474
  });
4445
4475
 
@@ -4694,8 +4724,8 @@ var init_auth = __esm({
4694
4724
  // src/lib/progress.ts
4695
4725
  import ora from "ora";
4696
4726
  import chalk from "chalk";
4697
- function createSpinner(text4) {
4698
- return ora({ text: brandColor(text4), spinner: "dots", color: "magenta" });
4727
+ function createSpinner(text5) {
4728
+ return ora({ text: brandColor(text5), spinner: "dots", color: "magenta" });
4699
4729
  }
4700
4730
  function formatBytes(bytes) {
4701
4731
  if (bytes === 0) return "0 B";
@@ -4773,6 +4803,9 @@ function printSuccess(message) {
4773
4803
  function printError(message) {
4774
4804
  console.log(` ${error("\u2716")} ${message}`);
4775
4805
  }
4806
+ function printWarn(message) {
4807
+ console.log(` ${warn("\u25B2")} ${message}`);
4808
+ }
4776
4809
  function printInfo(message) {
4777
4810
  console.log(` ${brand("\u25CF")} ${message}`);
4778
4811
  }
@@ -5271,19 +5304,23 @@ var init_bundle = __esm({
5271
5304
  }
5272
5305
  });
5273
5306
 
5274
- // src/commands/sync.ts
5275
- var sync_exports = {};
5276
- __export(sync_exports, {
5277
- runSync: () => runSync,
5278
- syncCommand: () => syncCommand
5307
+ // src/commands/push.ts
5308
+ var push_exports = {};
5309
+ __export(push_exports, {
5310
+ pushCommand: () => pushCommand
5279
5311
  });
5280
5312
  import { Command as Command4 } from "commander";
5281
- import { select as select2, isCancel as isCancel3 } from "@clack/prompts";
5282
- import { mkdir as mkdir3, lstat as lstat2, symlink, unlink, rename, rm } from "fs/promises";
5313
+ import { text as text3, isCancel as isCancel3 } from "@clack/prompts";
5283
5314
  import { join as join4 } from "path";
5284
5315
  import { homedir as homedir4, tmpdir } from "os";
5285
- async function runSync(options) {
5286
- printIntro("Sync");
5316
+ import { rm } from "fs/promises";
5317
+ function pushCommand() {
5318
+ return new Command4("push").description("Push local changes to cloud").option("-m, --message <message>", "Commit message").option("--dry-run", "Show what would be pushed without pushing").option("--verbose", "Show detailed output").action(async (options) => {
5319
+ await runPush(options);
5320
+ });
5321
+ }
5322
+ async function runPush(options) {
5323
+ printIntro("Push");
5287
5324
  const config = await loadConfig();
5288
5325
  if (!isAuthenticated(config)) {
5289
5326
  printError("Not logged in. Run `claude-sync login` first.");
@@ -5303,275 +5340,930 @@ async function runSync(options) {
5303
5340
  projectDir = join4(projectsDir, ctx.symlinkTarget);
5304
5341
  }
5305
5342
  printInfo(`Project: ${brand(cwd)}`);
5306
- if (ctx.state === "symlink") {
5307
- printInfo(`Linked to: ${dim(ctx.symlinkTarget || "unknown")}`);
5308
- }
5309
5343
  const manifest = await buildProjectManifest(cwd);
5310
- if (!projectLink) {
5311
- const result = await handleFirstRun(client, config, cwd, ctx, manifest, projectDir, projectsDir, options);
5312
- if (result === "cancelled") {
5313
- printOutro("Cancelled.");
5314
- return;
5315
- }
5316
- if (result === "pulled") {
5317
- const finalManifest2 = await buildProjectManifest(cwd);
5318
- await saveProjectManifest(cwd, finalManifest2);
5319
- printOutro("Done");
5320
- return;
5321
- }
5322
- if (result === "pushed") {
5323
- const finalManifest2 = await buildProjectManifest(cwd);
5324
- await saveProjectManifest(cwd, finalManifest2);
5325
- printOutro("Done");
5326
- return;
5327
- }
5344
+ if (manifest.length === 0) {
5345
+ printInfo("No files to push.");
5328
5346
  printOutro("Done");
5329
5347
  return;
5330
5348
  }
5331
- let pushResult = { fileCount: 0, projectId: projectLink.projectId, failed: false };
5332
- let pullResult = { fileCount: 0, failed: false };
5333
- if (manifest.length > 0) {
5334
- pushResult = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, projectLink.projectId, options);
5335
- }
5336
- if (!pushResult.failed) {
5337
- pullResult = await bundlePullPhase(client, config.deviceId, cwd, projectDir, projectLink.projectId, options);
5338
- }
5339
- if (pushResult.failed && pullResult.failed) {
5340
- printOutro("Sync failed.");
5341
- return;
5342
- }
5343
- const finalManifest = await buildProjectManifest(cwd);
5344
- await saveProjectManifest(cwd, finalManifest);
5345
- if (pushResult.fileCount > 0 || pullResult.fileCount > 0) {
5346
- const parts = [];
5347
- if (pushResult.fileCount > 0) parts.push(`${success(`${pushResult.fileCount}`)} pushed`);
5348
- if (pullResult.fileCount > 0) parts.push(`${success(`${pullResult.fileCount}`)} pulled`);
5349
- printSuccess(`Synced: ${parts.join(", ")} files`);
5350
- } else if (!pushResult.failed && !pullResult.failed) {
5351
- printSuccess("Everything is up to date.");
5352
- }
5353
- printOutro("Done");
5354
- }
5355
- async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, projectsDir, options) {
5356
- printInfo(brand("First time syncing this project"));
5357
- const hasLocalFiles = ctx.state !== "none" && manifest.length > 0;
5358
- const devicesSpinner = createSpinner("Checking for other devices...").start();
5359
- let devices;
5360
- try {
5361
- devices = await client.get("/api/devices");
5362
- } catch (err) {
5363
- if (err instanceof AuthExpiredError) throw err;
5364
- devices = [];
5365
- }
5366
- devicesSpinner.stop();
5367
- const otherDevices = devices.filter((d) => d.id !== config.deviceId);
5368
- const menuOptions = [];
5369
- if (hasLocalFiles) {
5370
- menuOptions.push({
5371
- value: "push",
5372
- label: "Push to cloud",
5373
- hint: dim(`Upload ${manifest.length} local files`)
5374
- });
5375
- }
5376
- if (otherDevices.length > 0) {
5377
- menuOptions.push({
5378
- value: "continue",
5379
- label: "Continue from another machine",
5380
- hint: dim("Pull sessions from a device")
5381
- });
5382
- }
5383
- if (menuOptions.length === 0) {
5384
- printInfo("No local sessions and no other devices found.");
5385
- printInfo("Use Claude Code in this directory first, then run sync.");
5386
- return "cancelled";
5387
- }
5388
- if (menuOptions.length === 1 && menuOptions[0].value === "push") {
5389
- printInfo("No other devices found.");
5390
- }
5391
- const choice = await select2({
5392
- message: brand("What would you like to do?"),
5393
- options: menuOptions
5394
- });
5395
- if (isCancel3(choice)) return "cancelled";
5396
- if (choice === "push") {
5397
- const result2 = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, void 0, options);
5398
- if (result2.failed) {
5399
- return "cancelled";
5400
- }
5401
- await saveProjectLink(cwd, {
5402
- projectId: result2.projectId,
5403
- foreignEncodedDir: ctx.encodedPath,
5404
- linkedAt: (/* @__PURE__ */ new Date()).toISOString()
5405
- });
5406
- return "pushed";
5407
- }
5408
- const deviceChoice = await select2({
5409
- message: brand("Select device"),
5410
- options: otherDevices.map((d) => ({
5411
- value: d.id,
5412
- label: d.name,
5413
- hint: dim(`${d.hostname} \xB7 ${d.platform}`)
5414
- }))
5415
- });
5416
- if (isCancel3(deviceChoice)) return "cancelled";
5417
- const spinner = createSpinner("Loading projects...").start();
5418
- let projects;
5419
- try {
5420
- projects = await client.get(`/api/devices/${deviceChoice}/projects`);
5421
- } catch (err) {
5422
- spinner.stop();
5423
- if (err instanceof AuthExpiredError) throw err;
5424
- printError("Failed to load projects from device.");
5425
- return "cancelled";
5426
- }
5427
- spinner.stop();
5428
- if (projects.length === 0) {
5429
- printInfo("No projects found on that device.");
5430
- return "cancelled";
5431
- }
5432
- const projectChoice = await select2({
5433
- message: brand("Select project"),
5434
- options: projects.map((p) => ({
5435
- value: p.id,
5436
- label: p.displayName || p.originalPath,
5437
- hint: dim(p.localPath)
5438
- }))
5439
- });
5440
- if (isCancel3(projectChoice)) return "cancelled";
5441
- const selectedProject = projects.find((p) => p.id === projectChoice);
5442
- const existingCheck = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
5443
- if (existingCheck === "cancelled") {
5444
- return "cancelled";
5445
- }
5446
- const result = await crossMachineBundlePull(
5447
- client,
5448
- config.deviceId,
5449
- cwd,
5450
- deviceChoice,
5451
- selectedProject.id,
5452
- selectedProject.encodedDir,
5453
- projectsDir,
5454
- options
5455
- );
5456
- if (result.fileCount === 0 && !options.dryRun) {
5457
- printInfo("No files to download.");
5458
- return "cancelled";
5459
- }
5460
- if (!options.dryRun) {
5461
- await createProjectSymlink(projectsDir, ctx.encodedPath, selectedProject.encodedDir);
5462
- await saveProjectLink(cwd, {
5463
- projectId: selectedProject.id,
5464
- foreignEncodedDir: selectedProject.encodedDir,
5465
- linkedAt: (/* @__PURE__ */ new Date()).toISOString()
5466
- });
5467
- printInfo(`Symlink: ${dim(ctx.encodedPath)} \u2192 ${dim(selectedProject.encodedDir)}`);
5468
- }
5469
- return "pulled";
5470
- }
5471
- async function handleExistingSessionDir(projectsDir, localEncoded) {
5472
- const symlinkPath = join4(projectsDir, localEncoded);
5473
- try {
5474
- const stats = await lstat2(symlinkPath);
5475
- if (stats.isSymbolicLink()) {
5476
- return "continue";
5477
- }
5478
- if (stats.isDirectory()) {
5479
- printInfo(brand("Local session directory already exists"));
5480
- printInfo(dim(symlinkPath));
5481
- const choice = await select2({
5482
- message: brand("What would you like to do?"),
5483
- options: [
5484
- {
5485
- value: "backup",
5486
- label: "Backup and continue",
5487
- hint: dim("Move existing to .bak and sync from remote")
5488
- },
5489
- {
5490
- value: "cancel",
5491
- label: "Stop sync",
5492
- hint: dim("Keep existing local sessions")
5493
- }
5494
- ]
5495
- });
5496
- if (isCancel3(choice) || choice === "cancel") {
5497
- return "cancelled";
5498
- }
5499
- const backupPath = `${symlinkPath}.bak.${Date.now()}`;
5500
- await rename(symlinkPath, backupPath);
5501
- printSuccess(`Backed up to: ${dim(backupPath)}`);
5502
- return "continue";
5503
- }
5504
- } catch {
5505
- }
5506
- return "continue";
5507
- }
5508
- async function createProjectSymlink(projectsDir, localEncoded, foreignEncoded) {
5509
- const symlinkPath = join4(projectsDir, localEncoded);
5510
- await mkdir3(projectsDir, { recursive: true });
5511
- try {
5512
- const stats = await lstat2(symlinkPath);
5513
- if (stats.isSymbolicLink()) {
5514
- await unlink(symlinkPath);
5515
- }
5516
- } catch {
5517
- }
5518
- await symlink(foreignEncoded, symlinkPath);
5519
- }
5520
- async function bundlePushPhase(client, deviceId, projectPath, manifest, projectDir, projectId, options) {
5521
- if (manifest.length === 0) {
5522
- return { fileCount: 0, projectId: projectId || "", failed: false };
5523
- }
5524
5349
  const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
5525
- printInfo(`${brand(String(manifest.length))} files ${dim(`(${formatBytes(totalBytes)})`)} to push`);
5350
+ printInfo(`Files: ${brand(String(manifest.length))} ${dim(`(${formatBytes(totalBytes)})`)}`);
5526
5351
  if (options.dryRun) {
5352
+ console.log();
5353
+ printInfo("Changes to push:");
5527
5354
  if (options.verbose) {
5528
5355
  for (const entry of manifest) {
5529
5356
  console.log(` ${success("+")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
5530
5357
  }
5358
+ } else {
5359
+ console.log(` ${brand(String(manifest.length))} files would be uploaded`);
5360
+ }
5361
+ printOutro("Dry run complete");
5362
+ return;
5363
+ }
5364
+ if (projectLink?.projectId) {
5365
+ const permission = await checkPushPermission(client, projectLink.projectId);
5366
+ if (!permission.allowed) {
5367
+ printError(`Cannot push: You have ${brand(permission.role)} access to this project.`);
5368
+ printInfo("Only project editors and owners can push changes.");
5369
+ return;
5531
5370
  }
5532
- return { fileCount: manifest.length, projectId: projectId || "", failed: false };
5371
+ }
5372
+ const localVersion = projectLink?.localVersion ?? 0;
5373
+ let commitMessage = options.message;
5374
+ if (!commitMessage) {
5375
+ const result = await text3({
5376
+ message: brand("Commit message:"),
5377
+ placeholder: "Describe your changes...",
5378
+ validate: (value) => {
5379
+ if (!value || value.trim().length === 0) {
5380
+ return "Please enter a commit message";
5381
+ }
5382
+ if (value.length > 500) {
5383
+ return "Message too long (max 500 characters)";
5384
+ }
5385
+ }
5386
+ });
5387
+ if (isCancel3(result)) {
5388
+ printInfo("Push cancelled.");
5389
+ return;
5390
+ }
5391
+ commitMessage = result;
5533
5392
  }
5534
5393
  const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
5535
5394
  try {
5536
- const spinner = createSpinner("Syncing...").start();
5395
+ const spinner = createSpinner("Preparing...").start();
5537
5396
  const bundleResult = await createBundle(projectDir, manifest, tempBundle, (bytes, total, phase) => {
5538
5397
  if (phase === "compressing") {
5539
5398
  const percent = Math.round(bytes / total * 100);
5540
5399
  spinner.text = `Compressing files... ${dim(`${percent}%`)}`;
5541
5400
  }
5542
5401
  });
5543
- const progress = createTransferProgress("Uploading", bundleResult.size, spinner);
5402
+ spinner.text = "Checking remote...";
5544
5403
  let prepareResponse;
5545
5404
  try {
5546
5405
  prepareResponse = await client.post("/api/sync/push/bundle/prepare", {
5547
- deviceId,
5548
- projectPath,
5549
- projectId,
5406
+ deviceId: config.deviceId,
5407
+ projectPath: cwd,
5408
+ projectId: projectLink?.projectId,
5550
5409
  manifest,
5551
- bundleSize: bundleResult.size
5410
+ bundleSize: bundleResult.size,
5411
+ localVersion,
5412
+ message: commitMessage
5552
5413
  });
5553
5414
  } catch (err) {
5554
5415
  spinner.stop();
5555
5416
  if (err instanceof AuthExpiredError) throw err;
5556
5417
  printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
5557
- return { fileCount: 0, projectId: projectId || "", failed: true };
5418
+ return;
5558
5419
  }
5559
- await streamUpload(prepareResponse.uploadUrl, tempBundle, (bytes, total) => {
5560
- progress.update(bytes);
5561
- });
5562
- try {
5563
- await client.post("/api/sync/push/bundle/complete", {
5564
- syncEventId: prepareResponse.syncEventId,
5565
- manifest
5566
- });
5567
- } catch {
5420
+ if (!prepareResponse.canProceed) {
5421
+ spinner.stop();
5422
+ console.log();
5423
+ printError(`Cannot push: You are ${brand(String(prepareResponse.behindBy))} version(s) behind.`);
5424
+ console.log();
5425
+ if (prepareResponse.recentCommits && prepareResponse.recentCommits.length > 0) {
5426
+ printInfo("Recent commits on remote:");
5427
+ for (const commit of prepareResponse.recentCommits) {
5428
+ const deviceName = commit.device?.name || "Unknown";
5429
+ const timeAgo = formatTimeAgo(commit.createdAt);
5430
+ console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName}, ${timeAgo})`)}`);
5431
+ }
5432
+ console.log();
5433
+ }
5434
+ printInfo(`Run ${brand("claude-sync pull")} first to get these changes.`);
5435
+ return;
5436
+ }
5437
+ const progress = createTransferProgress("Uploading", bundleResult.size, spinner);
5438
+ await streamUpload(prepareResponse.uploadUrl, tempBundle, (bytes) => {
5439
+ progress.update(bytes);
5440
+ });
5441
+ let completeResponse;
5442
+ try {
5443
+ completeResponse = await client.post("/api/sync/push/bundle/complete", {
5444
+ syncEventId: prepareResponse.syncEventId,
5445
+ manifest,
5446
+ message: commitMessage
5447
+ });
5448
+ } catch (err) {
5449
+ spinner.stop();
5450
+ if (err instanceof AuthExpiredError) throw err;
5451
+ printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
5452
+ return;
5453
+ }
5454
+ const newVersion = completeResponse.commit.version;
5455
+ const compressionRatio = Math.round((1 - bundleResult.size / bundleResult.originalSize) * 100);
5456
+ progress.finish(`Pushed ${manifest.length} files ${dim(`(${formatBytes(bundleResult.size)}, ${compressionRatio}% smaller)`)}`);
5457
+ await saveProjectLink(cwd, {
5458
+ projectId: prepareResponse.projectId,
5459
+ foreignEncodedDir: (0, import_utils3.encodePath)(cwd),
5460
+ linkedAt: projectLink?.linkedAt || (/* @__PURE__ */ new Date()).toISOString(),
5461
+ localVersion: newVersion
5462
+ });
5463
+ console.log();
5464
+ printSuccess(`Commit: "${commitMessage}"`);
5465
+ printInfo(`Version: ${localVersion} \u2192 ${newVersion}`);
5466
+ printOutro("Done");
5467
+ } finally {
5468
+ try {
5469
+ await rm(tempBundle, { force: true });
5470
+ } catch {
5471
+ }
5472
+ }
5473
+ }
5474
+ async function checkPushPermission(client, projectId) {
5475
+ try {
5476
+ const roleInfo = await client.get(`/api/projects/${projectId}/my-role`);
5477
+ return { allowed: roleInfo.canPush, role: roleInfo.role };
5478
+ } catch {
5479
+ return { allowed: true, role: "owner" };
5480
+ }
5481
+ }
5482
+ function formatTimeAgo(dateStr) {
5483
+ const date = new Date(dateStr);
5484
+ const now = /* @__PURE__ */ new Date();
5485
+ const diffMs = now.getTime() - date.getTime();
5486
+ const diffMins = Math.floor(diffMs / 6e4);
5487
+ const diffHours = Math.floor(diffMs / 36e5);
5488
+ const diffDays = Math.floor(diffMs / 864e5);
5489
+ if (diffMins < 1) return "just now";
5490
+ if (diffMins < 60) return `${diffMins}m ago`;
5491
+ if (diffHours < 24) return `${diffHours}h ago`;
5492
+ if (diffDays < 7) return `${diffDays}d ago`;
5493
+ return date.toLocaleDateString();
5494
+ }
5495
+ var import_utils3;
5496
+ var init_push = __esm({
5497
+ "src/commands/push.ts"() {
5498
+ "use strict";
5499
+ init_esm_shims();
5500
+ init_config();
5501
+ init_api_client();
5502
+ init_sync_engine();
5503
+ init_progress();
5504
+ init_bundle();
5505
+ import_utils3 = __toESM(require_dist(), 1);
5506
+ init_theme();
5507
+ }
5508
+ });
5509
+
5510
+ // src/commands/pull.ts
5511
+ var pull_exports = {};
5512
+ __export(pull_exports, {
5513
+ pullCommand: () => pullCommand
5514
+ });
5515
+ import { Command as Command5 } from "commander";
5516
+ import { confirm, isCancel as isCancel4 } from "@clack/prompts";
5517
+ import { join as join5 } from "path";
5518
+ import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
5519
+ import { rm as rm2, mkdir as mkdir3 } from "fs/promises";
5520
+ function pullCommand() {
5521
+ return new Command5("pull").description("Pull latest changes from cloud").option("--dry-run", "Show what would be pulled without pulling").option("--verbose", "Show detailed output").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
5522
+ await runPull(options);
5523
+ });
5524
+ }
5525
+ async function runPull(options) {
5526
+ printIntro("Pull");
5527
+ const config = await loadConfig();
5528
+ if (!isAuthenticated(config)) {
5529
+ printError("Not logged in. Run `claude-sync login` first.");
5530
+ return;
5531
+ }
5532
+ if (!config.deviceId) {
5533
+ printError("No device registered. Run `claude-sync device add` first.");
5534
+ return;
5535
+ }
5536
+ const cwd = process.cwd();
5537
+ const ctx = await resolveProjectState(cwd);
5538
+ const client = new ApiClient(config.apiUrl);
5539
+ const projectsDir = join5(homedir5(), import_utils4.CLAUDE_DIR, import_utils4.PROJECTS_DIR);
5540
+ const projectLink = await loadProjectLink(cwd);
5541
+ let projectDir = ctx.projectDir;
5542
+ if (ctx.state === "symlink" && ctx.symlinkTarget) {
5543
+ projectDir = join5(projectsDir, ctx.symlinkTarget);
5544
+ }
5545
+ printInfo(`Project: ${brand(cwd)}`);
5546
+ if (!projectLink?.projectId) {
5547
+ printInfo("No project linked yet. Run `claude-sync push` first to create one.");
5548
+ printOutro("Done");
5549
+ return;
5550
+ }
5551
+ const localVersion = projectLink.localVersion ?? 0;
5552
+ const spinner = createSpinner("Checking for updates...").start();
5553
+ let syncState;
5554
+ try {
5555
+ syncState = await client.get(`/api/projects/${projectLink.projectId}/sync-state?localVersion=${localVersion}`);
5556
+ } catch (err) {
5557
+ spinner.stop();
5558
+ if (err instanceof AuthExpiredError) throw err;
5559
+ printError(`Failed to check sync state: ${err instanceof Error ? err.message : "Unknown error"}`);
5560
+ return;
5561
+ }
5562
+ if (!syncState.isBehind) {
5563
+ spinner.stop();
5564
+ console.log();
5565
+ printSuccess("Already up to date.");
5566
+ printInfo(`Version: ${localVersion} (latest)`);
5567
+ printOutro("Done");
5568
+ return;
5569
+ }
5570
+ spinner.stop();
5571
+ console.log();
5572
+ printInfo(`You are ${brand(String(syncState.behindBy))} version(s) behind.`);
5573
+ printInfo(`Local: v${localVersion} Remote: v${syncState.currentVersion}`);
5574
+ console.log();
5575
+ if (syncState.recentCommits.length > 0) {
5576
+ printInfo("Commits to pull:");
5577
+ for (const commit of syncState.recentCommits) {
5578
+ const deviceName = commit.device?.name || "Unknown";
5579
+ const timeAgo = formatTimeAgo2(commit.createdAt);
5580
+ console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName}, ${timeAgo})`)}`);
5581
+ }
5582
+ console.log();
5583
+ }
5584
+ if (options.dryRun) {
5585
+ printOutro("Dry run complete");
5586
+ return;
5587
+ }
5588
+ if (!options.yes) {
5589
+ const proceed = await confirm({
5590
+ message: brand(`Pull ${syncState.behindBy} version(s)?`),
5591
+ initialValue: true
5592
+ });
5593
+ if (isCancel4(proceed) || !proceed) {
5594
+ printInfo("Pull cancelled.");
5595
+ return;
5596
+ }
5597
+ }
5598
+ const pullSpinner = createSpinner("Downloading...").start();
5599
+ let prepareResponse;
5600
+ try {
5601
+ prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
5602
+ deviceId: config.deviceId,
5603
+ projectPath: cwd,
5604
+ projectId: projectLink.projectId
5605
+ });
5606
+ } catch (err) {
5607
+ pullSpinner.stop();
5608
+ if (err instanceof AuthExpiredError) throw err;
5609
+ printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
5610
+ return;
5611
+ }
5612
+ if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
5613
+ pullSpinner.stop();
5614
+ printInfo("No files to download.");
5615
+ printOutro("Done");
5616
+ return;
5617
+ }
5618
+ const tempBundle = join5(tmpdir2(), `claude-sync-pull-${Date.now()}.tar.gz`);
5619
+ try {
5620
+ const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, pullSpinner);
5621
+ await streamDownload(prepareResponse.downloadUrl, tempBundle, (bytes) => {
5622
+ progress.update(bytes);
5623
+ });
5624
+ progress.finish("Downloaded");
5625
+ const extractSpinner = createSpinner("Extracting...").start();
5626
+ await mkdir3(projectDir, { recursive: true });
5627
+ const extractedCount = await extractBundle(tempBundle, projectDir);
5628
+ extractSpinner.succeed(`Extracted ${extractedCount} files`);
5629
+ try {
5630
+ await client.post("/api/sync/pull/bundle/complete", {
5631
+ syncEventId: prepareResponse.syncEventId,
5632
+ manifest: prepareResponse.manifest
5633
+ });
5634
+ } catch {
5635
+ }
5636
+ await saveProjectLink(cwd, {
5637
+ ...projectLink,
5638
+ localVersion: syncState.currentVersion
5639
+ });
5640
+ console.log();
5641
+ printSuccess(`Pulled ${prepareResponse.manifest.length} files`);
5642
+ printInfo(`Version: ${localVersion} \u2192 ${syncState.currentVersion}`);
5643
+ printOutro("Done");
5644
+ } finally {
5645
+ try {
5646
+ await rm2(tempBundle, { force: true });
5647
+ } catch {
5648
+ }
5649
+ }
5650
+ }
5651
+ function formatTimeAgo2(dateStr) {
5652
+ const date = new Date(dateStr);
5653
+ const now = /* @__PURE__ */ new Date();
5654
+ const diffMs = now.getTime() - date.getTime();
5655
+ const diffMins = Math.floor(diffMs / 6e4);
5656
+ const diffHours = Math.floor(diffMs / 36e5);
5657
+ const diffDays = Math.floor(diffMs / 864e5);
5658
+ if (diffMins < 1) return "just now";
5659
+ if (diffMins < 60) return `${diffMins}m ago`;
5660
+ if (diffHours < 24) return `${diffHours}h ago`;
5661
+ if (diffDays < 7) return `${diffDays}d ago`;
5662
+ return date.toLocaleDateString();
5663
+ }
5664
+ var import_utils4;
5665
+ var init_pull = __esm({
5666
+ "src/commands/pull.ts"() {
5667
+ "use strict";
5668
+ init_esm_shims();
5669
+ init_config();
5670
+ init_api_client();
5671
+ init_sync_engine();
5672
+ init_progress();
5673
+ init_bundle();
5674
+ import_utils4 = __toESM(require_dist(), 1);
5675
+ init_theme();
5676
+ }
5677
+ });
5678
+
5679
+ // src/commands/log.ts
5680
+ var log_exports = {};
5681
+ __export(log_exports, {
5682
+ logCommand: () => logCommand
5683
+ });
5684
+ import { Command as Command6 } from "commander";
5685
+ function logCommand() {
5686
+ return new Command6("log").description("Show commit history for current project").option("-n, --limit <number>", "Number of commits to show", "10").action(async (options) => {
5687
+ await runLog(options);
5688
+ });
5689
+ }
5690
+ async function runLog(options) {
5691
+ printIntro("Log");
5692
+ const config = await loadConfig();
5693
+ if (!isAuthenticated(config)) {
5694
+ printError("Not logged in. Run `claude-sync login` first.");
5695
+ return;
5696
+ }
5697
+ const cwd = process.cwd();
5698
+ const projectLink = await loadProjectLink(cwd);
5699
+ printInfo(`Project: ${brand(cwd)}`);
5700
+ if (!projectLink?.projectId) {
5701
+ printInfo("No project linked yet. Run `claude-sync push` first.");
5702
+ printOutro("Done");
5703
+ return;
5704
+ }
5705
+ const client = new ApiClient(config.apiUrl);
5706
+ const limit = parseInt(options.limit, 10) || 10;
5707
+ let commits;
5708
+ try {
5709
+ commits = await client.get(`/api/projects/${projectLink.projectId}/commits?limit=${limit}`);
5710
+ } catch (err) {
5711
+ if (err instanceof AuthExpiredError) throw err;
5712
+ printError(`Failed to fetch commits: ${err instanceof Error ? err.message : "Unknown error"}`);
5713
+ return;
5714
+ }
5715
+ if (commits.length === 0) {
5716
+ printInfo("No commits yet.");
5717
+ printOutro("Done");
5718
+ return;
5719
+ }
5720
+ const localVersion = projectLink.localVersion ?? 0;
5721
+ console.log();
5722
+ for (const commit of commits) {
5723
+ const deviceName = commit.device?.name || "Unknown device";
5724
+ const timeAgo = formatTimeAgo3(commit.createdAt);
5725
+ const isHead = commit.version === localVersion;
5726
+ const versionStr = isHead ? `${brand(`v${commit.version}`)} ${dim("(HEAD)")}` : `v${commit.version}`;
5727
+ console.log(` ${versionStr} "${commit.message}"`);
5728
+ console.log(` ${dim(`${deviceName} \u2022 ${timeAgo}`)}`);
5729
+ console.log();
5730
+ }
5731
+ printOutro("");
5732
+ }
5733
+ function formatTimeAgo3(dateStr) {
5734
+ const date = new Date(dateStr);
5735
+ const now = /* @__PURE__ */ new Date();
5736
+ const diffMs = now.getTime() - date.getTime();
5737
+ const diffMins = Math.floor(diffMs / 6e4);
5738
+ const diffHours = Math.floor(diffMs / 36e5);
5739
+ const diffDays = Math.floor(diffMs / 864e5);
5740
+ if (diffMins < 1) return "just now";
5741
+ if (diffMins < 60) return `${diffMins}m ago`;
5742
+ if (diffHours < 24) return `${diffHours}h ago`;
5743
+ if (diffDays < 7) return `${diffDays}d ago`;
5744
+ return date.toLocaleDateString();
5745
+ }
5746
+ var init_log = __esm({
5747
+ "src/commands/log.ts"() {
5748
+ "use strict";
5749
+ init_esm_shims();
5750
+ init_config();
5751
+ init_api_client();
5752
+ init_theme();
5753
+ }
5754
+ });
5755
+
5756
+ // src/commands/status.ts
5757
+ var status_exports = {};
5758
+ __export(status_exports, {
5759
+ statusCommand: () => statusCommand
5760
+ });
5761
+ import { Command as Command8 } from "commander";
5762
+ function statusCommand() {
5763
+ return new Command8("status").description("Show sync status for current project").action(async () => {
5764
+ printIntro("Status");
5765
+ const config = await loadConfig();
5766
+ const cwd = process.cwd();
5767
+ printLabel("API", config.apiUrl);
5768
+ printLabel("Authenticated", isAuthenticated(config) ? success("yes") : error("no"));
5769
+ printLabel("Device", config.deviceName || dim("none"));
5770
+ printLabel("Project", brand(cwd));
5771
+ const ctx = await resolveProjectState(cwd);
5772
+ printLabel("State", ctx.state === "none" ? dim("no session data") : ctx.state === "symlink" ? `symlink \u2192 ${dim(ctx.symlinkTarget || "unknown")}` : "local");
5773
+ if (!isAuthenticated(config) || ctx.state === "none") return;
5774
+ const spinner = createSpinner("Scanning files...").start();
5775
+ const savedManifest = await loadProjectManifest(cwd);
5776
+ const currentManifest = await buildProjectManifest(cwd);
5777
+ const diff = computeDiff(currentManifest, savedManifest);
5778
+ spinner.stop();
5779
+ const totalSize = currentManifest.reduce((sum, e) => sum + e.size, 0);
5780
+ printDivider();
5781
+ printLabel("Local files", `${brand(String(currentManifest.length))} ${dim(`(${formatBytes(totalSize)})`)}`);
5782
+ console.log();
5783
+ printInfo("Changes since last sync:");
5784
+ console.log(` ${success(`+${diff.added.length}`)} added ${warn(`~${diff.modified.length}`)} modified ${error(`-${diff.deleted.length}`)} deleted`);
5785
+ console.log();
5786
+ const projectLink = await loadProjectLink(cwd);
5787
+ if (projectLink?.projectId) {
5788
+ const client = new ApiClient(config.apiUrl);
5789
+ const localVersion = projectLink.localVersion ?? 0;
5790
+ try {
5791
+ const syncState = await client.get(`/api/projects/${projectLink.projectId}/sync-state?localVersion=${localVersion}`);
5792
+ if (syncState.isBehind) {
5793
+ printWarn(`You are ${brand(String(syncState.behindBy))} version(s) behind.`);
5794
+ printInfo(`Local: v${localVersion} Remote: v${syncState.currentVersion}`);
5795
+ console.log();
5796
+ if (syncState.recentCommits.length > 0) {
5797
+ printInfo("Recent commits on remote:");
5798
+ for (const commit of syncState.recentCommits) {
5799
+ const deviceName = commit.device?.name || "Unknown";
5800
+ console.log(` v${commit.version} "${commit.message}" ${dim(`(${deviceName})`)}`);
5801
+ }
5802
+ console.log();
5803
+ }
5804
+ printInfo(`Run ${brand("claude-sync pull")} to get latest changes.`);
5805
+ } else {
5806
+ printSuccess("Up to date");
5807
+ printInfo(`Version: ${localVersion}`);
5808
+ }
5809
+ } catch {
5810
+ printInfo(`Local version: ${localVersion}`);
5811
+ }
5812
+ }
5813
+ });
5814
+ }
5815
+ var init_status = __esm({
5816
+ "src/commands/status.ts"() {
5817
+ "use strict";
5818
+ init_esm_shims();
5819
+ init_config();
5820
+ init_sync_engine();
5821
+ init_api_client();
5822
+ init_progress();
5823
+ init_theme();
5824
+ }
5825
+ });
5826
+
5827
+ // src/commands/whoami.ts
5828
+ var whoami_exports = {};
5829
+ __export(whoami_exports, {
5830
+ whoamiCommand: () => whoamiCommand
5831
+ });
5832
+ import { Command as Command9 } from "commander";
5833
+ function whoamiCommand() {
5834
+ return new Command9("whoami").description("Show current user and device info").action(async () => {
5835
+ printIntro("Who Am I");
5836
+ const config = await loadConfig();
5837
+ if (!isAuthenticated(config)) {
5838
+ printError("Not logged in. Run `claude-sync login` first.");
5839
+ return;
5840
+ }
5841
+ const client = new ApiClient(config.apiUrl);
5842
+ const spinner = createSpinner("Fetching user info...").start();
5843
+ try {
5844
+ const user = await client.get("/api/auth/me");
5845
+ spinner.stop();
5846
+ printLabel("User", `${user.name} ${dim(`(${user.email})`)}`);
5847
+ printLabel("Plan", brand(user.plan));
5848
+ printDivider();
5849
+ printLabel("Device", config.deviceName || dim("none"));
5850
+ printLabel("Device ID", config.deviceId || dim("not registered"));
5851
+ } catch (err) {
5852
+ spinner.stop();
5853
+ if (err instanceof AuthExpiredError) throw err;
5854
+ printLabel("Email", config.email || dim("unknown"));
5855
+ printLabel("Device", config.deviceName || dim("none"));
5856
+ }
5857
+ console.log();
5858
+ });
5859
+ }
5860
+ var init_whoami = __esm({
5861
+ "src/commands/whoami.ts"() {
5862
+ "use strict";
5863
+ init_esm_shims();
5864
+ init_config();
5865
+ init_api_client();
5866
+ init_progress();
5867
+ init_theme();
5868
+ }
5869
+ });
5870
+
5871
+ // bin/claude-sync.ts
5872
+ init_esm_shims();
5873
+
5874
+ // src/index.ts
5875
+ init_esm_shims();
5876
+ init_login();
5877
+ init_logout();
5878
+ init_device();
5879
+ init_push();
5880
+ init_pull();
5881
+ init_log();
5882
+ import { Command as Command10 } from "commander";
5883
+
5884
+ // src/commands/sync.ts
5885
+ init_esm_shims();
5886
+ init_config();
5887
+ init_api_client();
5888
+ init_sync_engine();
5889
+ init_progress();
5890
+ init_bundle();
5891
+ var import_utils5 = __toESM(require_dist(), 1);
5892
+ init_theme();
5893
+ import { Command as Command7 } from "commander";
5894
+ import { select as select2, isCancel as isCancel5 } from "@clack/prompts";
5895
+ import { mkdir as mkdir4, lstat as lstat2, symlink, unlink, rename, rm as rm3 } from "fs/promises";
5896
+ import { join as join6 } from "path";
5897
+ import { homedir as homedir6, tmpdir as tmpdir3 } from "os";
5898
+ async function runSync(options) {
5899
+ printIntro("Sync");
5900
+ const config = await loadConfig();
5901
+ if (!isAuthenticated(config)) {
5902
+ printError("Not logged in. Run `claude-sync login` first.");
5903
+ return;
5904
+ }
5905
+ if (!config.deviceId) {
5906
+ printError("No device registered. Run `claude-sync device add` first.");
5907
+ return;
5908
+ }
5909
+ const cwd = process.cwd();
5910
+ const ctx = await resolveProjectState(cwd);
5911
+ const client = new ApiClient(config.apiUrl);
5912
+ const projectsDir = join6(homedir6(), import_utils5.CLAUDE_DIR, import_utils5.PROJECTS_DIR);
5913
+ const projectLink = await loadProjectLink(cwd);
5914
+ let projectDir = ctx.projectDir;
5915
+ if (ctx.state === "symlink" && ctx.symlinkTarget) {
5916
+ projectDir = join6(projectsDir, ctx.symlinkTarget);
5917
+ }
5918
+ printInfo(`Project: ${brand(cwd)}`);
5919
+ if (ctx.state === "symlink") {
5920
+ printInfo(`Linked to: ${dim(ctx.symlinkTarget || "unknown")}`);
5921
+ }
5922
+ const manifest = await buildProjectManifest(cwd);
5923
+ if (!projectLink) {
5924
+ const result = await handleFirstRun(client, config, cwd, ctx, manifest, projectDir, projectsDir, options);
5925
+ if (result === "cancelled") {
5926
+ printOutro("Cancelled.");
5927
+ return;
5928
+ }
5929
+ if (result === "pulled") {
5930
+ const finalManifest2 = await buildProjectManifest(cwd);
5931
+ await saveProjectManifest(cwd, finalManifest2);
5932
+ printOutro("Done");
5933
+ return;
5934
+ }
5935
+ if (result === "pushed") {
5936
+ const finalManifest2 = await buildProjectManifest(cwd);
5937
+ await saveProjectManifest(cwd, finalManifest2);
5938
+ printOutro("Done");
5939
+ return;
5940
+ }
5941
+ printOutro("Done");
5942
+ return;
5943
+ }
5944
+ let pushResult = { fileCount: 0, projectId: projectLink.projectId, failed: false };
5945
+ let pullResult = { fileCount: 0, failed: false };
5946
+ if (manifest.length > 0) {
5947
+ pushResult = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, projectLink.projectId, options);
5948
+ }
5949
+ if (!pushResult.failed) {
5950
+ pullResult = await bundlePullPhase(client, config.deviceId, cwd, projectDir, projectLink.projectId, options);
5951
+ }
5952
+ if (pushResult.failed && pullResult.failed) {
5953
+ printOutro("Sync failed.");
5954
+ return;
5955
+ }
5956
+ const finalManifest = await buildProjectManifest(cwd);
5957
+ await saveProjectManifest(cwd, finalManifest);
5958
+ if (pushResult.fileCount > 0 || pullResult.fileCount > 0) {
5959
+ const parts = [];
5960
+ if (pushResult.fileCount > 0) parts.push(`${success(`${pushResult.fileCount}`)} pushed`);
5961
+ if (pullResult.fileCount > 0) parts.push(`${success(`${pullResult.fileCount}`)} pulled`);
5962
+ printSuccess(`Synced: ${parts.join(", ")} files`);
5963
+ } else if (!pushResult.failed && !pullResult.failed) {
5964
+ printSuccess("Everything is up to date.");
5965
+ }
5966
+ printOutro("Done");
5967
+ }
5968
+ async function handleFirstRun(client, config, cwd, ctx, manifest, projectDir, projectsDir, options) {
5969
+ printInfo(brand("First time syncing this project"));
5970
+ const hasLocalFiles = ctx.state !== "none" && manifest.length > 0;
5971
+ const devicesSpinner = createSpinner("Checking for other devices...").start();
5972
+ let devices;
5973
+ try {
5974
+ devices = await client.get("/api/devices");
5975
+ } catch (err) {
5976
+ if (err instanceof AuthExpiredError) throw err;
5977
+ devices = [];
5978
+ }
5979
+ devicesSpinner.stop();
5980
+ const otherDevices = devices.filter((d) => d.id !== config.deviceId);
5981
+ let sharedProjects = [];
5982
+ try {
5983
+ sharedProjects = await client.get("/api/projects/shared");
5984
+ } catch {
5985
+ }
5986
+ const menuOptions = [];
5987
+ if (hasLocalFiles) {
5988
+ menuOptions.push({
5989
+ value: "push",
5990
+ label: "Push to cloud",
5991
+ hint: dim(`Upload ${manifest.length} local files`)
5992
+ });
5993
+ }
5994
+ if (otherDevices.length > 0) {
5995
+ menuOptions.push({
5996
+ value: "continue",
5997
+ label: "Continue from another machine",
5998
+ hint: dim("Pull sessions from a device")
5999
+ });
6000
+ }
6001
+ if (sharedProjects.length > 0) {
6002
+ menuOptions.push({
6003
+ value: "collaborator",
6004
+ label: "Sync from collaborator",
6005
+ hint: dim(`${sharedProjects.length} shared project(s) available`)
6006
+ });
6007
+ }
6008
+ if (menuOptions.length === 0) {
6009
+ printInfo("No local sessions and no other devices found.");
6010
+ printInfo("Use Claude Code in this directory first, then run sync.");
6011
+ return "cancelled";
6012
+ }
6013
+ if (menuOptions.length === 1 && menuOptions[0].value === "push") {
6014
+ printInfo("No other devices found.");
6015
+ }
6016
+ const choice = await select2({
6017
+ message: brand("What would you like to do?"),
6018
+ options: menuOptions
6019
+ });
6020
+ if (isCancel5(choice)) return "cancelled";
6021
+ if (choice === "push") {
6022
+ const result2 = await bundlePushPhase(client, config.deviceId, cwd, manifest, projectDir, void 0, options);
6023
+ if (result2.failed) {
6024
+ return "cancelled";
6025
+ }
6026
+ await saveProjectLink(cwd, {
6027
+ projectId: result2.projectId,
6028
+ foreignEncodedDir: ctx.encodedPath,
6029
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
6030
+ });
6031
+ return "pushed";
6032
+ }
6033
+ if (choice === "collaborator") {
6034
+ const sharedChoice = await select2({
6035
+ message: brand("Select shared project"),
6036
+ options: sharedProjects.map((p) => ({
6037
+ value: p.id,
6038
+ label: p.displayName || p.originalPath,
6039
+ hint: dim(`${p.owner.name} \xB7 ${p.role}`)
6040
+ }))
6041
+ });
6042
+ if (isCancel5(sharedChoice)) return "cancelled";
6043
+ const selectedShared = sharedProjects.find((p) => p.id === sharedChoice);
6044
+ const syncStatusSpinner = createSpinner("Checking sync status...").start();
6045
+ let syncStatus = null;
6046
+ try {
6047
+ syncStatus = await client.get(`/api/projects/${selectedShared.id}/sync-status`);
6048
+ } catch {
6049
+ }
6050
+ syncStatusSpinner.stop();
6051
+ if (syncStatus?.hasRecentSync && syncStatus.lastSyncedBy) {
6052
+ printInfo(brand("Recent sync detected"));
6053
+ printInfo(dim(`${syncStatus.lastSyncedBy.name} synced from ${syncStatus.lastSyncedBy.deviceName} recently`));
6054
+ const confirmChoice = await select2({
6055
+ message: brand("Continue anyway?"),
6056
+ options: [
6057
+ { value: "yes", label: "Yes, continue", hint: dim("Pull the latest version") },
6058
+ { value: "no", label: "Cancel", hint: dim("Wait for them to finish") }
6059
+ ]
6060
+ });
6061
+ if (isCancel5(confirmChoice) || confirmChoice === "no") return "cancelled";
6062
+ }
6063
+ const existingCheck2 = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
6064
+ if (existingCheck2 === "cancelled") {
6065
+ return "cancelled";
6066
+ }
6067
+ const result2 = await collaboratorBundlePull(
6068
+ client,
6069
+ config.deviceId,
6070
+ cwd,
6071
+ selectedShared.id,
6072
+ selectedShared.canonicalPath,
6073
+ projectsDir,
6074
+ options
6075
+ );
6076
+ if (result2.fileCount === 0 && !options.dryRun) {
6077
+ printInfo("No files to download.");
6078
+ return "cancelled";
6079
+ }
6080
+ if (!options.dryRun) {
6081
+ const foreignEncoded = selectedShared.canonicalPath.replace(/\//g, "%2F");
6082
+ await createProjectSymlink(projectsDir, ctx.encodedPath, foreignEncoded);
6083
+ await saveProjectLink(cwd, {
6084
+ projectId: selectedShared.id,
6085
+ foreignEncodedDir: foreignEncoded,
6086
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
6087
+ });
6088
+ printInfo(`Synced from: ${dim(`${selectedShared.owner.name}'s ${selectedShared.displayName}`)}`);
6089
+ }
6090
+ return "pulled";
6091
+ }
6092
+ const deviceChoice = await select2({
6093
+ message: brand("Select device"),
6094
+ options: otherDevices.map((d) => ({
6095
+ value: d.id,
6096
+ label: d.name,
6097
+ hint: dim(`${d.hostname} \xB7 ${d.platform}`)
6098
+ }))
6099
+ });
6100
+ if (isCancel5(deviceChoice)) return "cancelled";
6101
+ const spinner = createSpinner("Loading projects...").start();
6102
+ let projects;
6103
+ try {
6104
+ projects = await client.get(`/api/devices/${deviceChoice}/projects`);
6105
+ } catch (err) {
6106
+ spinner.stop();
6107
+ if (err instanceof AuthExpiredError) throw err;
6108
+ printError("Failed to load projects from device.");
6109
+ return "cancelled";
6110
+ }
6111
+ spinner.stop();
6112
+ if (projects.length === 0) {
6113
+ printInfo("No projects found on that device.");
6114
+ return "cancelled";
6115
+ }
6116
+ const projectChoice = await select2({
6117
+ message: brand("Select project"),
6118
+ options: projects.map((p) => ({
6119
+ value: p.id,
6120
+ label: p.displayName || p.originalPath,
6121
+ hint: dim(p.localPath)
6122
+ }))
6123
+ });
6124
+ if (isCancel5(projectChoice)) return "cancelled";
6125
+ const selectedProject = projects.find((p) => p.id === projectChoice);
6126
+ const existingCheck = await handleExistingSessionDir(projectsDir, ctx.encodedPath);
6127
+ if (existingCheck === "cancelled") {
6128
+ return "cancelled";
6129
+ }
6130
+ const result = await crossMachineBundlePull(
6131
+ client,
6132
+ config.deviceId,
6133
+ cwd,
6134
+ deviceChoice,
6135
+ selectedProject.id,
6136
+ selectedProject.encodedDir,
6137
+ projectsDir,
6138
+ options
6139
+ );
6140
+ if (result.fileCount === 0 && !options.dryRun) {
6141
+ printInfo("No files to download.");
6142
+ return "cancelled";
6143
+ }
6144
+ if (!options.dryRun) {
6145
+ await createProjectSymlink(projectsDir, ctx.encodedPath, selectedProject.encodedDir);
6146
+ await saveProjectLink(cwd, {
6147
+ projectId: selectedProject.id,
6148
+ foreignEncodedDir: selectedProject.encodedDir,
6149
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
6150
+ });
6151
+ printInfo(`Symlink: ${dim(ctx.encodedPath)} \u2192 ${dim(selectedProject.encodedDir)}`);
6152
+ }
6153
+ return "pulled";
6154
+ }
6155
+ async function handleExistingSessionDir(projectsDir, localEncoded) {
6156
+ const symlinkPath = join6(projectsDir, localEncoded);
6157
+ try {
6158
+ const stats = await lstat2(symlinkPath);
6159
+ if (stats.isSymbolicLink()) {
6160
+ return "continue";
6161
+ }
6162
+ if (stats.isDirectory()) {
6163
+ printInfo(brand("Local session directory already exists"));
6164
+ printInfo(dim(symlinkPath));
6165
+ const choice = await select2({
6166
+ message: brand("What would you like to do?"),
6167
+ options: [
6168
+ {
6169
+ value: "backup",
6170
+ label: "Backup and continue",
6171
+ hint: dim("Move existing to .bak and sync from remote")
6172
+ },
6173
+ {
6174
+ value: "cancel",
6175
+ label: "Stop sync",
6176
+ hint: dim("Keep existing local sessions")
6177
+ }
6178
+ ]
6179
+ });
6180
+ if (isCancel5(choice) || choice === "cancel") {
6181
+ return "cancelled";
6182
+ }
6183
+ const backupPath = `${symlinkPath}.bak.${Date.now()}`;
6184
+ await rename(symlinkPath, backupPath);
6185
+ printSuccess(`Backed up to: ${dim(backupPath)}`);
6186
+ return "continue";
6187
+ }
6188
+ } catch {
6189
+ }
6190
+ return "continue";
6191
+ }
6192
+ async function createProjectSymlink(projectsDir, localEncoded, foreignEncoded) {
6193
+ const symlinkPath = join6(projectsDir, localEncoded);
6194
+ await mkdir4(projectsDir, { recursive: true });
6195
+ try {
6196
+ const stats = await lstat2(symlinkPath);
6197
+ if (stats.isSymbolicLink()) {
6198
+ await unlink(symlinkPath);
6199
+ }
6200
+ } catch {
6201
+ }
6202
+ await symlink(foreignEncoded, symlinkPath);
6203
+ }
6204
+ async function bundlePushPhase(client, deviceId, projectPath, manifest, projectDir, projectId, options) {
6205
+ if (manifest.length === 0) {
6206
+ return { fileCount: 0, projectId: projectId || "", failed: false };
6207
+ }
6208
+ if (projectId) {
6209
+ const permission = await checkPushPermission2(client, projectId);
6210
+ if (!permission.allowed) {
6211
+ printError(`Cannot push: You have ${brand(permission.role)} access to this project.`);
6212
+ printInfo("Only project editors and owners can push changes.");
6213
+ return { fileCount: 0, projectId, failed: true };
6214
+ }
6215
+ }
6216
+ const totalBytes = manifest.reduce((sum, f) => sum + f.size, 0);
6217
+ printInfo(`${brand(String(manifest.length))} files ${dim(`(${formatBytes(totalBytes)})`)} to push`);
6218
+ if (options.dryRun) {
6219
+ if (options.verbose) {
6220
+ for (const entry of manifest) {
6221
+ console.log(` ${success("+")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
6222
+ }
6223
+ }
6224
+ return { fileCount: manifest.length, projectId: projectId || "", failed: false };
6225
+ }
6226
+ const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
6227
+ try {
6228
+ const spinner = createSpinner("Syncing...").start();
6229
+ const bundleResult = await createBundle(projectDir, manifest, tempBundle, (bytes, total, phase) => {
6230
+ if (phase === "compressing") {
6231
+ const percent = Math.round(bytes / total * 100);
6232
+ spinner.text = `Compressing files... ${dim(`${percent}%`)}`;
6233
+ }
6234
+ });
6235
+ const progress = createTransferProgress("Uploading", bundleResult.size, spinner);
6236
+ let prepareResponse;
6237
+ try {
6238
+ prepareResponse = await client.post("/api/sync/push/bundle/prepare", {
6239
+ deviceId,
6240
+ projectPath,
6241
+ projectId,
6242
+ manifest,
6243
+ bundleSize: bundleResult.size
6244
+ });
6245
+ } catch (err) {
6246
+ spinner.stop();
6247
+ if (err instanceof AuthExpiredError) throw err;
6248
+ printError(`Push failed: ${err instanceof Error ? err.message : "Unknown error"}`);
6249
+ return { fileCount: 0, projectId: projectId || "", failed: true };
6250
+ }
6251
+ await streamUpload(prepareResponse.uploadUrl, tempBundle, (bytes, total) => {
6252
+ progress.update(bytes);
6253
+ });
6254
+ try {
6255
+ await client.post("/api/sync/push/bundle/complete", {
6256
+ syncEventId: prepareResponse.syncEventId,
6257
+ manifest
6258
+ });
6259
+ } catch {
5568
6260
  }
5569
6261
  const compressionRatio = Math.round((1 - bundleResult.size / bundleResult.originalSize) * 100);
5570
6262
  progress.finish(`Pushed ${manifest.length} files ${dim(`(${formatBytes(bundleResult.size)}, ${compressionRatio}% smaller)`)}`);
5571
6263
  return { fileCount: manifest.length, projectId: prepareResponse.projectId, failed: false };
5572
6264
  } finally {
5573
6265
  try {
5574
- await rm(tempBundle, { force: true });
6266
+ await rm3(tempBundle, { force: true });
5575
6267
  } catch {
5576
6268
  }
5577
6269
  }
@@ -5606,7 +6298,7 @@ async function bundlePullPhase(client, deviceId, projectPath, projectDir, projec
5606
6298
  }
5607
6299
  return { fileCount, failed: false };
5608
6300
  }
5609
- const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
6301
+ const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
5610
6302
  try {
5611
6303
  const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
5612
6304
  await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
@@ -5626,7 +6318,7 @@ async function bundlePullPhase(client, deviceId, projectPath, projectDir, projec
5626
6318
  return { fileCount: extractedCount, failed: false };
5627
6319
  } finally {
5628
6320
  try {
5629
- await rm(tempBundle, { force: true });
6321
+ await rm3(tempBundle, { force: true });
5630
6322
  } catch {
5631
6323
  }
5632
6324
  }
@@ -5662,15 +6354,15 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
5662
6354
  }
5663
6355
  return { fileCount, failed: false };
5664
6356
  }
5665
- const targetDir = join4(projectsDir, foreignEncodedDir);
5666
- const tempBundle = join4(tmpdir(), `claude-sync-bundle-${Date.now()}.tar.gz`);
6357
+ const targetDir = join6(projectsDir, foreignEncodedDir);
6358
+ const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
5667
6359
  try {
5668
6360
  const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
5669
6361
  await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
5670
6362
  progress.update(bytes);
5671
6363
  });
5672
6364
  spinner.text = "Extracting files...";
5673
- await mkdir3(targetDir, { recursive: true });
6365
+ await mkdir4(targetDir, { recursive: true });
5674
6366
  const extractedCount = await extractBundle(tempBundle, targetDir);
5675
6367
  try {
5676
6368
  const completeManifest = await walkDirectory(targetDir);
@@ -5684,137 +6376,93 @@ async function crossMachineBundlePull(client, deviceId, projectPath, fromDeviceI
5684
6376
  return { fileCount: extractedCount, failed: false };
5685
6377
  } finally {
5686
6378
  try {
5687
- await rm(tempBundle, { force: true });
6379
+ await rm3(tempBundle, { force: true });
5688
6380
  } catch {
5689
6381
  }
5690
6382
  }
5691
6383
  }
5692
- function syncCommand() {
5693
- 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) => {
5694
- await runSync(options);
5695
- });
5696
- }
5697
- var import_utils3;
5698
- var init_sync = __esm({
5699
- "src/commands/sync.ts"() {
5700
- "use strict";
5701
- init_esm_shims();
5702
- init_config();
5703
- init_api_client();
5704
- init_sync_engine();
5705
- init_progress();
5706
- init_bundle();
5707
- import_utils3 = __toESM(require_dist(), 1);
5708
- init_theme();
6384
+ async function collaboratorBundlePull(client, deviceId, projectPath, projectId, canonicalPath, projectsDir, options) {
6385
+ const spinner = createSpinner("Preparing download...").start();
6386
+ let prepareResponse;
6387
+ try {
6388
+ prepareResponse = await client.post("/api/sync/pull/bundle/prepare", {
6389
+ deviceId,
6390
+ projectPath,
6391
+ projectId
6392
+ });
6393
+ } catch (err) {
6394
+ spinner.stop();
6395
+ if (err instanceof AuthExpiredError) throw err;
6396
+ printError(`Pull failed: ${err instanceof Error ? err.message : "Unknown error"}`);
6397
+ return { fileCount: 0, failed: true };
5709
6398
  }
5710
- });
5711
-
5712
- // src/commands/status.ts
5713
- var status_exports = {};
5714
- __export(status_exports, {
5715
- statusCommand: () => statusCommand
5716
- });
5717
- import { Command as Command5 } from "commander";
5718
- function statusCommand() {
5719
- return new Command5("status").description("Show sync status for current project").action(async () => {
5720
- printIntro("Status");
5721
- const config = await loadConfig();
5722
- const cwd = process.cwd();
5723
- printLabel("API", config.apiUrl);
5724
- printLabel("Authenticated", isAuthenticated(config) ? success("yes") : error("no"));
5725
- printLabel("Device", config.deviceName || dim("none"));
5726
- printLabel("Project", brand(cwd));
5727
- const ctx = await resolveProjectState(cwd);
5728
- printLabel("State", ctx.state === "none" ? dim("no session data") : ctx.state === "symlink" ? `symlink \u2192 ${dim(ctx.symlinkTarget || "unknown")}` : "local");
5729
- if (!isAuthenticated(config) || ctx.state === "none") return;
5730
- const spinner = createSpinner("Scanning files...").start();
5731
- const savedManifest = await loadProjectManifest(cwd);
5732
- const currentManifest = await buildProjectManifest(cwd);
5733
- const diff = computeDiff(currentManifest, savedManifest);
6399
+ if (!prepareResponse.downloadUrl || !prepareResponse.bundleSize) {
5734
6400
  spinner.stop();
5735
- const totalSize = currentManifest.reduce((sum, e) => sum + e.size, 0);
5736
- printDivider();
5737
- printLabel("Local files", `${brand(String(currentManifest.length))} ${dim(`(${formatBytes(totalSize)})`)}`);
5738
- console.log();
5739
- printInfo("Changes since last sync:");
5740
- console.log(` ${success(`+${diff.added.length}`)} added ${warn(`~${diff.modified.length}`)} modified ${error(`-${diff.deleted.length}`)} deleted`);
5741
- console.log();
5742
- });
5743
- }
5744
- var init_status = __esm({
5745
- "src/commands/status.ts"() {
5746
- "use strict";
5747
- init_esm_shims();
5748
- init_config();
5749
- init_sync_engine();
5750
- init_progress();
5751
- init_theme();
6401
+ return { fileCount: 0, failed: false };
5752
6402
  }
5753
- });
5754
-
5755
- // src/commands/whoami.ts
5756
- var whoami_exports = {};
5757
- __export(whoami_exports, {
5758
- whoamiCommand: () => whoamiCommand
5759
- });
5760
- import { Command as Command6 } from "commander";
5761
- function whoamiCommand() {
5762
- return new Command6("whoami").description("Show current user and device info").action(async () => {
5763
- printIntro("Who Am I");
5764
- const config = await loadConfig();
5765
- if (!isAuthenticated(config)) {
5766
- printError("Not logged in. Run `claude-sync login` first.");
5767
- return;
6403
+ const fileCount = prepareResponse.manifest.length;
6404
+ if (options.dryRun) {
6405
+ spinner.stop();
6406
+ printInfo(`${brand(String(fileCount))} files to pull`);
6407
+ if (options.verbose) {
6408
+ for (const entry of prepareResponse.manifest) {
6409
+ console.log(` ${brand("\u25CB")} ${entry.path} ${dim(`(${formatBytes(entry.size)})`)}`);
6410
+ }
5768
6411
  }
5769
- const client = new ApiClient(config.apiUrl);
5770
- const spinner = createSpinner("Fetching user info...").start();
6412
+ return { fileCount, failed: false };
6413
+ }
6414
+ const foreignEncoded = canonicalPath.replace(/\//g, "%2F");
6415
+ const targetDir = join6(projectsDir, foreignEncoded);
6416
+ const tempBundle = join6(tmpdir3(), `claude-sync-bundle-${Date.now()}.tar.gz`);
6417
+ try {
6418
+ const progress = createTransferProgress("Downloading", prepareResponse.bundleSize, spinner);
6419
+ await streamDownload(prepareResponse.downloadUrl, tempBundle, prepareResponse.bundleSize, (bytes) => {
6420
+ progress.update(bytes);
6421
+ });
6422
+ spinner.text = "Extracting files...";
6423
+ await mkdir4(targetDir, { recursive: true });
6424
+ const extractedCount = await extractBundle(tempBundle, targetDir);
5771
6425
  try {
5772
- const user = await client.get("/api/auth/me");
5773
- spinner.stop();
5774
- printLabel("User", `${user.name} ${dim(`(${user.email})`)}`);
5775
- printLabel("Plan", brand(user.plan));
5776
- printDivider();
5777
- printLabel("Device", config.deviceName || dim("none"));
5778
- printLabel("Device ID", config.deviceId || dim("not registered"));
5779
- } catch (err) {
5780
- spinner.stop();
5781
- if (err instanceof AuthExpiredError) throw err;
5782
- printLabel("Email", config.email || dim("unknown"));
5783
- printLabel("Device", config.deviceName || dim("none"));
6426
+ const completeManifest = await walkDirectory(targetDir);
6427
+ await client.post("/api/sync/pull/bundle/complete", {
6428
+ syncEventId: prepareResponse.syncEventId,
6429
+ manifest: completeManifest
6430
+ });
6431
+ } catch {
5784
6432
  }
5785
- console.log();
5786
- });
6433
+ progress.finish(`Pulled ${extractedCount} files ${dim(`(${formatBytes(prepareResponse.bundleSize)})`)}`);
6434
+ return { fileCount: extractedCount, failed: false };
6435
+ } finally {
6436
+ try {
6437
+ await rm3(tempBundle, { force: true });
6438
+ } catch {
6439
+ }
6440
+ }
5787
6441
  }
5788
- var init_whoami = __esm({
5789
- "src/commands/whoami.ts"() {
5790
- "use strict";
5791
- init_esm_shims();
5792
- init_config();
5793
- init_api_client();
5794
- init_progress();
5795
- init_theme();
6442
+ async function checkPushPermission2(client, projectId) {
6443
+ try {
6444
+ const myRole = await client.get(`/api/projects/${projectId}/my-role`);
6445
+ return { allowed: myRole.canPush, role: myRole.role };
6446
+ } catch {
6447
+ return { allowed: true, role: "owner" };
5796
6448
  }
5797
- });
5798
-
5799
- // bin/claude-sync.ts
5800
- init_esm_shims();
6449
+ }
6450
+ function syncCommand() {
6451
+ return new Command7("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) => {
6452
+ await runSync(options);
6453
+ });
6454
+ }
5801
6455
 
5802
6456
  // src/index.ts
5803
- init_esm_shims();
5804
- init_login();
5805
- init_logout();
5806
- init_device();
5807
- init_sync();
5808
6457
  init_status();
5809
6458
  init_whoami();
5810
- import { Command as Command7 } from "commander";
5811
6459
 
5812
6460
  // src/commands/tui.ts
5813
6461
  init_esm_shims();
5814
6462
  init_theme();
5815
6463
  init_config();
5816
6464
  init_api_client();
5817
- import { select as select3, text as text3, isCancel as isCancel4 } from "@clack/prompts";
6465
+ import { select as select3, text as text4, isCancel as isCancel6 } from "@clack/prompts";
5818
6466
  import chalk3 from "chalk";
5819
6467
  function buildMenu(loggedIn) {
5820
6468
  if (!loggedIn) {
@@ -5823,7 +6471,10 @@ function buildMenu(loggedIn) {
5823
6471
  ];
5824
6472
  }
5825
6473
  return [
5826
- { value: "sync", label: "Sync", hint: "Sync current project" },
6474
+ { value: "quick-push", label: "Quick Push", hint: "Push without message" },
6475
+ { value: "push-message", label: "Push with Message", hint: "Add a commit message" },
6476
+ { value: "pull", label: "Pull", hint: "Get latest changes" },
6477
+ { value: "log", label: "View History", hint: "Show commit log" },
5827
6478
  { value: "status", label: "Status", hint: "Show sync state" },
5828
6479
  { value: "device:add", label: "Add Device", hint: "Register this machine" },
5829
6480
  { value: "device:list", label: "List Devices", hint: "Show registered devices" },
@@ -5865,7 +6516,7 @@ async function runTui() {
5865
6516
  message: brand("What would you like to do?"),
5866
6517
  options
5867
6518
  });
5868
- if (isCancel4(choice) || choice === "quit") {
6519
+ if (isCancel6(choice) || choice === "quit") {
5869
6520
  printOutro("Goodbye!");
5870
6521
  return;
5871
6522
  }
@@ -5909,21 +6560,33 @@ async function promptDeviceSelection(config) {
5909
6560
  hint: d.id === config.deviceId ? dim("current") : void 0
5910
6561
  }))
5911
6562
  });
5912
- if (isCancel4(choice)) return null;
6563
+ if (isCancel6(choice)) return null;
5913
6564
  return choice;
5914
6565
  }
5915
6566
  async function executeCommand(command) {
5916
6567
  const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), login_exports));
5917
6568
  const { logoutCommand: logoutCommand2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
5918
6569
  const { deviceCommand: deviceCommand2 } = await Promise.resolve().then(() => (init_device(), device_exports));
5919
- const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
6570
+ const { pushCommand: pushCommand2 } = await Promise.resolve().then(() => (init_push(), push_exports));
6571
+ const { pullCommand: pullCommand2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));
6572
+ const { logCommand: logCommand2 } = await Promise.resolve().then(() => (init_log(), log_exports));
5920
6573
  const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), status_exports));
5921
6574
  const { whoamiCommand: whoamiCommand2 } = await Promise.resolve().then(() => (init_whoami(), whoami_exports));
5922
6575
  const config = await loadConfig();
5923
6576
  const handlers = {
5924
6577
  login: () => loginCommand2().parseAsync(["", ""]),
5925
6578
  logout: () => logoutCommand2().parseAsync(["", ""]),
5926
- sync: () => syncCommand2().parseAsync(["", ""]),
6579
+ "quick-push": () => pushCommand2().parseAsync(["", "", "-m", "Quick sync"]),
6580
+ "push-message": async () => {
6581
+ const message = await text4({
6582
+ message: brand("Commit message:"),
6583
+ placeholder: "Describe your changes..."
6584
+ });
6585
+ if (isCancel6(message)) return;
6586
+ await pushCommand2().parseAsync(["", "", "-m", message]);
6587
+ },
6588
+ pull: () => pullCommand2().parseAsync(["", "", "-y"]),
6589
+ log: () => logCommand2().parseAsync(["", ""]),
5927
6590
  status: () => statusCommand2().parseAsync(["", ""]),
5928
6591
  whoami: () => whoamiCommand2().parseAsync(["", ""]),
5929
6592
  "device:add": () => deviceCommand2().parseAsync(["", "", "add"]),
@@ -5934,8 +6597,8 @@ async function executeCommand(command) {
5934
6597
  await deviceCommand2().parseAsync(["", "", "remove", deviceId]);
5935
6598
  },
5936
6599
  "device:rename": async () => {
5937
- const name = await text3({ message: `${brand("New device name")}:` });
5938
- if (isCancel4(name)) return;
6600
+ const name = await text4({ message: `${brand("New device name")}:` });
6601
+ if (isCancel6(name)) return;
5939
6602
  await deviceCommand2().parseAsync(["", "", "rename", name]);
5940
6603
  }
5941
6604
  };
@@ -5948,7 +6611,7 @@ async function executeCommand(command) {
5948
6611
  // src/index.ts
5949
6612
  init_config();
5950
6613
  function run() {
5951
- const program = new Command7();
6614
+ const program = new Command10();
5952
6615
  program.name("claude-sync").description("Sync Claude Code sessions across machines \u2014 claude-sync.com").version("0.1.0").option("--menu", "Open interactive menu").action(async (options) => {
5953
6616
  if (options.menu) {
5954
6617
  await runTui();
@@ -5964,6 +6627,9 @@ function run() {
5964
6627
  program.addCommand(loginCommand());
5965
6628
  program.addCommand(logoutCommand());
5966
6629
  program.addCommand(deviceCommand());
6630
+ program.addCommand(pushCommand());
6631
+ program.addCommand(pullCommand());
6632
+ program.addCommand(logCommand());
5967
6633
  program.addCommand(syncCommand());
5968
6634
  program.addCommand(statusCommand());
5969
6635
  program.addCommand(whoamiCommand());